
做了大半年的嵌入式小项目陆陆续续也折腾过不少板子最让我觉得“过瘾”的还是这台基于STM32与MPU6050的手势控制小车。它没有用遥控器也没有接蓝牙/WiFi完全靠手部的姿态变化来控制小车的前进、后退、左转、右转。简单说你左手一翻车就走手掌一抬车就停。整个过程涉及传感器原始数据读取、姿态解算、PWM电机调速、硬件接线几乎把嵌入式开发的几个核心环节全串起来了。今天我把整个设计思路、硬件选型、代码实现和调试踩坑过程完整梳理一遍给想动手做智能小车或者入门IMU姿态解算的朋友一个可直接参考的方案。这个项目适合谁如果你会一点C语言和Keil用过STM32标准库或者HAL库那跟着做难度不大如果你完全没接触过MPU6050也没关系我会把寄存器配置和数据融合原理用比较直白的方式讲清楚。整个项目成本也不高一块STM32F103C8T6最小系统板、一个MPU6050模块、一个电机驱动模块和两个TT电机就够了。做出来的东西既是毕业设计的好素材也能当桌面玩具还能作为后续扩展循迹、避障、无线控制等功能的基础底盘。1. 项目背景与整体方案设计1.1 手势控制小车的核心需求动手之前先得把需求想明白。手势控制小车听起来很酷但落到工程上其实就是三个问题怎么感知人的手势动作怎么把动作翻译成控制指令怎么让小车按照指令稳定跑起来这三个问题分别对应传感器、算法和执行机构。感知端我用MPU6050它内置三轴加速度计和三轴陀螺仪能实时输出物体的加速度和角速度。算法端在STM32上做姿态解算从原始数据里提取出俯仰角Pitch和横滚角Roll。执行端则通过PWM控制两个直流减速电机用差速转向实现前进后退和左右转弯。我做过用Arduino加蓝牙模块的遥控小车也试过OpenMV视觉识别最后还是觉得手势控制这个玩法最“优雅”。它没有多余的操作设备手本身就是遥控器挥动和倾斜的直觉感特别强。而且MPU6050模块很便宜几块钱到十几块钱就能买到精度对“手势级别”的控制完全够用。1.2 主控选型为什么是STM32而不是Arduino很多新手会问手势控制小车用Arduino不是更简单吗确实Arduino有现成的库I2C读MPU6050几行代码就搞定。但Arduino的CPU主频低内存小一旦你后续想加OLED显示、超声波避障、PID调速、无线模块资源很快就会吃紧。STM32F103C8T6这颗芯片虽然“老”但72MHz主频、64KB Flash、20KB SRAM做这个小车绰绰有余。它的定时器资源丰富可以同时输出多路PWMI2C、USART、SPI接口齐全扩展性非常好。更关键的是STM32是绝大多数电子、自动化、计算机专业学生必碰的平台拿它来做项目积累的经验可以直接迁移到以后的工作项目里。我手头这块“蓝丸”最小系统板价格也只有十块钱上下引脚全部引出配ST-Link下载器就能调试。Keil里工程配置也很成熟标准库和HAL库都有大量示例代码踩坑成本低。对比下来STM32是平衡学习成本和扩展能力的最佳选择。1.3 传感器选型MPU6050的硬实力MPU6050是InvenSense出品的六轴运动传感器内部集成了一个三轴MEMS加速度计和一个三轴MEMS陀螺仪通过I2C接口与主控通信默认设备地址是0x68。它还有个很实用的特性——内置DMPDigital Motion Processor可以直接在传感器内部完成姿态解算输出四元数减轻主控CPU的负担。我选它而不是MPU9250主要是够用且便宜。MPU9250额外带了三轴磁力计能输出航向角但对两轮差速小车来说我们关注的Pitch和Roll完全不需要磁力计参与多一个传感器反而要处理磁校准和干扰问题没必要自找麻烦。MPU6050模块一般还自带了稳压IC和I2C电平转换电路供电3.3V或5V都行对新手尤其友好。这里要提醒一句市面上MPU6050模块质量参差不齐有的偏置很大静止时读出来角度漂移严重。我踩过坑所以在后面专门写一节怎么校准和滤波。1.4 电机驱动方案与整体系统框架小车要动起来单纯靠STM32的GPIO输出是不够的直流电机的电流远超过IO口的驱动能力。我这里用的是经典的TB6612FNG驱动模块它和L298N相比体积更小、压降更低发热也小很多。L298N虽然便宜但两颗PNP/NPN三极管压降比较大供电电压稍微低一点就不够给电机喂饱电所以后来我一直习惯用TB6612。整体系统框架是这样的STM32通过I2C从MPU6050读取六轴原始数据经过互补滤波算出Pitch和Roll角把角度映射成小车的运动控制量再通过STM32的定时器输出两路PWM信号给TB6612TB6612根据PWM占空比和方向引脚电平驱动左右两个电机。整个系统由两节18650锂电池或一块7.4V电池包供电经过降压模块给STM32提供5V或3.3V。我给这个系统画过一个很简单的信号流手势 → MPU6050 → I2C → STM32 → PWM → TB6612 → 电机 → 车轮。这一条链路看似简单但每一个环节都有参数需要认真配置下面我会逐一展开。2. 硬件连接与关键电路要点2.1 完整材料清单我列一下自用的一套物料仅供参考模块型号/规格数量备注主控板STM32F103C8T6最小系统板1蓝丸板姿态传感器MPU6050模块1GY-521常见电机驱动TB6612FNG模块1也可用L298N代替直流减速电机N20或TT马达2配橡胶轮、编码器可选电池7.4V 18650两串1组或3S航模电池降压模块AMS1117-5.0 / MP15841给STM32供电面包板/洞洞板任意1方便搭电路杜邦线母对母、公对母若干多备点下载器ST-Link V21烧录调试如果你用的是L298N电源输入可以宽一些但要注意它的逻辑电平可能是5V和STM32的3.3V GPIO不匹配时需要用逻辑电平转换或直接选3.3V逻辑兼容的驱动模块。TB6612的逻辑电压可以直接接3.3V省事很多。2.2 电源设计电机和逻辑必须分开处理这个项目里最大的坑不是代码而是供电。电机启动瞬间电流可以达到几百毫安甚至一安培以上如果和STM32共用同一个电源且没有隔离电机一转主控板的电压就会被拉低轻则传感器数据跳变重则STM32直接复位。我用7.4V电池包先接TB6612的VM引脚再在电源输出端并联一个大电容470μF以上降低瞬态压降。STM32的供电我单独从电池经过MP1584降压模块取5V再接AMS1117-3.3给最小系统板的3.3V引脚有些蓝丸板上有AMS1117-3.3可以直接从5V输入。总之原则是电机电源要粗、要稳逻辑电源要干净两者共地GND连一起即可。共地不能少否则逻辑信号没有参考点电机会不受控。2.3 STM32与MPU6050的I2C连接MPU6050模块的SCL和SDA引脚接到STM32的I2C引脚。我常用软件模拟I2C因为引脚分配更灵活。这里我选PB6做SCL、PB7做SDA这刚好是STM32F103的I2C1引脚既能用硬件I2C也能软件模拟。模块的VCC接3.3VGND接GND。MPU6050模块的AD0引脚决定I2C地址接GND时地址是0x68接VCC时是0x69我默认接GND。注意一点MPU6050模块上一般已经自带上拉电阻到3.3V所以不需要额外外接上拉。如果你用的是别的传感器没有上拉电阻I2C通信会不稳定需要自己在SCL和SDA上各接一个4.7kΩ电阻到VCC。2.4 TB6612电机驱动接线TB6612FNG模块引脚不算多主要是VM电机电源、VCC逻辑电源、GND、PWMA/PWMB、AIN1/AIN2、BIN1/BIN2、AO1/AO2、BO1/BO2。我接了一个定时器的两个通道作为PWM输出比如TIM2的CH1和CH2分别对应PA0和PA1接到PWMA和PWMB。AIN1、AIN2、BIN1、BIN2用四个普通GPIO控制电机方向和刹车状态。左右两个电机分别接AO1/AO2和BO1/BO2。这里要特别留意电机线序因为两个电机对面安装旋转方向的定义不同。我调试时一开始把左右电机并联接反结果“前进”指令变成了原地转圈。后来我是用一个简单的MODE_GESTURE测试先只给右轮PWM确认正转方向再去接左轮保持一致。2.5 传感器安装方向与手势映射的关系MPU6050的安装方向直接决定姿态角正负号和前后左右手势的映射关系。我把模块平放在手背或者一块小“遥控板”上X轴朝向车头方向Y轴朝向左边Z轴垂直向上。这样当手向前俯视Pitch变小时传感器读到的角速度在X轴方向有分量当手向左倾Roll变小时Y轴有分量。代码里可以直接根据角度正负来映射动作少踩不少符号颠倒的坑。如果不小心装反了问题也不大只需要在解算时把对应轴的加速度和角速度值取反就行。但我建议先定好安装方向再做代码不要代码跑起来之后再去测方向非常容易混乱。3. 姿态解算与手势识别算法3.1 加速度计与陀螺仪到底在测什么先别急着写代码理解数据最重要。MPU6050输出的原始数据是16位有符号整数范围取决于你配置的量程。加速度计可以配置为±2g、±4g、±8g、±16g陀螺仪可以配置为±250、±500、±1000、±2000°/s。默认情况下加速度计量程常设为±2g对应灵敏度16384 LSB/g陀螺仪量程设为±250°/s对应灵敏度131 LSB/(°/s)。静止时加速度计测到的是重力加速度在三个轴上的分量。比如模块水平放置Z轴读数为1g左右约16384X轴和Y轴接近0。通过三角关系可以求出当前倾角acc_pitch atan2(acc_y, acc_z) * 180 / PIacc_roll atan2(-acc_x, acc_z) * 180 / PI。这个角度长期稳定但动态响应慢而且有振动时噪声很大。陀螺仪测的是绕各轴的角速度也就是转动快慢。对时间积分可以得到角度增量响应非常快但会随着时间累积漂移。单独用陀螺仪角度会慢慢飘走单独用加速度计会抖得没法看。所以必须做数据融合。3.2 互补滤波与卡尔曼滤波怎么选姿态解算最常用的方法是互补滤波和卡尔曼滤波。卡尔曼滤波基于状态空间模型能动态融合加速度计和陀螺仪效果细腻但矩阵运算代码量大对初学者不友好。互补滤波则利用两者频率特性互为补充陀螺仪的高频响应好加速度计的低频响应好用一个权重系数把两个角度“混”在一起。互补滤波公式非常简单angle alpha * (angle gyro * dt) (1 - alpha) * acc_angle其中alpha通常在0.9到0.98之间dt是采样间隔。alpha越大越信任陀螺仪积分结果整体角度越平滑但跟踪越慢alpha越小越信任加速度计角度跟踪越快但有振动时会抖动。我给小车项目用的是alpha0.98实测效果不错。如果你嫌公式麻烦也可以直接调用MPU6050内置DMP。DMP可以输出稳定的四元数再转成欧拉角主控代码量少很多。但DMP需要烧录官方固件到传感器内部的RAM里调试起来稍微麻烦一些。我的建议是想彻底理解姿态解算就自己写互补滤波想快速出效果可以直接上DMP。下面我会把互补滤波的代码完整列出来。3.3 手势识别逻辑与动作状态机姿态角算出来之后手势识别就变成了“阈值判断”的问题。我定义了五个动作状态静止/停止Pitch在-15°到15°之间Roll在-15°到15°之间前进Pitch 30°后退Pitch -30°左转Roll -30°右转Roll 30°这个阈值不是拍脑袋定的。如果阈值太小比如10°日常手抖就会误触发如果阈值太大比如60°手要转得很夸张体验不好。我在实际测试中用手掌自然平放时的角度大约在0°附近上下浮动5°以内所以初始阈值设在30°比较合适。为了避免动作在阈值边界来回抖动我加了迟滞逻辑从静止切换到前进需要Pitch超过30°但从前进切回静止需要Pitch回到15°以下。这就像空调的温度回差防止系统频繁切换。此外我还加了一个“持续触发”时间窗手势角度必须连续超过阈值100ms以上才被认为是有效动作这样能过滤掉短促的抖动和误触碰。实现上用一个计时变量在定时器中断里累加判断超时后再改变状态。3.4 手势平滑与防抖处理防抖处理有两层。第一层是输入滤波我除了互补滤波还会对读取的加速度原始数据做简单的滑动平均滤波取最近4次的数据平均。这样可以在硬件噪声和电机振动同时存在时稳住读数。第二层是输出滤波也就是让小车速度不要突然跳变。同类手势控制问题里最影响手感的就是当Pitch从45°一下变成15°时PWM直接从100%掉到0%车会产生明显的耸动感。我加了一个输出斜坡限幅器每次控制周期PWM变化量最大不超过10%让加减速柔和很多。我用一个简单的状态机来管理手势状态代码里的核心流程是读取MPU6050原始数据将原始数据换算成物理单位互补滤波计算Pitch/Roll套用阈值和迟滞逻辑识别当前动作将动作映射成左右电机PWM占空比和目标方向限幅、输出PWM这个流程在主循环里每10ms跑一次之后就是稳定的姿态控制。4. STM32软件架构与关键代码解析4.1 软件总体框架STM32端的软件结构不复杂我分成底层驱动、算法层和应用层三层。底层驱动包括I2C读写、MPU6050寄存器读写、PWM初始化、GPIO控制算法层包括互补滤波、滑动平均滤波、手势识别状态机应用层是把手势状态映射成电机速度和方向。我用的是STM32标准库在Keil5里建工程。如果用HAL库逻辑也一样把寄存器操作换成HAL函数就行。工程结构我习惯分成几个文件main.c主循环调用各模块mpu6050.c/.hMPU6050初始化和数据读取filter.c/.h互补滤波、滑动平均gesture.c/.h手势识别状态机motor.c/.h电机PWM和方向控制uart_debug.c/.h串口打印调试用4.2 I2C读取MPU6050原始数据的实现方式硬件I2C在STM32F103上有一些坑比如事件标志位要处理得非常仔细否则容易卡死在查询状态。所以我更推荐新手用软件模拟I2C代码完全可控不容易出玄学问题。一个简单的I2C读函数如下#define MPU6050_ADDR 0xD0 // 0x68左移一位 uint8_t MPU6050_ReadByte(uint8_t reg) { uint8_t data 0; I2C_Start(); I2C_SendByte(MPU6050_ADDR | 0); // 写地址 I2C_SendByte(reg); // 寄存器地址 I2C_Start(); I2C_SendByte(MPU6050_ADDR | 1); // 读地址 data I2C_RecvByte(); I2C_Stop(); return data; } int MPU6050_ReadRaw(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz) { uint8_t buf[14]; MPU6050_ReadBytes(MPU6050_ACCEL_XOUT_H, buf, 14); *ax (buf[0] 8) | buf[1]; *ay (buf[2] 8) | buf[3]; *az (buf[4] 8) | buf[5]; *gx (buf[6] 8) | buf[7]; *gy (buf[8] 8) | buf[9]; *gz (buf[10] 8) | buf[11]; return 0; }初始化时我会先复位传感器向寄存器0x6B写0x00唤醒MPU6050。然后配置电源管理、采样率、量程MPU6050_WriteByte(MPU6050_PWR_MGMT_1, 0x00); // 退出睡眠模式 MPU6050_WriteByte(MPU6050_SMPLRT_DIV, 0x07); // 采样率 1kHz / (1 7) ≈ 125Hz MPU6050_WriteByte(MPU6050_CONFIG, 0x06); // 数字低通滤波截止频率5Hz左右 MPU6050_WriteByte(MPU6050_GYRO_CONFIG, 0x00); // 量程±250°/s MPU6050_WriteByte(MPU6050_ACCEL_CONFIG, 0x00); // 量程±2g注意CONFIG寄存器的DLPF数字低通滤波器配置设置成0x06对应带宽约5Hz能滤掉不少高频噪声。我当时调试时发现数据还是有点抖仔细一查是电机产生的六轴干扰后来加了滑动平均和互补滤波才满意。4.3 互补滤波的完整实现互补滤波代码很简短但参数调整是关键。下面是我项目里的核心代码#define ALPHA 0.98f #define DT 0.01f // 每10ms执行一次 float pitch 0, roll 0; void ComplementaryFilter(int16_t ax, int16_t ay, int16_t az, int16_t gx, int16_t gy, int16_t gz) { float acc_pitch, acc_roll; float gyro_pitch_rate, gyro_roll_rate; // 把加速度原始值转成单位g float fax ax / 16384.0f; float fay ay / 16384.0f; float faz az / 16384.0f; // 加速度计估计角度 acc_pitch atan2f(fay, faz) * 57.29578f; acc_roll atan2f(-fax, faz) * 57.29578f; // 陀螺仪角速度转成°/s再按轴投影 gyro_pitch_rate gz / 131.0f; // 根据安装方向绕Z轴角速度影响Pitch gyro_roll_rate gx / 131.0f; // 互补滤波 pitch ALPHA * (pitch gyro_pitch_rate * DT) (1 - ALPHA) * acc_pitch; roll ALPHA * (roll gyro_roll_rate * DT) (1 - ALPHA) * acc_roll; }这里有个细节要注意陀螺仪角速度的投影方向和加速度计计算角度使用的坐标轴必须保持一致否则融合出来的角度会完全不对。我刚开始把gx和gz写反了出来的角度一直“发疯”。所以建议你拿到模块后先看数据手册或者做个简单实验绕某个轴转动时观察哪个轴的陀螺仪读数变化再写进代码里。如果你想偷懒不写公式DMP方式也不难网上有很多库可以直接用。但我个人的建议是这个项目总共就两个自由度角度需要算自己写一遍互补滤波反而能加深理解以后做四轴、平衡车都会用到。4.4 手势识别与状态机手势识别我用了一个state变量在手势阈值判断的基础上加上了时间窗。这里以判断前进为例typedef enum { STATE_STOP, STATE_FORWARD, STATE_BACKWARD, STATE_TURN_LEFT, STATE_TURN_RIGHT } GestureState; GestureState CurrentState STATE_STOP; GestureState Gesture_Detect(float pitch, float roll) { static uint16_t forward_cnt 0; // 先做迟滞判断 float threshold_enter_forward 30.0f; float threshold_exit_forward 15.0f; if (CurrentState STATE_FORWARD) { if (pitch threshold_exit_forward) forward_cnt 0; return STATE_FORWARD; } if (pitch threshold_enter_forward) { if (forward_cnt 10) forward_cnt; // 10ms一次持续100ms else { forward_cnt 10; return STATE_FORWARD; } } else { forward_cnt 0; } // 其他动作类似 return STATE_STOP; }同样的方法可以套到后退、左转、右转上。在动作切换时比如从前进切到右转状态机会先回态到STOP再进入右转判定。这样做是为了避免两个相邻动作互相覆盖。4.5 电机PWM控制的实现电机方向由AIN1、AIN2控制正转、反转、刹车都能做。PWM占空比则决定转速。我用的定时器是TIM2PA0、PA1输出PWM频率设为10kHz。10kHz对直流电机的效果很好高频PWM会减少电机啸叫。void Motor_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 时钟使能、GPIO配置为复用推挽输出 // 省略具体寄存器配置 TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 72MHz/72 1MHz TIM_TimeBaseStructure.TIM_Period 100 - 1; // 10kHz TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // CH1/CH2 PWM模式1 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC2Init(TIM2, TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); } void Motor_SetSpeed(uint8_t left_pwm, uint8_t left_dir, uint8_t right_pwm, uint8_t right_dir) { // left_dir: 0正转, 1反转 // 设置AIN1/AIN2和BIN1/BIN2 // 设置CCR1/CCR2 }在应用层我根据手势状态来设置左右轮目标速度。直行时两轮速度相同转弯时一轮全速、一轮低速或者反转实现原地旋转。为了让转弯不至于太猛我给转向时内侧轮保留了20%速度而不是直接刹停这样转弯半径小且稳定。5. 调试过程与常见问题实录5.1 串口打印姿态角先“看”再“动”拿到板子的第一步不是直接装小车而是先把MPU6050数据通过串口打印到电脑上。我用USART1波特率115200把Pitch、Roll、以及识别出的手势状态实时打印出来。观察静止时角度是否稳定在0°附近然后手动倾斜传感器看角度是否跟着变化、方向是否正确。这一步能过滤掉70%的接线和代码问题。我印象很深的一件事是有一次打印数据发现角度一直朝一个方向漂移怎么调滤波参数都没用。最后用逻辑分析仪看I2C波形才发现是SCL信号受电机供电回路干扰产生毛刺导致读取到的陀螺仪数据偶尔跳变。后来我把传感器用的3.3V从主控板独立引出并加了一个0.1μF去耦电容问题才彻底消失。5.2 硬件调试的排查顺序如果小车完全没反应我一般按这个顺序排查用万用表量电源确认STM32、MPU6050、TB6612都有电看串口输出确认MPU6050数据是否正常读取手动给PWM固定值先让小车直行排除电机和驱动问题再看手势识别是否被正确触发最后联合测试整体闭环不要把“代码、接线、电源”混在一起调会很崩溃。我写过一个小函数上电后不运行手势逻辑直接固定输出两轮50%占空比各1秒依次测试正转和反转确认电机驱动没问题以后再挂手势逻辑。5.3 常见问题速查表问题现象可能原因排查方法MPU6050读取失败串口全是0或FFI2C接线错误、地址不对、模块供电异常检查SCL/SDA确认AD0接GND地址0x68角度稳定但PWM不动手势阈值未触发、GPIO配置错误串口打印Pitch/Roll和当前状态小车前进变成原地转圈电机线序接反单独测试左右轮正转方向电机转一下停一下电源压降导致主控复位检查电池电压、加电容逻辑电源和电机电源分开角度向一个方向漂移陀螺仪零点偏置静止采样校准减去零偏数据抖动剧烈DLPF配置不当或震动干扰增大滤波系数加滑动平均优化传感器供电5.4 避坑经验MPU6050零点校准MPU6050出厂有零偏不同模块差异还挺大。我在初始化的时候在传感器静置状态下采集100次陀螺仪数据求出平均值把它作为零偏之后每次读取原始值都减去这个零偏。别小看这一步做过校准后角度漂移明显改善手势控制灵敏度也会提高很多。校准代码很简单但要在上电后等1~2秒再开始采集避免开机瞬间电压不稳影响数据int16_t gyro_offset[3] {0,0,0}; void MPU6050_Calibrate(void) { int32_t sum[3] {0,0,0}; int16_t gx, gy, gz; for(int i 0; i 100; i) { MPU6050_ReadRaw(0,0,0, gx, gy, gz); sum[0] gx; sum[1] gy; sum[2] gz; delay_ms(10); } gyro_offset[0] (int16_t)(sum[0] / 100); gyro_offset[1] (int16_t)(sum[1] / 100); gyro_offset[2] (int16_t)(sum[2] / 100); }读取后在做互补滤波前用gx - gyro_offset[0]这样的方式清零零偏。5.5 关于DMP和自写的取舍如果你用了DMP姿态角的输出会更平滑但遇到传感器安装方向不对时要通过旋转四元数来调整。在我看来DMP适合不需要修改算法的快速项目而自写互补滤波适合学习和调试。这个手势小车项目我最终选择了自写互补滤波因为控制逻辑只有两个角度代码简单而且阈值和滤波系数都可以实时调整可控性更强。如果你非要用DMP可以去搜一下“MPU6050 DMP库移植”主要工作是把官方运动驱动库加入到工程里调用dmpReadFifo期望数据。但要注意DMP输出的是四元数还需要转换成欧拉角代码量并不少。6. 调试工具与扩展方向6.1 串口监视与数据可视化调试姿态角时我推荐用匿名上位机或者VOFA它们可以实时绘制Pitch/Roll曲线比看串口数字直观得多。你只需要把格式化好的数据帧通过串口发送出去上位机就能画出波形。比如发送“Pitch:%.1f Roll:%.1f\n\r”虽然简单但看数字跳动不如看曲线舒服。用上位机可以很清楚地看到滤波效果和阈值边界。还有一个很实用的技巧在串口调试时把手势状态和PWM占空比一起打印出来除了判断识别对不对还能看输出是否平滑。如果PWM在很短时间里变化剧烈就要考虑改斜坡限幅参数。6.2 功能扩展无线控制与自动控制结合做完手势控制的基本功能后这个底盘就是一个很好的平台可以继续扩展。比如在MPU6050数据融合基础上加入HC-05蓝牙模块就可以在手机APP和手势控制之间切换控制方式加入超声波模块可以在手势控制的同时做避障甚至后续接一个ESP8266实现手机WiFi网页控制。我比较推荐加的是测速反馈。如果你买的电机带霍尔编码器可以通过STM32的定时器编码器模式读回实际轮速再做闭环PID调速。手势控制加上PID后小车的速度稳定性会提升一个档次特别是地面摩擦不均匀时两轮差速误差会明显减小。这个扩展虽然要写一点PID代码但逻辑不算难很多PID例程都可以直接套。6.3 把实时性做得更好的建议手势控制的实时性主要取决于三个参数传感器采样率、姿态解算频率、控制频率。我当前是10ms跑一次主循环MPU6050采样率为125Hz左右控制频率100Hz。如果感觉响应偏慢可以把主循环缩短到5ms同时把互补滤波的dt同步改成0.005。但要注意主循环里不要做太重的浮点运算STM32F103是Cortex-M3内核没有硬件浮点单元大量atan2f会占用不少CPU时间。我实测主循环占用大概40%左右还能接受。如果想让传感器数据更稳定可以适当调高MPU6050采样率到500Hz然后在软件里做队列平均。不过对于手势控制这种慢动作场景125Hz完全够用没有必要盲目拉高。6.4 整个项目的经验沉淀做这个项目的最大收获不是最终小车能跑多顺而是把“从传感器数据到执行机构动作”的完整链路亲手打通了一遍。你会在过程中遇到无数个问题I2C时序不对、角度符号反了、电机电源把传感器干扰了、阈值怎么调都不舒服……每一个问题都会逼着你去看数据手册、查信号、打日志。这个过程很磨人但非常有价值。如果你也打算做一个类似的手势控制小车我以一个做了不少项目的人的身份给你一个忠告先分模块调试每个模块都验证通过后再组合。传感器通了接电机电机通了再接手势识别最后才是整车联调。一次只改一个变量出了问题也容易定位。我做这个项目时踩过的最大一个坑就是MPU6050的安装朝向和陀螺仪投影关系搞反了。姿态解算看起来是一堆公式实际上方向符号才是最容易出错的地方。后来的经验是拿到模块先做一个“绕轴实验”把模块绕X轴转动记录哪个轴的加速度和角速度变化真正理解传感器坐标系之后再去写融合代码能少走很多弯路。这台小车目前还在我桌上放着偶尔拿手机当“遥控器”比划两下还是很好玩。接下来我打算把MPU6050换成一颗带磁力计的九轴传感器把航向角也解出来再配合路径规划让小车能按手势设定的路线巡航。如果你也有类似的想法或者在做这个项目的过程中遇到了问题欢迎多交流很多细节只有动手做过了才知道里面有多少门道。