本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本资料深入讲解基于STM32微控制器的平衡车开发技术,涵盖传感器数据采集、姿态融合、PID控制算法、电机驱动、蓝牙通信等核心内容。通过开源固件项目,学习者可掌握使用DMP数字运动处理器提升系统性能、FreeRTOS多任务调度、Cortex-M内核开发、电源管理及故障检测等关键技术。适用于嵌入式开发者与电子爱好者,帮助其构建稳定高效的自平衡系统。
STM32平衡车资料

1. STM32微控制器架构与开发环境搭建

STM32系列微控制器基于ARM Cortex-M内核,广泛应用于高性能、低成本、低功耗的嵌入式控制系统中。其架构包含多个外设模块,如GPIO、定时器、ADC、UART等,为嵌入式开发提供了丰富的硬件资源支持。

在进行项目开发前,必须搭建完整的开发环境。本章将重点介绍Keil MDK与STM32CubeMX的安装与配置流程。Keil MDK作为主流的嵌入式开发IDE,提供了编译、调试、仿真等功能;而STM32CubeMX则用于初始化配置,可生成初始化代码,极大提升开发效率。

开发环境搭建流程如下:

  1. 安装Keil MDK(推荐版本:Keil uVision5)并激活相应器件支持包;
  2. 安装STM32CubeMX,并配置Java运行环境;
  3. 使用STM32CubeMX选择目标芯片型号,配置时钟、GPIO、外设等;
  4. 生成Keil工程模板,导入Keil MDK进行编译和下载调试。

通过上述步骤,开发者可以快速构建一个可运行的STM32基础工程,为后续传感器控制、算法实现和系统集成打下坚实基础。

2. 陀螺仪与加速度计传感器融合技术

在现代嵌入式系统中,尤其是涉及姿态检测、运动控制、导航等应用的设备中, 陀螺仪 加速度计 作为核心传感器,其数据的准确性和稳定性直接决定了系统的性能。然而,单一传感器往往存在固有缺陷,例如陀螺仪存在积分漂移问题,而加速度计易受振动干扰。因此, 传感器融合技术 成为解决这一问题的关键。

本章将从传感器的基本原理入手,逐步深入到数据采集、处理、融合算法设计与优化,并结合实际应用场景,分析误差补偿机制,帮助读者构建一套完整的传感器数据处理与融合知识体系。

2.1 传感器基础与工作原理

2.1.1 陀螺仪的工作原理与数据输出

陀螺仪是一种测量角速度(angular velocity)的传感器,常用于检测物体绕某一轴旋转的速率。其核心原理基于 科里奥利效应 (Coriolis Effect),在微机电系统(MEMS)陀螺仪中,通过微小振动结构的偏移来检测角速度。

工作原理简述:
  • 内部质量块在驱动电路作用下以固定频率振动;
  • 当外部物体绕轴旋转时,科里奥利力作用于质量块;
  • 该力引起质量块的位移,通过电容变化检测;
  • 最终转换为电压信号,经模数转换后输出数字角速度值。
输出数据格式:

以MPU6050为例,陀螺仪输出为16位ADC值,量程可配置为±250、±500、±1000、±2000 dps(度/秒),例如:

int16_t gyro_x = (gyro_data[0] << 8) | gyro_data[1]; // X轴原始数据
数据转换公式:

若量程为±2000 dps,灵敏度为 16.4 LSB/dps ,则实际角速度为:

float gyro_x_deg_per_sec = (float)gyro_x / 16.4f;
优缺点分析:
特性 优点 缺点
响应速度 快速响应角速度变化 积分漂移严重
噪声 低噪声 长时间积分导致误差累积
应用场景 短时姿态估计、旋转检测 不适合长期绝对角度测量

2.1.2 加速度计的基本原理与测量方法

加速度计用于测量线性加速度(linear acceleration),其工作原理基于 惯性力作用于质量块 。在静态状态下,加速度计可测量重力方向,从而估算设备的姿态角度。

工作原理简述:
  • 内部质量块受加速度影响发生位移;
  • 位移引起电容或电阻变化;
  • 转换为电压信号,经ADC输出数字值。
输出数据格式:

MPU6050加速度计输出为16位ADC值,量程可配置为±2g、±4g、±8g、±16g,例如:

int16_t acc_x = (acc_data[0] << 8) | acc_data[1]; // X轴原始数据
数据转换公式:

若量程为±2g,灵敏度为 16384 LSB/g ,则实际加速度为:

float acc_x_g = (float)acc_x / 16384.0f;
优缺点分析:
特性 优点 缺点
绝对角度 可测量重力方向,估算姿态角 易受振动和线性运动干扰
响应延迟 相对稳定 对高频运动响应不敏感
应用场景 静态姿态测量、倾角检测 不适合动态旋转状态下的角度估计

2.2 传感器数据采集与处理

2.2.1 数据采集的通信接口(I2C/SPI)

STM32微控制器支持多种通信接口,其中 I2C SPI 是与传感器通信的常用方式。

I2C通信特点:
  • 双线制:SCL(时钟线)、SDA(数据线)
  • 支持多设备连接,需地址区分
  • 通信速率:标准模式(100 kbps)、快速模式(400 kbps)
示例代码(STM32 HAL库 I2C读取MPU6050数据):
uint8_t data[6];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR << 1, ACCEL_XOUT_H, 1, data, 6, HAL_MAX_DELAY);
代码逻辑分析:
  • &hi2c1 :使用的I2C句柄;
  • MPU6050_ADDR << 1 :I2C地址左移一位,符合HAL库要求;
  • ACCEL_XOUT_H :加速度计X轴高字节寄存器地址;
  • data :用于存储读取到的6字节数据;
  • HAL_MAX_DELAY :无限等待模式,适用于调试环境。
SPI通信特点:
  • 四线制:SCLK、MOSI、MISO、CS
  • 通信速率高,可达几十Mbps
  • 占用引脚多,适合高速传输

2.2.2 原始数据的滤波与校准处理

传感器输出的原始数据往往包含噪声和偏差,需进行滤波和校准处理。

常见滤波方法:
  1. 滑动平均滤波 (Moving Average Filter)
  2. 低通滤波 (Low-pass Filter)
  3. 卡尔曼滤波 (Kalman Filter)
示例:低通滤波实现(C语言)
#define ALPHA 0.1f  // 滤波系数
float filtered_value = 0.0f;

filtered_value = ALPHA * raw_value + (1 - ALPHA) * filtered_value;
参数说明:
  • ALPHA :滤波系数,值越大响应越快但滤波效果越差;
  • raw_value :当前原始数据;
  • filtered_value :滤波后的输出值。
校准方法:
  • 零点校准 :在静止状态下采集多个样本,计算偏移量;
  • 比例因子校准 :通过已知加速度值(如重力)进行标定。
示例:陀螺仪零点校准(伪代码)
int16_t gyro_offset = 0;
for(int i = 0; i < 1000; i++) {
    gyro_offset += read_gyro();
}
gyro_offset /= 1000;  // 平均偏移值
流程图(mermaid):
graph TD
    A[开始采集] --> B[读取原始数据]
    B --> C[累加偏移值]
    C --> D{是否达到1000次?}
    D -- 是 --> E[计算平均偏移]
    D -- 否 --> B

2.3 传感器融合算法实现

2.3.1 卡尔曼滤波算法的原理与应用

卡尔曼滤波是一种递归滤波算法,适用于线性系统中的状态估计。其核心思想是通过系统模型预测和测量更新来估计真实状态。

算法步骤:
  1. 预测阶段 :根据系统模型预测状态和误差协方差;
  2. 更新阶段 :根据测量值更新状态估计;
  3. 迭代计算 :不断重复上述过程。
示例代码(简化卡尔曼滤波):
float kalman_filter(float raw_data) {
    static float x_est = 0.0f, P = 1.0f;
    float Q = 0.001f, R = 0.1f;
    float K;

    // 预测
    x_est = x_est;  // 假设无系统模型
    P = P + Q;

    // 更新
    K = P / (P + R);
    x_est = x_est + K * (raw_data - x_est);
    P = (1 - K) * P;

    return x_est;
}
代码逻辑分析:
  • x_est :状态估计值;
  • P :估计误差协方差;
  • Q :过程噪声;
  • R :观测噪声;
  • K :卡尔曼增益,用于权衡预测与测量的比重。

2.3.2 互补滤波器的设计与优化

互补滤波器是一种将陀螺仪和加速度计数据融合的简单有效方法,其基本思想是:

  • 使用陀螺仪积分得到角度;
  • 使用加速度计计算出角度;
  • 将两者通过加权相加,得到最终角度。
公式:
angle = alpha * (angle_prev + gyro * dt) + (1 - alpha) * angle_acc;

其中:
- alpha :权重系数(0~1);
- dt :采样时间间隔;
- angle_prev :上一次估计角度;
- gyro :陀螺仪角速度;
- angle_acc :加速度计计算出的角度。

优化建议:
  • 根据系统动态调整 alpha
  • 加入动态权重机制,提升适应性;
  • 结合卡尔曼滤波进一步提升精度。

2.4 实际应用中的误差补偿与稳定性提升

2.4.1 温度漂移与安装误差的修正方法

温度漂移补偿:

陀螺仪和加速度计的输出受温度影响,表现为零点漂移。可以通过以下方法补偿:

  • 温度传感器联动 :使用片内温度传感器记录当前温度;
  • 查找表法 :建立温度-偏移查找表;
  • 实时补偿算法 :采用线性回归或多项式拟合。
示例:温度补偿公式(线性)
gyro_offset = gyro_offset_25C + temp_coeff * (current_temp - 25);
安装误差修正:

传感器安装不正可能导致轴向误差。可通过以下方式修正:

  • 静态标定 :在已知姿态下测量各轴输出;
  • 旋转矩阵校正 :使用3D旋转矩阵进行坐标变换;
  • 软件补偿 :在数据处理阶段加入修正因子。

2.4.2 传感器融合在姿态检测中的应用案例

案例背景:

使用MPU6050传感器进行姿态检测,应用于自平衡小车。

系统结构图(mermaid):
graph TD
    A[MPU6050] --> B[I2C通信]
    B --> C[STM32]
    C --> D[传感器融合算法]
    D --> E[PWM输出]
    E --> F[电机驱动]
    F --> G[姿态调整]
核心代码(融合角度计算):
// 假设已获取滤波后的陀螺仪和加速度计数据
float angle_gyro = angle_prev + gyro_z * dt;  // 陀螺仪积分角度
float angle_acc = atan2(acc_y, acc_z) * RAD_TO_DEG;  // 加速度计角度
angle = 0.98 * angle_gyro + 0.02 * angle_acc;  // 互补滤波
参数说明:
  • angle_prev :上一次角度估计值;
  • gyro_z :Z轴角速度;
  • dt :采样时间;
  • acc_y , acc_z :Y、Z轴加速度;
  • RAD_TO_DEG :弧度转角度系数(约57.2958);
实际效果对比表格:
方法 稳定性 抗干扰能力 动态响应 实现复杂度
仅使用陀螺仪
仅使用加速度计
互补滤波
卡尔曼滤波 极强

通过本章内容,读者不仅掌握了陀螺仪与加速度计的基本原理和数据处理方法,还深入理解了传感器融合的核心算法与实际应用技巧。这些知识为后续开发自平衡系统、姿态控制等项目打下了坚实的基础。

3. DMP数字运动处理器原理与应用

本章深入探讨DMP(Digital Motion Processor)在MPU6050等传感器芯片中的核心作用及其在姿态解算中的应用。我们将从DMP的硬件结构和功能特性出发,逐步介绍其固件加载与配置方法、数据交互机制,并结合实际应用展示其在实时姿态检测中的价值。通过本章内容,读者将掌握如何高效利用DMP提升系统性能、降低主控负担,并具备解决实际问题的能力。

3.1 DMP处理器的硬件结构与功能特性

MPU6050 是目前在姿态检测中广泛应用的集成式运动传感器,其内部集成一个高性能的数字运动处理器(DMP)。DMP 的核心作用是实现对原始传感器数据(陀螺仪和加速度计)的预处理和融合,从而输出更高精度的姿态信息。

3.1.1 DMP在MPU6050中的作用与优势

MPU6050 的 DMP 模块主要负责以下任务:

  • 传感器数据融合 :将加速度计和陀螺仪数据进行融合,输出四元数(Quaternions)或欧拉角(Euler Angles)。
  • 滤波与补偿 :执行卡尔曼滤波或互补滤波,消除传感器噪声和漂移。
  • 运动检测与识别 :支持手势识别、动作触发等功能。
  • 降低主控负担 :通过硬件处理代替主控进行大量数学运算,节省主控资源。

优势总结
- 实时性高:DMP硬件加速处理,响应快。
- 资源消耗低:主控只需读取最终结果,无需复杂计算。
- 精度更高:融合算法优化,提升姿态解算稳定性。

3.1.2 DMP与主控芯片的交互机制

DMP 与主控芯片(如 STM32)之间的通信主要通过 I²C 接口完成。以下是典型交互流程:

graph TD
    A[STM32初始化MPU6050] --> B[加载DMP固件]
    B --> C[启动DMP]
    C --> D[配置输出数据格式]
    D --> E[DMP持续采集与处理数据]
    E --> F[STM32读取姿态数据]

主控芯片通过以下寄存器与 DMP 交互:

寄存器地址 功能说明
0x6A DMP 控制寄存器
0x7F DMP 程序计数器
0x92 FIFO 缓冲区控制
0x68 陀螺仪和加速度计数据输出地址

DMP 会将处理后的数据存入 FIFO 缓冲区,主控通过读取 FIFO 数据包获取姿态信息。这种方式避免了频繁中断,提升了系统效率。

3.2 DMP固件加载与配置

为了启用 DMP 功能,需要将预编译的固件加载到 MPU6050 的 RAM 中,并完成必要的初始化配置。

3.2.1 固件烧写与初始化流程

MPU6050 的 DMP 固件通常以数组形式存储在代码中,例如:

const unsigned char dmpImage[] = {
    0x00, 0x01, 0x02, ... // 固件数据
};

以下是加载固件的步骤:

void DMP_Init(void) {
    MPU_WriteReg(PWR_MGMT_1, 0x00); // 唤醒MPU6050
    MPU_WriteReg(USER_CTRL, 0x00); // 禁用DMP
    MPU_WriteReg(USER_CTRL, 0x0C); // 使能DMP和FIFO
    MPU_WriteReg(DMP_CONFIG1, 0x03); // 设置DMP采样率
    MPU_WriteReg(DMP_CONFIG2, 0x00); // 配置输出格式

    // 加载DMP固件
    for (int i = 0; i < sizeof(dmpImage); i++) {
        MPU_WriteReg(DMP_BANK_SEL, i / 256); // 选择内存段
        MPU_WriteReg(DMP_MEM_START_ADDR, i % 256); // 内存地址
        MPU_WriteReg(DMP_MEM_R_W, dmpImage[i]); // 写入数据
    }

    MPU_WriteReg(DMP_INT_EN, 0x01); // 启用DMP中断
    MPU_WriteReg(USER_CTRL, 0x40); // 启动DMP
}
代码分析:
  • MPU_WriteReg :自定义函数,用于向 MPU6050 的寄存器写入数据。
  • PWR_MGMT_1 :电源管理寄存器,用于唤醒 MPU。
  • USER_CTRL :控制 DMP 启动和 FIFO 使能。
  • DMP_CONFIG1/2 :配置 DMP 的采样率和输出格式。
  • DMP_BANK_SEL DMP_MEM_START_ADDR :选择内存段和地址,逐字节写入固件。

3.2.2 运动数据输出格式与解析方法

DMP 支持多种输出格式,常见为四元数(Quaternions)和欧拉角(Euler Angles)。以下是一个典型的 FIFO 数据包结构:

字段 字节数 说明
Header 1 数据包标识
Quaternion W 4 四元数 W 分量
Quaternion X 4 四元数 X 分量
Quaternion Y 4 四元数 Y 分量
Quaternion Z 4 四元数 Z 分量
Timestamp 2 时间戳

解析示例代码如下:

void ParseDMPData(uint8_t *data, float *quat) {
    quat[0] = ((int32_t)data[0] << 24) | ((int32_t)data[1] << 16) |
              ((int32_t)data[2] << 8)  | ((int32_t)data[3]);
    quat[1] = ((int32_t)data[4] << 24) | ((int32_t)data[5] << 16) |
              ((int32_t)data[6] << 8)  | ((int32_t)data[7]);
    quat[2] = ((int32_t)data[8] << 24) | ((int32_t)data[9] << 16) |
              ((int32_t)data[10] << 8) | ((int32_t)data[11]);
    quat[3] = ((int32_t)data[12] << 24) | ((int32_t)data[13] << 16) |
              ((int32_t)data[14] << 8) | ((int32_t)data[15]);

    for (int i = 0; i < 4; i++) {
        quat[i] /= 1073741824.0f; // 转换为浮点数
    }
}
参数说明:
  • data :从 FIFO 读取的数据缓冲区。
  • quat :输出的四元数数组,格式为 [W, X, Y, Z]。
  • 1073741824.0f :是 Q30 格式下的缩放因子,用于归一化。

3.3 DMP在姿态解算中的实际应用

在实际系统中,DMP 的输出可用于姿态估计、运动控制、用户交互等多个场景。

3.3.1 DMP与主控MCU的数据交互

DMP 与主控 MCU 的数据交互通常通过中断触发 + FIFO 读取方式实现。以下为中断服务函数示例:

void MPU6050_IRQHandler(void) {
    uint8_t intStatus = MPU_ReadReg(INT_STATUS);
    if (intStatus & 0x01) { // DMP中断标志
        uint8_t fifoCount = MPU_ReadReg(FIFO_COUNT_H) << 8 | MPU_ReadReg(FIFO_COUNT_L);
        uint8_t fifoBuffer[128];
        MPU_ReadFIFO(fifoBuffer, fifoCount);

        // 解析数据并更新姿态
        ParseDMPData(fifoBuffer, quat);
        ConvertToEulerAngles(quat, eulerAngles);
    }
}
代码分析:
  • INT_STATUS :中断状态寄存器,用于判断中断来源。
  • FIFO_COUNT_H/L :读取 FIFO 中当前数据长度。
  • MPU_ReadFIFO :读取 FIFO 缓冲区内容。
  • ParseDMPData :解析数据为四元数。
  • ConvertToEulerAngles :可选步骤,将四元数转换为欧拉角。

3.3.2 实时姿态数据获取与可视化展示

获取姿态数据后,可通过串口将数据发送至上位机进行可视化。例如使用 Python 的 matplotlib 绘图:

import serial
import matplotlib.pyplot as plt

ser = serial.Serial('COM3', 115200)

plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

while True:
    data = ser.readline().decode().strip().split(',')
    if len(data) == 3:
        x, y, z = map(float, data)
        ax.clear()
        ax.quiver(0, 0, 0, x, y, z)
        plt.pause(0.01)

可视化说明
- 通过串口接收姿态数据(欧拉角或四元数)。
- 使用 quiver 绘制三维向量箭头,实时显示姿态变化。

3.4 DMP性能优化与常见问题分析

在实际应用中,DMP 可能会出现数据延迟、精度下降等问题,需要从配置和算法层面进行优化。

3.4.1 数据延迟与采样率设置优化

DMP 的采样率通过 DMP_CONFIG1 寄存器设置,值为:

Sample Rate = 1kHz / (1 + rate_divider)

建议设置为:

MPU_WriteReg(DMP_CONFIG1, 0x03); // 采样率为 1kHz / 4 = 250Hz

优化建议
- 适当提高采样率可减少延迟,但需注意 FIFO 缓冲区溢出。
- 设置中断触发频率,避免主控过载。

3.4.2 异常情况处理与系统稳定性保障

常见问题及处理方式如下:

问题类型 原因 解决方法
数据异常跳变 初始化未完成 延迟启动 DMP 或等待初始化完成
FIFO 溢出 读取速度过慢 增加中断优先级或使用 DMA 读取
姿态漂移 校准未完成 执行静止状态下的零偏校准
固件加载失败 地址错误 检查固件数组和内存地址映射

此外,建议加入校准机制:

void CalibrateDMP(void) {
    for (int i = 0; i < 1000; i++) {
        ReadRawData(&gyro, &accel);
        gyroBias += gyro;
        accelBias += accel;
    }
    gyroBias /= 1000;
    accelBias /= 1000;
}

该函数在系统启动时运行,用于计算传感器零偏并进行补偿。

4. PID控制算法设计与参数调优

PID控制算法作为工业控制中最经典、最广泛使用的控制策略之一,其在自平衡系统、机器人、飞行器、电机控制等领域具有不可替代的地位。本章将深入剖析PID控制的数学原理与实现逻辑,结合嵌入式系统开发环境(如STM32平台)详细讲解PID控制器的软件实现方式、参数调优方法以及其在平衡车系统中的具体优化应用。通过本章的学习,读者将掌握PID算法的核心思想,并具备在实际工程项目中灵活应用与调优的能力。

4.1 PID控制的基本原理与数学模型

PID控制算法的核心在于通过比例(Proportional)、积分(Integral)、微分(Derivative)三个部分的加权计算,实现对系统误差的动态调整,从而使得系统的输出尽可能接近期望值(设定值)。其数学表达式如下:

u(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{de(t)}{dt}

其中:
- $ u(t) $:控制器的输出;
- $ e(t) = r(t) - y(t) $:误差,即设定值 $ r(t) $ 与系统输出 $ y(t) $ 的差值;
- $ K_p $:比例增益;
- $ K_i $:积分增益;
- $ K_d $:微分增益。

4.1.1 比例、积分、微分三部分的作用

作用 特点 问题
比例(P) 根据当前误差大小进行响应 响应快,误差越大控制输出越大 存在稳态误差
积分(I) 累计误差以消除稳态误差 能消除长期偏差 易引起超调和振荡
微分(D) 根据误差变化趋势提前预测调整 抑制振荡,提高稳定性 对噪声敏感

三者协同作用,才能实现对系统的快速响应、稳定性和精确控制的平衡。

4.1.2 PID控制在自平衡系统中的应用意义

在自平衡系统中,如两轮平衡车或倒立摆系统,系统需要实时检测姿态角度(通常通过陀螺仪和加速度计融合得到),并根据角度偏差计算电机的控制输出。PID控制器能够根据角度误差动态调整电机转速和方向,使系统维持在垂直平衡状态。

在实际工程中,由于系统存在惯性、延迟和外部干扰,仅靠P控制往往无法满足稳定性的需求,因此I和D项的引入对于提升系统鲁棒性至关重要。

4.2 PID控制器的软件实现

在嵌入式系统中,尤其是基于STM32等MCU的控制系统中,PID控制器通常以离散形式实现,即使用离散时间点上的误差值进行计算。下面将详细介绍其软件实现逻辑和关键代码。

4.2.1 控制逻辑的代码实现流程

在STM32系统中,PID控制器通常配合定时器中断进行周期性计算,流程如下:

graph TD
    A[开始] --> B[初始化PID参数]
    B --> C[读取传感器数据]
    C --> D[计算误差e = 设定值 - 实测值]
    D --> E[计算P、I、D分量]
    E --> F[总输出 = Kp*e + Ki*积分(e) + Kd*差分(e)]
    F --> G[输出控制信号]
    G --> H[等待下一次中断]
    H --> C

4.2.2 实时数据反馈与控制输出调整

在实际实现中,我们通常使用一个结构体来封装PID控制器的状态和参数:

typedef struct {
    float Kp;             // 比例增益
    float Ki;             // 积分增益
    float Kd;             // 微分增益
    float setpoint;       // 设定值
    float last_error;     // 上一次误差
    float integral;       // 积分累积
    float output;         // 输出值
    uint32_t last_time;   // 上一次计算时间(用于差分计算)
} PID_Controller;

然后实现一个PID计算函数:

float PID_Compute(PID_Controller *pid, float feedback) {
    float error = pid->setpoint - feedback;
    uint32_t current_time = HAL_GetTick();
    float dt = (current_time - pid->last_time) / 1000.0f;

    // 比例项
    float P_term = pid->Kp * error;

    // 积分项
    pid->integral += error * dt;
    float I_term = pid->Ki * pid->integral;

    // 微分项
    float derivative = (error - pid->last_error) / dt;
    float D_term = pid->Kd * derivative;

    // 输出总和
    float output = P_term + I_term + D_term;

    // 保存状态
    pid->last_error = error;
    pid->last_time = current_time;
    pid->output = output;

    return output;
}
代码逐行解读与参数说明:
  • error = pid->setpoint - feedback :计算当前误差;
  • current_time = HAL_GetTick() :获取当前时间戳(毫秒级);
  • dt = (current_time - pid->last_time) / 1000.0f :计算时间间隔(秒),用于积分和微分;
  • integral += error * dt :积分项累加,注意需考虑积分饱和(可加限制条件);
  • derivative = (error - last_error) / dt :微分项,即误差变化率;
  • output = P + I + D :三者加权输出,作为控制信号发送给执行机构(如电机PWM);
  • 更新last_error和last_time :为下一次计算做准备。

4.3 参数调优方法与实践

PID控制器的性能高度依赖于参数 $ K_p $、$ K_i $、$ K_d $ 的设定。参数设置不当可能导致系统震荡、响应迟缓或无法收敛。因此,调参是实现高性能控制的关键。

4.3.1 Ziegler-Nichols整定法与经验调参

Ziegler-Nichols方法是一种经典的PID参数整定法,适用于线性系统。其步骤如下:

  1. 仅使用比例控制(Kp > 0,Ki = 0,Kd = 0) ,逐步增加Kp直到系统出现持续振荡。
  2. 记录此时的临界增益 $ K_u $ 和振荡周期 $ T_u $。
  3. 根据以下表格设定PID参数:
控制类型 Kp Ki Kd
P 0.5Ku 0 0
PI 0.45Ku 1.2Ku / Tu 0
PID 0.6Ku 2Ku / Tu Ku * Tu / 8

示例:若测得 $ K_u = 50 $,$ T_u = 2s $,则PID参数为:
$ K_p = 30 $, $ K_i = 50 $, $ K_d = 12.5 $

4.3.2 基于系统响应的自动调优策略

随着嵌入式系统性能的提升,越来越多的项目采用自动调优策略,例如:
- 模型识别 + 最优控制 :通过输入阶跃信号获取系统响应,拟合系统模型,再使用LQR、极点配置等方法设计控制器;
- 模糊PID控制 :结合模糊逻辑动态调整PID参数;
- 遗传算法、粒子群优化 :通过智能算法自动搜索最优参数组合。

这些方法虽然复杂,但在非线性系统或高精度控制中具有显著优势。

4.4 PID控制在平衡车系统中的优化应用

在实际的两轮平衡车系统中,PID控制不仅要处理角度误差,还需要考虑电机响应、系统耦合、外部干扰等问题。

4.4.1 多轴控制中的耦合问题处理

平衡车通常需要控制两个自由度(俯仰角与转向角),但两者之间存在耦合效应。例如,转向控制会影响车身的平衡状态,反之亦然。

解决思路
- 使用多环PID控制:外环控制角度,内环控制角速度;
- 引入耦合补偿项:根据转向角速度调整平衡控制量;
- 使用IMU数据进行姿态解算(如通过DMP或卡尔曼滤波),提高角度精度。

4.4.2 抗干扰能力提升与响应速度优化

为了提升系统的鲁棒性,可以在PID控制中引入以下优化手段:

  1. 误差死区(Deadband) :在误差较小时不进行控制,防止系统在平衡点附近震荡;
  2. 积分限幅(Integral Windup) :防止积分项过大导致输出饱和;
  3. 前馈控制(Feedforward) :在PID基础上加入前馈项,加快响应速度;
  4. 输出限幅 :限制控制器输出的最大值,防止电机过载。

示例:加入输出限幅后的代码调整:

// 在PID_Compute函数最后加入限幅逻辑
if (output > MAX_OUTPUT) {
    output = MAX_OUTPUT;
} else if (output < -MAX_OUTPUT) {
    output = -MAX_OUTPUT;
}

其中 MAX_OUTPUT 通常根据电机驱动能力和系统安全裕量设定。

本章从PID控制的基本原理出发,逐步深入其数学模型、软件实现、参数调优方法,并结合平衡车系统的实际应用进行了优化分析。通过本章内容,读者不仅掌握了PID算法的理论基础,也具备了在STM32平台上实现和调优PID控制器的能力,为后续系统集成与调试打下了坚实基础。

5. FreeRTOS实时操作系统多任务调度

在嵌入式系统开发中,随着系统功能的复杂度不断提升,单一任务顺序执行的方式已经难以满足实时性与并发性的双重需求。为了解决这一问题,引入实时操作系统(RTOS)成为主流方案。FreeRTOS 作为一款轻量级、可移植性强、实时性高的开源实时操作系统,广泛应用于基于 STM32 系列微控制器的项目中,尤其适用于多任务调度和资源管理场景。

本章将深入探讨 FreeRTOS 在 STM32 平台上的多任务调度机制,包括任务创建、调度策略、任务间通信与同步机制等内容。通过本章的学习,读者将掌握如何在嵌入式系统中合理使用 FreeRTOS 来实现高效、稳定的多任务控制架构。

5.1 FreeRTOS简介与实时系统优势

5.1.1 FreeRTOS概述

FreeRTOS(Free Real-Time Operating System)是一款轻量级的实时操作系统内核,专为嵌入式设备设计。它由 Richard Barry 于 2002 年开发,具有高度可移植性,支持包括 ARM Cortex-M 系列在内的多种处理器架构。其核心特性如下:

  • 轻量级 :代码量小,适合资源受限的嵌入式平台。
  • 可移植性强 :支持多种编译器和处理器架构。
  • 开源免费 :便于学习、定制和扩展。
  • 实时性高 :提供抢占式和协作式任务调度机制。

FreeRTOS 提供了丰富的系统服务模块,如任务管理、队列、信号量、事件组、定时器等,极大地简化了嵌入式多任务系统的开发。

5.1.2 实时系统的基本特征

实时系统(Real-Time System)是指系统必须在规定的时间内完成任务处理,否则将导致系统功能异常甚至失败。FreeRTOS 作为实时系统的核心,具有以下关键特征:

  • 确定性 :任务调度和中断响应具有确定的最坏执行时间。
  • 抢占式调度 :高优先级任务可以中断低优先级任务的执行。
  • 低延迟 :系统响应外部事件的延迟时间极短。
  • 资源管理 :通过信号量、互斥量等机制实现对共享资源的安全访问。

5.1.3 FreeRTOS在STM32平台上的优势

STM32系列微控制器(如STM32F4、STM32F1、STM32H7等)广泛支持FreeRTOS,得益于其Cortex-M架构的中断管理机制和硬件栈支持,FreeRTOS可以在这些平台上高效运行。其优势包括:

  • 与HAL库无缝集成 :STM32CubeMX工具支持FreeRTOS的快速配置。
  • 任务调度开销小 :上下文切换时间短,适合高频任务调度。
  • 资源占用低 :最小内核仅需6KB Flash和200字节RAM。
  • 调试支持丰富 :支持Tracealyzer等工具进行任务行为可视化分析。

5.2 FreeRTOS任务管理与调度机制

5.2.1 任务的定义与创建

在 FreeRTOS 中,任务是调度的基本单位。每个任务都是一个独立的函数,具有自己的堆栈和优先级。任务的创建使用 xTaskCreate() 函数,其原型如下:

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                        const char * const pcName,
                        configSTACK_DEPTH_TYPE usStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pxCreatedTask );

参数说明:

参数名 说明
pvTaskCode 任务函数指针,即任务的入口函数
pcName 任务名称(用于调试,最大长度由 configMAX_TASK_NAME_LEN 定义)
usStackDepth 任务堆栈大小,单位为字(32位系统中为4字节)
pvParameters 传递给任务函数的参数
uxPriority 任务优先级,数值越大优先级越高
pxCreatedTask 任务句柄指针,可用于后续操作(如任务删除、挂起等)

示例代码:

void vTask1(void *pvParameters)
{
    while(1)
    {
        printf("Task1 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms
    }
}

void vTask2(void *pvParameters)
{
    while(1)
    {
        printf("Task2 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1s
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xTaskCreate(vTask1, "Task1", 128, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 128, NULL, 2, NULL);

    vTaskStartScheduler();

    while(1);
}

逻辑分析:

  • vTask1 vTask2 是两个独立的任务函数,分别以不同的周期打印信息。
  • vTaskDelay() 用于延时,参数使用 pdMS_TO_TICKS() 宏将毫秒转换为系统节拍数。
  • 调用 vTaskStartScheduler() 启动任务调度器,系统进入多任务运行状态。

5.2.2 FreeRTOS任务调度策略

FreeRTOS 支持多种调度策略,主要包括:

  • 抢占式调度(Preemptive Scheduling)
  • 协作式调度(Cooperative Scheduling)
  • 时间片轮转(Time Slicing)
抢占式调度示意图(使用Mermaid流程图)
graph TD
    A[任务A运行] --> B{是否有更高优先级任务就绪?}
    B -- 是 --> C[任务B抢占任务A]
    B -- 否 --> D[任务A继续运行]
    C --> E[任务B运行]
    E --> F{任务B是否阻塞或完成?}
    F -- 是 --> G[重新调度,选择下一个就绪任务]
    F -- 否 --> E
时间片轮转机制说明:

当多个任务具有相同优先级时,FreeRTOS 采用时间片轮转的方式进行调度。每个任务在分配的时间片内运行,时间片结束后切换到下一个同优先级任务。

5.3 任务间通信与同步机制

在多任务系统中,任务之间往往需要共享数据或协调执行顺序。FreeRTOS 提供了多种机制来实现任务间的通信与同步,主要包括:

  • 队列(Queue)
  • 信号量(Semaphore)
  • 事件组(Event Group)
  • 互斥量(Mutex)

5.3.1 队列(Queue)

队列用于在任务与任务、任务与中断之间安全地传递数据。其创建函数为 xQueueCreate()

示例代码:

QueueHandle_t xQueue;

void vSenderTask(void *pvParameters)
{
    int32_t lValueToSend = 0;
    while(1)
    {
        xQueueSend(xQueue, &lValueToSend, portMAX_DELAY);
        lValueToSend++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vReceiverTask(void *pvParameters)
{
    int32_t lReceivedValue;
    while(1)
    {
        if(xQueueReceive(xQueue, &lReceivedValue, portMAX_DELAY))
        {
            printf("Received: %d\n", lReceivedValue);
        }
    }
}

int main(void)
{
    xQueue = xQueueCreate(10, sizeof(int32_t));
    xTaskCreate(vSenderTask, "Sender", 128, NULL, 1, NULL);
    xTaskCreate(vReceiverTask, "Receiver", 128, NULL, 2, NULL);
    vTaskStartScheduler();
    while(1);
}

逻辑分析:

  • xQueue 是一个队列句柄,容量为10,元素大小为 int32_t
  • vSenderTask 每秒发送一个递增的整数到队列中。
  • vReceiverTask 每次从队列接收数据并打印。
  • portMAX_DELAY 表示无限等待,任务会一直阻塞直到接收到数据。

5.3.2 信号量与互斥量

信号量(Semaphore)用于任务同步或资源管理,互斥量(Mutex)用于保护共享资源访问。

创建互斥量示例:

SemaphoreHandle_t xMutex;

xMutex = xSemaphoreCreateMutex();

void vTaskA(void *pvParameters)
{
    while(1)
    {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        // 临界区操作
        xSemaphoreGive(xMutex);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

逻辑分析:

  • xSemaphoreTake() 用于获取互斥量,若已被其他任务占用则阻塞等待。
  • xSemaphoreGive() 用于释放互斥量,其他任务可继续访问资源。
  • 通过互斥量保护临界区,防止资源竞争。

5.4 FreeRTOS在嵌入式系统中的典型应用场景

5.4.1 多传感器数据采集与处理

在传感器网络中,不同传感器任务可分别运行在不同优先级下,通过队列或事件组进行数据传输与处理。例如:

  • 高优先级任务处理陀螺仪数据;
  • 低优先级任务负责温湿度数据采集;
  • 使用队列将数据发送至主控任务进行融合处理。

5.4.2 用户界面与后台控制分离

在平衡车系统中,可以将用户输入(如蓝牙控制)与底层控制(如PID控制)分为两个任务:

  • 一个任务处理蓝牙接收指令;
  • 另一个任务负责PID控制与电机驱动;
  • 使用信号量或队列进行任务间通信。

5.4.3 实时性要求高的控制场景

例如在无人机飞控系统中,飞行姿态解算、电机控制、GPS定位等多个任务并行运行,FreeRTOS 的抢占式调度机制确保了关键任务的实时响应。

5.5 FreeRTOS性能优化与常见问题

5.5.1 内存管理优化

FreeRTOS 提供了多种内存分配方式,包括动态与静态分配。建议在资源受限系统中使用静态分配,避免内存碎片。

配置方法:

  • 设置 configSUPPORT_STATIC_ALLOCATION 为1;
  • 使用 xTaskCreateStatic() 创建任务;
  • 使用 xQueueCreateStatic() 创建队列。

5.5.2 堆栈溢出检测

启用堆栈溢出检测机制( configCHECK_FOR_STACK_OVERFLOW )可以在调试阶段及时发现任务堆栈不足问题。

5.5.3 常见问题与调试方法

问题现象 原因分析 解决方案
任务无法运行 未启动调度器或优先级设置错误 检查 vTaskStartScheduler() 是否调用
队列数据丢失 队列容量不足或读取不及时 增大队列大小或优化读取逻辑
系统卡死或重启 堆栈溢出或死锁 启用堆栈检测、避免资源死锁
任务调度延迟 优先级设置不合理 优化任务优先级与时间片分配

本章通过对 FreeRTOS 在 STM32 平台上的多任务调度机制的深入讲解,帮助开发者理解如何在资源受限的嵌入式系统中实现高效、稳定的任务调度与通信机制。掌握这些内容,将为后续章节中平衡车系统的整体控制架构设计打下坚实基础。

6. 电机驱动原理与控制程序开发

本章深入讲解电机驱动电路的设计原理、H桥驱动机制以及PWM控制策略,并结合STM32平台开发电机控制程序,实现平衡车的运动控制。通过本章的学习,读者将掌握如何在嵌入式系统中实现高效、稳定的电机控制逻辑,并能够基于STM32微控制器开发完整的电机控制程序。

6.1 电机驱动电路设计原理

6.1.1 直流有刷电机的工作原理

直流有刷电机是最常见的电机类型之一,广泛应用于平衡车、机器人等小型移动设备中。其工作原理基于电磁感应,通过电刷和换向器的配合,使得电流在定子线圈中周期性变化,从而产生持续旋转的磁场,带动转子旋转。

电机的转速可通过电压或PWM(脉宽调制)信号控制,而旋转方向则由电流方向决定。

6.1.2 H桥驱动电路的结构与作用

H桥驱动电路是控制直流电机方向和速度的核心电路结构,其名称来源于电路图中四个开关晶体管(通常是MOSFET或双极型晶体管)组成的类似字母“H”的形状。

H桥的基本结构如下:
graph TD
A[上桥臂左 Q1] --> C
B[上桥臂右 Q2] --> C
C[电机] --> D[下桥臂左 Q3]
C --> E[下桥臂右 Q4]
  • Q1 和 Q4 导通:电机正向转动(电流从左到右)
  • Q2 和 Q3 导通:电机反向转动(电流从右到左)
  • Q1 和 Q2 Q3 和 Q4 同时导通:电机短路制动
  • 全部关闭或对角导通 :电机自由滑行

⚠️ 注意:H桥电路设计中必须避免上下桥臂同时导通,否则将导致电源短路。

6.1.3 电机驱动芯片选型与应用

在实际嵌入式系统中,通常使用集成H桥的电机驱动芯片来简化电路设计,例如:

驱动芯片型号 最大电流 特点
L298N 2A 双H桥,适合中等功率应用
TB6612FNG 1.2A 高效率,体积小,适用于小功率设备
DRV8825 2.5A 支持微步控制,适合步进电机

TB6612FNG 为例,其控制逻辑如下:

STBY AIN1 AIN2 PWMA 输出状态
HIGH HIGH LOW HIGH 正转
HIGH LOW HIGH HIGH 反转
HIGH LOW LOW HIGH 制动
HIGH X X LOW 滑行

其中:
- STBY 为使能信号,高电平启用驱动;
- AIN1/AIN2 控制方向;
- PWMA 控制转速(PWM输入)。

6.2 PWM控制策略与实现

6.2.1 PWM信号的基本原理

PWM(Pulse Width Modulation,脉宽调制)是一种通过改变脉冲宽度来控制输出平均电压的技术,广泛应用于电机转速控制、LED亮度调节等场景。

  • 占空比(Duty Cycle) :高电平持续时间与整个周期的比值,决定了输出的平均电压。
  • 频率(Frequency) :PWM信号的周期倒数,需根据电机特性选择合适频率。

例如,10kHz的PWM信号用于控制电机时,可以有效避免电机噪音。

6.2.2 STM32中的PWM生成方法

STM32微控制器通过定时器(如TIM2、TIM3)实现PWM输出。以下是一个基于STM32CubeMX配置并使用HAL库生成PWM信号的示例代码:

// PWM初始化函数
void MX_TIM3_Init(void)
{
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 83;        // 84MHz / (83 + 1) = 1MHz
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 999;          // 1MHz / 1000 = 1kHz
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

逻辑分析:
- Prescaler 设置为83,使定时器时钟频率为1MHz;
- Period 设置为999,即每1000个时钟周期为一个PWM周期,对应频率为1kHz;
- HAL_TIM_PWM_Start 启动PWM通道输出。

// 设置占空比(50%)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);

参数说明:
- __HAL_TIM_SET_COMPARE() 设置比较寄存器值,该值与 Period 的比值即为占空比;
- 此处设置为500,表示50%占空比,即输出平均电压为VCC的一半。

6.3 基于STM32的电机控制程序开发

6.3.1 硬件连接与引脚配置

假设使用 TB6612FNG 驱动芯片,连接方式如下:

STM32引脚 功能 TB6612FNG引脚
PA0 PWMA PWMA
PA1 AIN1 AIN1
PA2 AIN2 AIN2
PA3 STBY STBY

通过配置GPIO引脚模式为输出,并初始化定时器生成PWM信号,即可控制电机。

6.3.2 电机控制函数实现

// 电机控制函数
void Motor_Control(int direction, int speed)
{
    // 使能电机驱动
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);

    if(direction == 1) // 正转
    {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
    }
    else if(direction == -1) // 反转
    {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
    }
    else // 停止
    {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
    }

    // 设置PWM占空比
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed);
}

逻辑分析:
- direction 参数控制方向(1:正转,-1:反转,0:停止);
- speed 参数控制PWM占空比(0~1000);
- HAL_GPIO_WritePin() 控制方向引脚电平;
- __HAL_TIM_SET_COMPARE() 设置PWM输出占空比,实现速度控制。

6.3.3 电机控制程序整合

在主程序中,可以调用上述函数实现电机控制逻辑:

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM3_Init();

    // 启动PWM输出
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

    while (1)
    {
        // 正转,50%速度
        Motor_Control(1, 500);
        HAL_Delay(2000);

        // 停止
        Motor_Control(0, 0);
        HAL_Delay(1000);

        // 反转,70%速度
        Motor_Control(-1, 700);
        HAL_Delay(2000);
    }
}

执行流程说明:
1. 初始化系统时钟、GPIO和定时器;
2. 启动PWM通道;
3. 循环执行以下动作:
- 正转运行2秒;
- 停止1秒;
- 反转运行2秒;
- 循环往复。

6.4 电机控制系统的优化与扩展

6.4.1 多电机控制实现

在平衡车系统中通常使用两个电机进行差速控制。可以通过扩展PWM通道与GPIO引脚,实现双电机控制。例如:

// 控制两个电机
void DualMotor_Control(int left_dir, int left_speed, int right_dir, int right_speed)
{
    Motor_Control(left_dir, left_speed); // 控制左轮
    Motor_Control(right_dir, right_speed); // 控制右轮
}

提示:可以使用另一个定时器(如TIM4)生成另一个PWM信号,控制第二个电机。

6.4.2 基于反馈的闭环控制

为了提高控制精度,可以引入编码器反馈机制,实现闭环控制。例如使用增量式编码器获取电机转速,并结合PID控制器调整PWM输出,实现恒速控制。

闭环控制流程如下:

graph LR
A[目标速度] --> B[PID控制器]
B --> C[PWM输出]
C --> D[电机]
D --> E[编码器]
E --> F[反馈速度]
F --> B

详细闭环控制实现将在 第四章:PID控制算法设计与参数调优 中展开。

6.4.3 驱动电路保护与热管理

在电机驱动过程中,必须考虑以下问题:
- 过流保护 :防止电机堵转导致电流过大;
- 过热保护 :驱动芯片在高温下性能下降,需加散热片或限制连续工作时间;
- 电源滤波 :使用电容对电源进行滤波,减少电机换向引起的电压波动。

小结

本章系统讲解了电机驱动电路的设计原理、H桥结构、PWM控制策略,并基于STM32平台开发了完整的电机控制程序。通过本章内容,读者不仅掌握了如何控制电机的方向与速度,还了解了如何扩展实现双电机控制与闭环反馈机制,为后续平衡车的整体控制打下坚实基础。

7. 蓝牙通信模块与APP远程控制实现

在现代嵌入式系统中,无线通信已成为不可或缺的一环。本章将围绕蓝牙通信模块(如 HC-05、ESP32)展开讲解,重点介绍其在 STM32 平台上的应用方式,并设计开发基于 Android/iOS 手机 APP 的远程控制系统,实现对平衡车状态的实时监控与控制指令的发送。

7.1 蓝牙通信模块基础与选型分析

蓝牙通信模块广泛应用于无线数据传输场景中。在平衡车系统中,常见的蓝牙模块包括:

模块型号 通信协议 最大波特率 特点
HC-05 Bluetooth 2.0 115200 bps 简单易用,适合串口透传
HC-06 Bluetooth 2.0 9600~115200 bps 仅支持从设备模式
ESP32 Bluetooth 4.2 + Wi-Fi 多协议支持 可编程性强,适合复杂系统

选型建议:

  • 对于仅需简单遥控功能的场景,HC-05 是性价比高的选择;
  • 对于需要 Wi-Fi 连接、OTA 升级或更复杂通信逻辑的系统,ESP32 更具优势。

7.2 蓝牙模块与STM32的通信连接

以 HC-05 模块为例,其与 STM32 的连接方式如下:

STM32       HC-05
TX  -------- RXD
RX  -------- TXD
VCC -------- 3.3V
GND -------- GND

注意: HC-05 的 RXD 引脚输入电压为 3.3V 电平,若 STM32 使用 5V 串口输出,需加电平转换电路。

7.2.1 初始化配置代码(基于STM32 HAL库)

以下代码展示了 STM32 使用 HAL_UART_Receive_IT 函数接收蓝牙模块数据的初始化流程:

// UART 初始化
UART_HandleTypeDef huart2;

void MX_USART2_UART_Init(void)
{
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 9600;           // 与蓝牙模块波特率一致
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  HAL_UART_Init(&huart2);
}

// 接收中断处理
uint8_t rx_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart == &huart2) {
      // 处理接收到的数据
      ProcessBluetoothCommand(rx_data);
      HAL_UART_Receive_IT(&huart2, &rx_data, 1); // 继续接收下一个字节
  }
}

参数说明:
- BaudRate :必须与蓝牙模块配置一致,否则通信失败;
- rx_data :每次接收一个字节的数据;
- ProcessBluetoothCommand() :自定义处理函数,用于解析并执行控制指令。

7.3 APP远程控制系统的开发

为了实现远程控制,需开发一个手机 APP。以下是一个基于 Android 平台的蓝牙控制 APP 的基本架构流程图:

graph TD
    A[APP启动] --> B[扫描蓝牙设备]
    B --> C{是否找到目标设备?}
    C -->|是| D[连接设备]
    C -->|否| B
    D --> E[发送控制指令]
    E --> F[接收反馈数据]
    F --> G[状态显示与更新]
    G --> H[用户操作反馈]
    H --> E

7.3.1 指令格式设计示例

为简化通信协议,可定义如下指令格式:

指令类型 数据格式 含义说明
0x01 0x01, speed 设置前进速度
0x02 0x02, angle 设置转向角度
0x03 0x03, data 请求状态反馈(如倾角、速度)

例如,发送 0x01 0x32 表示设置速度为 50(0x32 = 50)。

7.3.2 APP与STM32的交互流程

  1. APP通过蓝牙连接到STM32;
  2. 用户点击“前进”按钮,APP发送 0x01 0x32
  3. STM32解析数据并更新PID控制参数;
  4. STM32周期性采集姿态数据并通过蓝牙回传;
  5. APP接收数据后更新UI,显示当前状态。

7.4 蓝牙通信中的常见问题与优化

7.4.1 数据丢包与重传机制

蓝牙通信中可能出现数据丢包,特别是在电机驱动电磁干扰较强时。可通过以下方式优化:

  • 增加数据校验(如CRC);
  • 设计应答机制,未收到ACK则重发;
  • 提高串口波特率至115200以提升吞吐量。

7.4.2 通信协议优化建议

  • 使用结构化数据帧,如:
    c typedef struct { uint8_t header; // 帧头 0xA5 uint8_t cmd; // 指令 uint8_t data_len; // 数据长度 uint8_t data[10]; // 数据 uint16_t crc; // 校验码 } BluetoothPacket;
  • 增加帧同步机制,防止因丢包导致解析错误。

下一章节将探讨如何将蓝牙控制与FreeRTOS多任务调度结合,实现更加稳定高效的远程控制系统。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本资料深入讲解基于STM32微控制器的平衡车开发技术,涵盖传感器数据采集、姿态融合、PID控制算法、电机驱动、蓝牙通信等核心内容。通过开源固件项目,学习者可掌握使用DMP数字运动处理器提升系统性能、FreeRTOS多任务调度、Cortex-M内核开发、电源管理及故障检测等关键技术。适用于嵌入式开发者与电子爱好者,帮助其构建稳定高效的自平衡系统。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐