【平衡小车进阶】(一)蓝牙串口协议解析与多模式遥控实现(附源码)

张开发
2026/4/17 23:55:04 15 分钟阅读

分享文章

【平衡小车进阶】(一)蓝牙串口协议解析与多模式遥控实现(附源码)
1. 蓝牙串口通信基础与硬件选型玩平衡小车最爽的部分莫过于用手机遥控了但很多小伙伴卡在蓝牙通信这一关。我当年第一次用HC-05模块时光是AT指令配置就折腾了一整天。现在回头看其实只要掌握几个关键点就能少走弯路。核心硬件选择方面HC-05和HC-06是最常见的蓝牙串口模块价格都在20元以内。两者的主要区别在于HC-05支持主从模式切换而HC-06只能作为从机。对于平衡小车这种单向控制场景HC-06完全够用。实测下来这类模块的通信距离在空旷环境下能达到10米室内有遮挡时约3-5米完全满足日常玩耍需求。串口配置有三个关键参数需要特别注意波特率建议用9600这是出厂默认值兼容性最好数据位固定8位停止位选1位无校验在STM32端初始化时我习惯用USART3而不是USART1因为PB10/PB11引脚位置更靠近板子边缘接线更方便。下面是我的初始化代码优化版增加了错误处理void uart3_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; // 使能时钟时增加错误检查 if(RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOB, ENABLE) ! SUCCESS) { printf(GPIOB clock enable failed!\n); while(1); } // 引脚配置省略...与示例代码相同 // 增加串口状态检测 USART_Cmd(USART3, DISABLE); while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) RESET); USART_Cmd(USART3, ENABLE); }硬件连接时有个坑要注意蓝牙模块的TX要接STM32的RXRX接TX这个反接规则新手特别容易搞错。我曾因此浪费两小时查代码结果发现是线接反了。建议用不同颜色的杜邦线区分比如红色永远接TX黑色接RX。2. 通信协议设计实战原始代码里用0x01表示前进、0x02后退这种简单协议没问题但实际项目我推荐更健壮的方案。下面分享我迭代过三次的协议设计经验。基础帧结构至少要包含帧头0xAA 0x55用于同步指令类型1字节数据长度1字节数据域N字节校验和1字节异或校验比如控制指令可以这样定义#pragma pack(1) typedef struct { uint8_t head[2]; // 0xAA 0x55 uint8_t cmd; // 0x01:运动控制 0x02:参数配置 uint8_t len; // data长度 uint8_t data[8]; // 有效载荷 uint8_t checksum; // 校验位 } BluetoothFrame; #pragma pack()多模式控制的实现关键在于状态机设计。我常用的三种模式切换逻辑点动模式按住按钮时持续运动松开即停定速模式点击后保持固定速度直到新指令巡航模式自动维持当前运动状态对应的处理函数可以这样写void handle_motion_ctrl(uint8_t mode, int8_t speed) { static uint8_t last_mode 0; switch(mode) { case 0x01: // 点动 motor_set_speed(speed); break; case 0x02: // 定速 if(mode ! last_mode) { motor_lock_speed(speed); } break; case 0x03: // 巡航 motor_keep_moving(); break; } last_mode mode; }校验和的计算有个高效写法uint8_t calc_checksum(uint8_t *data, uint8_t len) { uint8_t sum 0; while(len--) sum ^ *data; return sum; }3. 多模式遥控的代码实现原始代码只能实现基础运动控制我们扩展为带速度调节的多模式控制。先定义指令集// 运动控制指令 #define CMD_STOP 0x00 #define CMD_FORWARD 0x01 #define CMD_BACKWARD 0x02 #define CMD_LEFT 0x03 #define CMD_RIGHT 0x04 // 模式切换指令 #define MODE_MANUAL 0x10 // 点动 #define MODE_FIXED 0x20 // 定速 #define MODE_CRUISE 0x30 // 巡航 // 速度调节 #define SPEED_UP 0x40 #define SPEED_DOWN 0x41中断处理函数要重构为状态机模式void USART3_IRQHandler(void) { static BluetoothFrame frame; static uint8_t cnt 0; if(USART_GetITStatus(USART3, USART_IT_RXNE)) { uint8_t byte USART_ReceiveData(USART3); // 简单状态机解析帧 switch(parse_state) { case 0: if(byte 0xAA) parse_state; break; case 1: if(byte 0x55) parse_state; else parse_state0; break; case 2: frame.cmd byte; parse_state; break; // ...其他字段解析 case 5: if(byte calc_checksum((uint8_t*)frame, 4frame.len)) { process_frame(frame); // 有效帧处理 } parse_state 0; break; } } }速度环控制需要增加模式判断void speed_control(void) { static int target_speed 0; switch(current_mode) { case MODE_MANUAL: target_speed manual_speed; break; case MODE_FIXED: // 固定速度不做调整 break; case MODE_CRUISE: target_speed encoder_avg_speed; break; } // PID计算 int output pid_calculate(target_speed, actual_speed); motor_output(output); }4. 手机APP与STM32的交互优化原始方案只能发简单指令我们可以用MIT App Inventor或者Android Studio开发功能更丰富的APP。这里分享几个实用技巧关键控件设计摇杆控件模拟游戏手柄操作模式切换开关三态选择器速度滑块0-100%线性调节紧急停止按钮大红色显眼设计数据发送建议采用JSON格式虽然会增加解析复杂度但扩展性更好。例如{ mode: fixed, speed: 60, direction: forward, timestamp: 123456789 }在STM32端可以移植cJSON解析器或者用简化版的字符串处理void parse_json(char *json) { char *p strstr(json, \speed\:); if(p) { p 8; current_speed atoi(p); } p strstr(json, \mode\:); if(p) { p 7; if(strncmp(p, \fixed\, 7) 0) { current_mode MODE_FIXED; } // 其他模式判断... } }性能优化方面我踩过的坑发送频率不要超过50Hz否则STM32可能处理不过来每条指令添加时间戳用于网络延迟补偿增加心跳包机制3秒无通信自动停车一个实用的调试技巧在APP里添加调试信息显示区域实时显示发送的指令和接收到的传感器数据。我在车体上加装了蓝牙信号强度指示LED通过RSSI值控制LED颜色绿色表示信号良好红色表示即将断开。5. 常见问题与调试技巧连接不稳定的解决方案检查天线位置不要被金属物体遮挡降低波特率从115200降到9600添加软件重连机制void check_connection(void) { static uint32_t last_time 0; if(HAL_GetTick() - last_time 3000) { if(!connection_active) { try_reconnect(); } last_time HAL_GetTick(); } }数据丢包的处理方法增加帧序号检测typedef struct { uint8_t seq; // 新增序号字段 //...其他字段 } Frame;实现简单的重传机制添加数据统计功能void print_stats(void) { printf(Total frames: %d\n, total_frames); printf(Lost frames: %d (%.2f%%)\n, lost_frames, (float)lost_frames/total_frames*100); }电机响应延迟的优化方向提高控制循环频率到至少100Hz使用DMA传输代替中断方式优化PID计算代码// 使用定点数运算替代浮点 int32_t pid_calculate(int32_t setpoint, int32_t input) { static int32_t last_error 0; static int32_t integral 0; int32_t error setpoint - input; integral error; integral constrain(integral, -INTEGRAL_MAX, INTEGRAL_MAX); int32_t derivative error - last_error; last_error error; return (Kp * error Ki * integral Kd * derivative) / SCALE_FACTOR; }调试时建议逐步验证先用串口助手测试蓝牙模块收发再验证STM32解析逻辑最后测试电机实际响应 每个阶段都可以添加LED指示灯辅助调试比如收到数据时闪烁LED解析错误时长亮红灯等。

更多文章