简介:本资源是一套完整的STM32平衡小车软硬件开发套件,面向嵌入式初学者、电子设计竞赛学生及智能车项目开发者,解决自平衡控制原理理解难、软硬件协同调试复杂、蓝牙遥控集成门槛高等实际问题。压缩包共351个文件,含75个C语言源码(.c)、71个头文件(.h)、37个编译中间文件(.o/.d)及10个Altium Designer原理图(.SchDoc)与2个PCB工程(.PcbDoc),覆盖主控、AHRS姿态解算、电机驱动、蓝牙通信等核心模块;另有安卓端APK安装包及Java源码,支持实时遥控与参数调节。资源大小22.89MB,结构清晰,含BOM清单、3D电路板图、实物照片及多版本Keil工程(uvproj/uvopt),便于分模块学习与移植验证。目前已有339人下载学习,是少有的集原理图、PCB、双MCU分工源码(F103RCT6运动控制+C8T6姿态解算)、安卓端闭环遥控于一体的高完整性平衡车实践资料。 手里这套资料我整理了很久,从硬件到软件再到手机端全都有。很多人做平衡车项目,卡在某一环就放弃了——画板子不会、PID调不出来、蓝牙连不上、APK不知道怎么打包。这篇文章我就把自己做STM32平衡小车的完整过程拆开讲一遍,原理图怎么画、PCB怎么布局、源码怎么写、安卓端怎么搞,每个环节的关键点都给你挑明。
先说清楚这套东西能干什么:基于STM32F103C8T6主控,加上MPU6050姿态传感器、TB6612电机驱动、HC-05蓝牙模块,配合自己用AD09画的原理图和PCB,实现了两轮自平衡、蓝牙遥控、手机APP实时操控这三个核心功能。适合正在学STM32的嵌入式初学者、准备做毕设或者电赛项目的学生,以及想从零搭建一个小型机器人平台的动手党。硬件资料、固件源码、安卓APK和源码都是齐的,拿到手就能照着做。
1. 项目整体设计与三端架构
1.1 先想清楚要做什么:功能需求拆解
动工之前,我习惯先把需求一条条列出来,这样后面每一块电路、每一段代码都有明确的目标,不会做到一半跑偏。这套平衡小车的核心功能其实可以拆成三个层面:自平衡是底层,一定要稳,车能自己站在原地不倒;蓝牙遥控是交互层,手机发指令,小车响应前、后、左、右;姿态反馈是数据层,实时把倾角、速度这些状态传回手机显示。
自平衡这块,核心在于姿态传感器和电机驱动的配合。姿态传感器用MPU6050,片上自带陀螺仪和加速度计,通过I2C接口把数据给STM32,STM32解算出当前车体的倾角,再用PID算法算出电机应该往哪个方向转、转多快。这个过程在数学上不复杂,但工程上牵扯到滤波、量化误差、响应延时,做起来有不少细节。
蓝牙遥控这块,我选的是HC-05模块,工作在经典蓝牙2.0协议下,通过UART串口和STM32通信。手机端用Android原生代码写了一个简单的遥控界面,界面上有前后左右方向键和一个速度滑块。按下方向键,手机通过蓝牙把指令帧发给HC-05,HC-05串口转给STM32,STM32解析指令帧后调整目标速度或者转向偏置。这套链路延时很低,实测下来基本感觉不到卡顿。
再把扩展需求也列一下:板子用Type-C供电、带电源指示灯、传感器和主控做成可插拔模块、留出串口调试接口,方便以后自己做调参和加功能。这些需求不一定全部实现,但都要在设计里预留好位置。
1.2 硬件端的方案选型:为什么是AD09而不是其他EDA
原理图和PCB设计这块,我选的是Altium Designer 09。虽然现在很多人用AD20、AD21甚至嘉立创EDA,但AD09这个版本我自己用了很久,界面清爽,库文件查找方便,生成的PCB文件兼容性好,网上参考资源也多,用来做这种中小型双层板绰绰有余。如果你用的是新版AD,工程文件也能直接打开,只是注意一下版本兼容提示。
硬件框架上,主控选了STM32F103C8T6,这是一颗Cortex-M3内核的芯片,72MHz主频,64KB Flash,20KB SRAM,对于控制两路电机、读取姿态传感器、解析串口指令这些任务来说性能完全够用。选它的另一个原因是引脚多、封装小,LQFP48封装手动焊接难度不大,加上网上资料铺天盖地,遇到问题几乎都能搜到答案。
传感器用MPU6050,六轴惯性测量单元,内置三轴陀螺仪和三轴加速度计。跟单独买陀螺仪加加速度计方案比,MPU6050的DMP(数字运动处理器)可以直接输出四元数,省去自己写姿态解算算法的麻烦。电机驱动用TB6612FNG,这是东芝的MOSFET驱动芯片,单路最大持续驱动电流1.2A,峰值3.2A,带低功耗待机模式,逻辑电压3.3V到5V都能兼容,直接和STM32的GPIO电平对接,不用额外做电平转换。
1.3 固件端与安卓端:软件框架的整体规划
固件端编程我用标准外设库(Standard Peripheral Library)写,没用HAL库。原因很直接:平衡车这种实时性要求比较高的应用,寄存器级的外设配置能让你把每个时钟周期都掌控在手里,中断响应时间可预测。当然HAL库也能做,但初学阶段我不太推荐,因为封装层数太多,出了问题不好定位。
安卓端用Android Studio开发,语言是Java。蓝牙这块用经典蓝牙的socket通信,不是BLE低功耗蓝牙——HC-05不支持BLE,两者协议栈不一样,这个要特别注意。APP结构上分三层:蓝牙管理模块负责扫描、配对、连接和数据收发;控制界面模块负责UI交互、按钮事件和滑块变化;协议解析模块负责组装和解析指令帧。
三端联调的时候,我的经验是先本地后远程:先把PID和小车调稳,再把蓝牙模块接上调遥控,最后再处理手机APP的指令格式和延时问题。如果一开始就三端全接上,出了问题都不知道该查哪一层。
2. 硬件电路设计:AD09原理图与PCB实战
2.1 原理图核心模块设计:五块电路一次理清
原理图我分成五个模块来画:电源管理、主控最小系统、姿态传感器接口、电机驱动、蓝牙串口接口。每个模块单独画框图,最后通过电源网络和地网络连在一起。
电源管理是首先需要关注的部分。整个系统电压有多个等级:电机驱动VM引脚要6V(两节18650锂电池串联),MCU和蓝牙模块要5V(TB6612的VCC),MPU6050和逻辑部分要3.3V(AMS1117稳压输出)。我的做法是两节18650锂电池串联输出7.4V,经过一个二极管防反接,再分两路:一路直接给TB6612的VM供电,另一路经过AMS1117-5.0稳压到5V,再经过AMS1117-3.3稳压到3.3V。注意AM1117系列压差比较大,输入7.4V输出3.3V没问题,如果输入只有5V想输出3.3V,压差1.7V也勉强够,但负载大了发热明显,设计时尽量预留余量。
主控最小系统这部分,STM32F103C8T6需要三个基本电路:8MHz外部晶振 + 两个20pF负载电容;BOOT0和BOOT1引脚通过10K下拉电阻接地,正常从Flash启动;NRST复位引脚接一个100nF电容到地。另外每个电源引脚旁边放一个100nF去耦电容,这个不能省。布局上我习惯把晶振放在芯片旁边,走线尽量短,避免引入高频噪声。
姿态传感器接口是I2C总线,SDA和SCL上各接一个4.7K上拉电阻到3.3V。这个上拉电阻很有讲究——阻值太大I2C通信速率上不去,阻值太小功耗增大且信号边沿变陡容易产生振铃。4.7K是MPU6050数据手册推荐的标准值,我实测在400KHz速率下工作正常。MPU6050的AD0引脚接地,I2C地址就是0x68,接高电平则变成0x69,这个在固件初始化的时候要对上。
电机驱动模块以TB6612FNG为核心。它有四路输入引脚用于控制两路电机(AIN1/AIN2对应A路,BIN1/BIN2对应B路),两路PWM输入(PWMA/PWMB),以及STBY使能引脚。设计的时候STBY引脚必须接高电平,否则整个芯片进入待机模式,电机完全不动。电机输出端A01/A02接电机A的两根线,B01/B02接电机B。电机电源VM和逻辑电源VCC要分开,VM接7.4V,VCC接5V,两者之间用一个10uF电解电容和一个100nF陶瓷电容并联去耦。
蓝牙串口接口这块,HC-05是3.3V逻辑电平,但它的TXD/RXD不能直接和STM32的5V电平引脚对接。好在STM32F103C8T6的GPIO大部分容忍5V,但保险起见我还是在TXD线上加了一个1K串联电阻限流,RXD线因为是从STM32输出到HC-05,而HC-05是3.3V电平,直接连接即可。文档里我写了串口参数是115200波特率、8位数据、无校验、1位停止位,这个要和固件初始化代码保持一致。
2.2 PCB布局布线避坑指南:从元件摆放到地线处理
原理图画好后就是PCB,这部分跟原理图比更考验耐心。我的PCB是双层板,尺寸控制在100mm×70mm左右,板厚1.6mm。两层板对于这个项目来说完全够用,只要布局合理,不需要做四层。
元件摆放的核心原则是功能分区+信号流向清晰。我把主控芯片放在板子中央偏上位置,方便四周的接口和外围器件都朝它靠拢;MPU6050放在板子中央,这是姿态传感器的安装位置要求——传感器越接近车体的几何中心,测量的加速度越准确,角速度的杠杆效应也越小;TB6612放在电机接口附近,让电机驱动线尽量短;HC-05放在板子边缘,方便蓝牙天线方向朝外,不被其他金属器件遮挡;电源模块放在一角,发热元件和敏感模拟电路分开。
地线处理是这个板子的关键。我用了整块地平面铜皮(Ground Pour),把顶层和底层的空白区域都铺上地铜,通过过孔连接。这样做的好处是每个元器件的地引脚都能就近打孔连接到地平面,形成完整的回流路径,大幅降低地环路面积,减少EMI。我第一次做的时候顶层没铺铜,只走信号线,结果电机一启动,MPU6050的数据就频繁跳变,后来顶层铺铜之后情况改善很多。
电源走线宽度方面,我给自己定了一个经验值:电机VM走线宽度不低于40mil,5V和3.3V主干线不低于30mil,普通信号线15mil。如果板子面积紧张,VM至少也要走25mil以上,否则大电流时压降很大,电机启动瞬间电压跌落会导致MCU复位。
然后说一个新手容易忽略的坑——MPU6050附近不要走大电流线路,特别是电机驱动线。电机线里流动的是高频PWM电流,会产生较强的电磁场,干扰MPU6050的I2C信号和模拟电源,导致姿态数据噪声很大。我的做法是MPU6050放在板子中央,周围只走低频信号线,并且在其下方铺一块完整的地铜皮作为屏蔽层。
2.3 关键元器件选型:这些参数别拍脑袋定
元器件选型这块,很多新手容易踩的一个坑是只看封装不看参数。我这里把几个关键器件列一下,给大家做参考。
晶振建议选8MHz 20ppm的无源贴片晶振,负载电容10-20pF,ESR不要太大。如果频率偏差大,影响的是系统时钟精度,会导致串口波特率偏移、定时器定时不准,进而影响PID采样周期的一致性。
锂电池选两节18650串联,容量根据你的电机功率和使用时长来定。市面上18650容量虚标严重,建议选正规品牌的2600mAh到3500mAh。降压芯片用AMS1117-5.0和AMS1117-3.3,输出电流能力各有1A和800mA,对这个系统足够。注意AMS1117系列输入输出压差至少1.2V以上才能正常稳压,所以7.4V转5V没问题,5V转3.3V压差只有1.7V,处于临界边缘,实测下来负载别超过300mA基本稳定。
电机推荐用N20微型减速电机,带霍尔编码器的那种,减速比1:30,额定电压6V,空载转速大约300转/分。如果你后面想加速度闭环,编码器输出是必须的,所以选型的时候直接选带编码器的版本,一步到位省得后头再换。
3. STM32固件实现:从姿态解算到PID控制
3.1 MPU6050数据读取与姿态解算
固件代码的第一个核心块是MPU6050的驱动和姿态解算。初始化流程包括:配置I2C时钟400KHz,唤醒MPU6050(退出睡眠模式),设置陀螺仪量程±2000°/s,加速度计量程±2g,配置数字低通滤波器(DLPF)截止频率到42Hz,配置采样率1KHz。
量程选择不是随便定的。平衡车正常运动过程中,车体的角速度变化范围一般在±400°/s以内,用±2000°/s量程可以保证任何剧烈动作都不会溢出,虽然分辨率会低一些,但配合滤波完全够用。加速度计量程±2g适合测量倾角,因为车体接近竖直时加速度计的静态输出主要是重力分量,量程太大会浪费分辨率。
姿态解算我用了两种方式,工程里两种都写了,可以切换。一种是互补滤波算法,把陀螺仪积分得到的角度和加速度计计算得到的角度加权融合,公式是:
angle = 0.98 * (angle_previous + gyro_rate * dt) + 0.02 * accel_angle0.98和0.02这两个权重系数是调出来的。陀螺仪短时间内积分很准,但长时间会漂移;加速度计瞬时值有振动噪声,但长期稳定。互补滤波就是信任陀螺仪的短期变化,用加速度计的长期稳定来修正漂移。实际测试中这个算法在平衡车上就够用,采样周期2ms的条件下,角度误差可以控制在±0.5°以内。
另一种是MPU6050的DMP(数字运动处理器)模式,直接读取四元数,再转成欧拉角。DMP的好处是算法在传感器内部跑,不占MCU算力,代码也简单。坏处是对MPU6050的寄存器配置比较繁琐,一旦出错很难调试。我工程里两种都写了,默认跑互补滤波,想看输出更平滑可以切换到DMP模式。
3.2 两级PID:直立环和速度环缺一不可
平衡车的控制逻辑可以分成三个环:直立环(角度环)、速度环、转向环。直立环是根本,速度环和转向环在直立环的基础上叠加。我的实现方式不是三环全做,而是主做直立环和速度环,转向环直接用遥控指令的左右偏置量叠加。
直立环的控制器输入是车体倾角,输出是电机PWM基值。如果车体向前倾倒,直立环会让轮子向前加速,用惯性力把车体拉回来。直立环的PID参数是整个项目中影响最大的部分,我用的是PD控制:
PWM_balance = Kp_angle * angle + Kd_angle * gyro_rate这个公式里没有积分项,因为直立环要求响应快、稳态误差小,加积分反而容易产生振荡。Kp_angle的值大致范围在15-30之间(这个值取决于你的PWM分辨率、电机特性和传感器灵敏度,需要实测调整),Kd_angle在0.3-1.0之间。调参顺序是先只调Kp,从小到大,找到车体能来回摆动但不倒的临界值,然后加Kd抑制振荡,让小车慢慢稳定下来。
速度环的作用是让小车在平衡的基础上保持设定的速度。如果只靠直立环,小车会往一个方向越走越快失去平衡,因为车体需要用一定的倾角来获得前进的加速度,而这个倾角会让它一直加速。速度环通过检测编码器的转速累加,当发现车速过快时,给直立环一个额外的倾角偏置,让它稍微往反方向倾斜一下来减速。
速度环我用PI控制:
speed_error = target_speed - current_speed speed_integral += speed_error * dt PWM_speed = Kp_speed * speed_error + Ki_speed * speed_integral最终叠加到电机PWM上:
PWM_left = PWM_balance - PWM_speed + PWM_turn PWM_right = PWM_balance - PWM_speed - PWM_turn3.3 电机驱动与编码器读取:PWM配置和定时器资源利用
STM32F103C8T6的定时器资源很丰富,我做如下分配:TIM2用来读取左轮编码器,TIM4用来读取右轮编码器(配置成编码器模式);TIM3的CH1和CH2输出PWM给TB6612的PWMA和PWMB;TIM1用作控制周期定时器,1ms中断一次,在中断里执行PID计算和指令解析。
PWM频率我选的是10KHz。这个频率下TB6612的开关损耗和电机的电磁噪声能达到一个比较好的平衡。频率太低电机会发出尖锐的啸叫声,频率太高驱动芯片的开关损耗增大、发热严重。10KHz电机基本静音,芯片温度也正常,跑起来很稳。
编码器读取这块,用的是N20电机自带的霍尔编码器,每转输出约11个脉冲,减速比1:30,所以车轮每转一圈编码器输出330个脉冲。我用的是4倍频计数模式,实际每圈计数值是1320,配合轮子直径65mm,每个脉冲对应约为0.155mm的位移,这个分辨率做速度环足够了。
编码器接口接在定时器的CH1和CH2上,硬件配置成编码器模式后,定时器计数器会自动根据A、B两相的相位关系做加减计数,完全不用CPU干预,代码里只需要在1ms中断里读取定时器的计数器值并清零即可。这个硬件功能,比软件判断A、B相的做法效率高出一个量级。
3.4 串口协议与蓝牙指令解析
固件端的另一个核心是串口协议。HC-05蓝牙模块接到STM32的USART2上,115200波特率,中断接收。我自定义了一个简单可靠的指令帧格式,固定9字节:
起始字节(0xA5) + 功能码(1字节) + 数据区(6字节) + 校验字节(1字节)功能码定义如下:
- 0x01:设置目标速度,数据区前两个字节是int16_t类型的左右轮速度平均值
- 0x02:设置转向偏置,数据区前两个字节是int16_t类型的转向量
- 0x03:请求回传状态,数据区全部填0
- 0x04:使能/禁能平衡功能,数据区第一个字节是1或0
校验字节采用简单的异或校验,对功能码和数据区的7个字节逐字节异或得到。这样通信过程中只要有一位错误,校验就过不了,固件直接丢弃这一帧。虽然用CRC32更可靠,但在这个低速遥控场景下异或校验完全够用,代码也简单。
回传状态帧格式也类似,功能码0x83,数据区装角度(int16_t)、目标速度(int16_t)、当前速度(int16_t)三个变量。APP端每200ms请求一次,实时显示小车的状态。
4. 安卓蓝牙遥控A从源码到APK打包
4.1 经典蓝牙开发流程:UUID和Socket是两个关键
安卓端蓝牙开发,首先必须搞清楚一个概念:HC-05是经典蓝牙(Bluetooth Classic),走的是RFCOMM协议,不是BLE(Bluetooth Low Energy)。很多新手在BLE和经典蓝牙之间搞混,写了一大堆BLE代码结果连不上HC-05。
经典蓝牙开发的流程大致是:获取蓝牙适配器BluetoothAdapter,检查蓝牙是否开启,扫描设备,找到HC-05后配对,然后通过UUID建立Socket连接。连接的核心代码如下:
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(uuid); socket.connect();这里UUID很关键,SPP(Serial Port Profile)服务的标准UUID是:
private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");这个UUID是蓝牙SIG组织规定的串口服务通用UUID,HC-05模块内部默认就支持这个服务。如果你随便写一个UUID,大概率连不上。
权限这块,安卓版本不同差别很大。Android 6.0及以上需要动态申请定位权限(因为扫描蓝牙设备需要位置权限),Android 12及以上还需要BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限。我工程里做了完整的兼容处理,在AndroidManifest.xml里声明了所有可能用到的权限,同时在MainActivity里动态检查请求。
4.2 遥控界面设计与指令帧组装
遥控APP的界面,我设计得比较简洁:界面上方是一个状态栏,显示蓝牙连接状态和当前回传的角度/速度;中间是两个方向控制按钮区,左侧是"前进/后退/左转/右转"四个方向键;下方是一个速度滑块,控制小车的最大速度。
界面布局用LinearLayout和RelativeLayout嵌套实现,没用复杂的自定义View,因为核心功能是遥控,不是炫UI。每个按钮的事件处理里调用一个sendCommand方法:
private void sendCommand(byte funcCode, byte[] data) { if (bluetoothSocket == null || !bluetoothSocket.isConnected()) { Toast.makeText(this, "蓝牙未连接", Toast.LENGTH_SHORT).show(); return; } byte[] frame = new byte[9]; frame[0] = (byte) 0xA5; frame[1] = funcCode; System.arraycopy(data, 0, frame, 2, Math.min(data.length, 6)); byte check = 0; for (int i = 1; i < 8; i++) { check ^= frame[i]; } frame[8] = check; // 通过输出流发送 outputStream.write(frame); outputStream.flush(); }安卓端的发送速度控制逻辑是:方向键按下时每100ms发送一次指令,松手后发送一次归零指令。每隔200ms发送一次回传状态请求,用Handler和Runnable实现定时任务。
4.3 APK打包配置:SDK版本、签名和混淆
APK打包这块,我用的是Android Studio的默认Gradle配置。关键参数是minSdkVersion和targetSdkVersion。考虑到国内用户手机系统版本跨度大,我把minSdkVersion设为21(Android 5.0),覆盖了绝大多数设备;targetSdkVersion设为32(Android 12),这样在新的系统权限模型下也能正常请求蓝牙权限。
打包之前,要注意把蓝牙相关权限在AndroidManifest.xml里声明完整:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />最后两个权限是Android 12新增的,如果targetSdkVersion低于31,系统会自动忽略它们,但保险起见还是写上。另外,如果targetSdkVersion是32,系统会在蓝牙连接时弹窗询问用户授权,这个弹窗是系统级的,APP自己不能控制,代码里要处理好授权回调。
签名方面,调试用Android Studio默认的debug签名,正式发布可以用Build -> Generate Signed Bundle / APK生成自己的签名文件。我这里APK直接用debug签名也可以安装到手机上调试,但如果你想发给别人装,建议手动导出一个release签名APK,否则某些手机可能装不上。
4.4 连接失败与传输优化的实战处理
蓝牙连接这块踩过的坑不少,我把常见问题整理一下。
首先是扫描不到HC-05。这往往是手机蓝牙缓存了旧的配对信息,或者HC-05之前连过其他设备还保持连接状态。解决办法是:在手机蓝牙设置里清除配对的HC-05,然后给HC-05重新上电,再扫描。如果还不行,检查HC-05的指示灯,慢闪(大概2秒闪一次)表示可被发现和配对,快闪(大概0.5秒闪一次)表示已连接。状态不对就按住HC-05上的复位键重新进入AT模式。
其次是配对成功后连接失败。连接时抛IOException,一般是因为UUID不对或者蓝牙socket已经过期。BluetoothDevice的createRfcommSocketToServiceRecord方法在某些ROM上会返回一个失效的socket,这时候可以尝试使用反射方法创建socket:
Method m = device.getClass().getMethod( "createRfcommSocket", int.class); BluetoothSocket socket = (BluetoothSocket) m.invoke(device, 1);这个反射方法是安卓系统的一个内部接口,能解决一部分兼容性问题,但并不能保证所有机型都有效。我的代码里做了try-catch,反射失败自动回退到标准的createRfcommSocketToServiceRecord方法。
数据传输这块,串口115200波特率的理论传输速率约11KB/s,实际控制指令一帧9字节,100ms发一次,一分钟才5400字节,完全没有带宽压力。但要注意的是安卓端的数据收发必须在子线程里做,不能在主线程直接操作socket。我专门开了一个HandlerThread处理收发逻辑,避免了接收数据时ANR。
5. 常见问题与调参实录
5.1 小车抖动、往一个方向跑、一放手就倒
这三个问题几乎是每个做平衡车的人都会遇到的,我挨个说下排查思路和解决办法。
小车高频抖动,通常原因是直立环Kd过大或者传感器噪声太大。Kd过大会把高频噪声放大成电机的高频抖动,听起来电机发出细微的"嗡嗡"声。解决办法:先把Kd调小到0,看是否还抖;如果还抖,检查MPU6050输出的角度是否毛刺特别多,可以在串口上打印角度值,连续观察10秒,正常波动应该在±0.2°以内。如果波动很大,检查I2C通信速率是否太快,或者MPU6050附近是否受到电机线干扰。
小车一直往一个方向跑,不断加速直到倒地,这说明速度环没起作用,或者作用方向反了。注意PID的方向问题——直立环控制电机让车体保持直立,但电机转动会让车体前进,车体前进改变了倾角,直立环又会纠正倾角,两者之间存在耦合。如果速度环方向反了,会让情况更糟。快速判断方法:把速度环的Kp设为0,观察小车往哪个方向跑,然后调整速度环的符号,让它产生一个阻止小车继续加速的趋势。
一放手就倒,一点都平衡不住,首先检查直立环Kp是否太小。我给的参考值是15-30,如果你的板子质量大、电机扭矩小,可能需要更大的Kp。其次是检查电机响应是否太慢,可以用手机录慢动作视频看电机是否在车体倾倒方向变化的瞬间就反转。电机反应慢可能是因为PWM频率太低、TB6612供电电压不足、或者电机驱动代码里加减速处理太慢。
5.2 HC-05蓝牙连不上或者连上频繁断连
蓝牙问题要分两层排查:硬件层和软件层。
硬件层先看HC-05和STM32之间的接线。HC-05的TXD接STM32的RXD(PA3),HC-05的RXD接STM32的TXD(PA2),交叉连接不要接反。共地是必须的,两块板子不共地就会通信不稳定甚至完全不通。然后看供电,HC-05峰值电流能到40mA左右,3.3V供电电压不能低于3.0V,我用AMS1117-3.3直接供电,实测稳定。
软件层先确认串口参数是否完全匹配:115200、8N1。我遇到过好多次,手机端蓝牙串口工具设置的波特率是对的,但固件里USART初始化配置错了数据位,导致收发数据乱码。另外固件里的串口中断优先级要设得高点,防止被PID定时器中断打断导致字节丢失。HC-05默认的波特率是38400,但我的固件是用AT指令把它改成115200了的,你拿到模块要先确认或重新配置。
断连问题,最常见的原因是HC-05进入了睡眠或者配对模式。当手机蓝牙和HC-05距离太远,或者中间隔了金属板,信号衰减会造成连接断开。另外如果两台手机同时配对过同一个HC-05,HC-05可能会接到另一台手机的重连请求,导致当前连接被挤掉。解决办法是给HC-05设置一个配对PIN码,别人不知道就配对不上。
5.3 PID参数调整的实用经验:先直立环后速度环
最后总结一下我给PID调参的完整顺序,这个方法帮我在不同的车体结构上快速调出稳定效果。
第一步,只调直立环。把速度环和转向环的输出全部屏蔽,只保留PWM_balance。Kp从小到大慢慢加,每次加1-2,观察车体是否能来回摆动。当Kp加到某个值,车体能摆动几下然后停下来,说明Kp接近临界,继续加一点,让车体能保持直立一小段时间。
第二步,加Kd抑制振荡。Kd从0.1开始加,每次加0.05,观察车体的摆动幅度是否减小。Kd太大,高频抖动明显,Kd太小,车体动态响应慢,推一下会晃很多下才稳住。最终目标是:车体被轻推一下后,能在1-2秒内恢复直立,没有明显的往复振荡。
第三步,加速度环。把目标速度设为0,让小车原地保持平衡不跑偏。Kp_speed从0.5开始加,观察小车是否会被拉回到零速位置。Ki_speed从0.05开始加,用来消除静态误差。这里要特别强调Ki_speed不能太大,否则积分饱和会让小车大幅度振荡,典型的症状就是小车先来回跑几次,然后突然朝一个方向冲出去。
第四步,调转向环。转向环的Kp一般比直立环小很多,它只是在直立环的基础上叠加一个小偏差,让左右轮出现差速。如果左右轮差速太猛,小车会原地打转然后失去平衡,所以转向量最终要限幅在直立环PWM输出的30%以内。
调参过程中我一直会在手机上用串口助手实时查看角度、速度、PWM三个量,画出波形来看,比肉眼观察车体直观得多。如果你没有蓝牙串口助手,也可以用USB转TTL模块通过有线连接的USART1口来看调试信息。
5.4 最终集成联调时的坑:干扰、供电、机械结构
所有模块单独测试没问题后,集成联调阶段还会有一些综合性问题冒出来。
电机干扰导致MPU6050数据跳变,这个我在前面PCB部分提过,但联调阶段还是最容易再次出现。判断方法很简单:把小车放在桌面上,不启动电机,看姿态数据是否稳定;然后用手转动轮子,看数据是否受影响。如果电机转动会导致角度值跳变几度甚至几十度,大概率是PCB布局问题或者电机线太长太靠近传感器。临时处理办法是给电机线套磁环,在电机输入引脚并联一个104陶瓷电容和一个电解电容;根治办法是重新布局PCB,把电机线远离传感器。
供电不足导致MCU频繁复位,症状是小车跑动时姿态突然归零、蓝牙掉线等。这个是典型的"大电流把电源拉垮了"问题。电机启动瞬间电流可能达到1A以上,如果电源线太细、降压芯片选型余量不足,电压就会被拉低到MCU复位阈值以下。之前说的40mil以上VM走线宽度不是随便定的,就是这个原因。另外在电池正负极和电源入口处各并联一个470uF电解电容和100nF陶瓷电容,能明显改善动态响应。
机械结构方面,容易忽略的是定位问题。车体的重心尽量低、左右对称,MPU6050的安装角度和代码里初始化角度一致,否则零偏校准会有问题。这里给一个建议:把电池放在车体下方,贴着底板放,重心低了车体更稳;MPU6050的X轴沿着前进方向安装,这样代码里的姿态角和遥控方向逻辑好对应。
以上都是反复折腾过、踩过坑之后总结出来的经验。硬件设计、固件逻辑、安卓开发,这三块内容每一块展开都有很多可以深挖的地方。这套资料里把这些环节都串起来了,照着走一遍,你对STM32、PID控制、蓝牙通信、安卓开发这几个方向都会有更深的理解。后面我还在整理一个带超声波避障和视觉循迹的升级版本,等做完了再继续更新这套平衡小车资料。
本文还有配套的精品资源,点击获取