首页 / 资讯中心 / 文章详情

STM32+OpenMV电赛巡线小车:串口通信与PID调参实战

STM32+OpenMV电赛巡线小车:串口通信与PID调参实战 ★ FEATURED ARTICLE
简介这份资源是2024年全国大学生电子设计竞赛H题巡线小车的完整工程面向备赛电赛控制类赛题的学生与嵌入式爱好者。方案以STM32为主控、OpenMV摄像头实现巡线核心思路是计算黑线与中心线的偏差并据此转向与官方要求的灰度传感器方案原理一致便于替换移植。压缩包共1053个文件约27.45MB以570个C源文件、256个头文件、51个汇编文件为主另含工程配置、库文件与少量Python脚本构成可直接编译的完整工程。目前已有4259人学习下载。读者可从中获取巡线控制逻辑、ABCD四点间丝滑衔接的调试思路、MPU6050零漂处理与初始摆放角度对转向影响的参考经验以及变速行驶优化稳定性和耗时的改进方向适合在此基础上二次开发与参数微调。1. 从零拆解一套电赛巡线小车工程STM32OpenMV 到底怎么配合如果你正在准备 2024 电赛 H 题这类巡线小车赛项大概率已经翻过不少帖子最后发现真正能跑通的完整工程少之又少。我手上这套 STM32OpenMV 的工程核心不是炫技而是把「视觉识别—串口通信—电机闭环」这条链路做扎实。它适合两类人一是第一次打电赛、需要一份能直接编译下载的参考框架的新手二是做过小车但视觉和主控总是配合不好的老手。整套方案里 OpenMV 负责找线和识别元素STM32 负责决策和控速两者通过串口协议交换数据。下面我按实际复现顺序把选型理由、接线、代码和踩坑点全部拆开讲。2. 硬件选型与串口通信协议为什么不用纯 STM32 方案2.1 视觉方案选型OpenMV 与纯灰度传感器的边界纯灰度传感器方案在直道和缓弯上确实够用但 2024 电赛 H 题的赛道元素里出现了断线、十字、锐角弯灰度方案一旦丢线就只能靠编码器盲走容错率极低。OpenMV 的优势在于它能输出线的角度和偏移量而不是简单的开关量。我一般会把 OpenMV 的 ROI 设在图像下半部分减少远处畸变干扰同时把分辨率降到 QQVGA160×120帧率能稳在 40fps 以上。这里有个反直觉的点分辨率越低巡线反而越稳因为噪点少了find_blobs 的阈值也更好调。选 OpenMV 的另一个理由是它的 UART 输出可以直接对接 STM32 的串口不需要额外的电平转换。常见做法是把 OpenMV 的 P4TX接 STM32 的 PA10RXP5RX接 PA9TX共地。注意 OpenMV 的 IO 是 3.3V 电平STM32 也是 3.3V直接连没问题但如果你用的是 5V 的 Arduino 类主控就必须加电平转换否则 OpenMV 的 RX 脚会被拉坏。2.2 串口帧格式设计定长帧比变长帧更抗干扰很多新手喜欢用\n结尾的变长字符串传数据比如120,30\n这种格式在实验室里没问题但赛场上电机一启动串口误码率立刻上升变长帧一旦丢一个字节后面全部错位。我在这套工程里用的是定长帧帧头 0xAA、帧尾 0x55中间 4 个字节分别是偏移量高八位、偏移量低八位、角度、校验和。STM32 端用状态机接收收到帧头才开始存收到帧尾才校验。// STM32 串口接收状态机放在 USART1_IRQHandler 里 #define FRAME_HEAD 0xAA #define FRAME_TAIL 0x55 uint8_t rx_buf[6]; uint8_t rx_cnt 0; uint8_t frame_ready 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); if (rx_cnt 0 ch ! FRAME_HEAD) return; // 没收到帧头就丢弃 rx_buf[rx_cnt] ch; if (rx_cnt 6) { if (rx_buf[5] FRAME_TAIL) { uint8_t sum rx_buf[1] rx_buf[2] rx_buf[3]; if (sum rx_buf[4]) frame_ready 1; // 校验通过 } rx_cnt 0; // 无论对错都复位防止卡死 } } }这段代码的关键在rx_cnt 0 ch ! FRAME_HEAD这一句它保证状态机只在帧头处启动中间任何干扰字节都会被跳过。校验和用简单的累加和就够了不用上 CRC16因为串口线很短误码主要是偶发跳变累加和足够过滤。参数上波特率我设的是 115200再高的话 OpenMV 的串口发送会丢包再低则帧率跟不上车速。2.3 电机驱动与编码器接口分配电机驱动我用的是 TB6612比 L298N 效率高、发热小。STM32 这边用 TIM1 的 CH1/CH2 输出 PWM 给左右电机TIM2 和 TIM3 的编码器模式读左右轮速。注意编码器模式要配置成「编码器接口模式」而不是普通的输入捕获否则计数方向会乱。接线时 A 相接 PA0、B 相接 PA1对应 TIM2 的 CH1 和 CH2。如果你用的是霍尔编码器上拉电阻别忘了加否则波形上升沿会变缓高速时丢计数。3. OpenMV 端巡线算法ROI、阈值与角度解算3.1 图像预处理灰度化与自适应阈值OpenMV 的find_blobs对光照非常敏感赛场顶灯和窗户光混在一起固定阈值基本必翻车。我一般用sensor.set_auto_gain(False)和sensor.set_auto_whitebal(False)先把自动增益和白平衡关掉然后在赛道现场用sensor.snapshot()取一帧手动调 LAB 阈值。更稳的做法是跑一个简单的自适应取图像底部 20 行的灰度均值动态调整黑线阈值。# OpenMV 端巡线主循环简化版 import sensor, image, time from pyb import UART sensor.reset() sensor.set_pixformat(sensor.GRAYSCALE) sensor.set_framesize(sensor.QQVGA) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) sensor.skip_frames(time500) uart UART(3, 115200) while True: img sensor.snapshot() # ROI 取图像下半部分减少远处干扰 roi (0, 60, 160, 60) # 灰度阈值黑线在 0-80 之间具体现场调 blobs img.find_blobs([(0, 80)], roiroi, pixels_threshold100, area_threshold100) if blobs: b max(blobs, keylambda x: x.pixels()) # 取最大色块 offset b.cx() - 80 # 中心偏移范围 -80 到 80 angle b.rotation() # 色块旋转角单位弧度 # 打包成定长帧 frame bytearray([0xAA, (offset 8) 0xFF, offset 0xFF, int(angle * 100) 0xFF, 0, 0x55]) frame[4] (frame[1] frame[2] frame[3]) 0xFF uart.write(frame) else: # 丢线时发一个特殊值STM32 端做保护 uart.write(bytearray([0xAA, 0x7F, 0xFF, 0x00, 0x00, 0x55])) time.sleep_ms(10)这里pixels_threshold和area_threshold都设成 100是为了过滤掉赛道上的小污点。b.rotation()返回的是色块的旋转角对于细长黑线来说这个角度直接反映线的倾斜程度。注意rotation()在色块接近正方形时不稳定所以我在 STM32 端加了判断如果色块宽高比小于 1.5就忽略角度只用偏移量。3.2 偏移量与角度的融合决策STM32 收到偏移量和角度后不能直接拿偏移量做 PID否则在锐角弯会震荡。我的做法是直道和缓弯用偏移量做位置环 PID锐角弯用角度做前馈。具体来说当abs(angle) 30度时把角度乘以一个系数直接叠加到转向量上同时降低车速。这个系数我一般从 0.5 开始试太大车会抖太小入弯不及时。// STM32 端转向决策放在主循环里 float offset (int16_t)((rx_buf[1] 8) | rx_buf[2]); float angle (int8_t)rx_buf[3] / 100.0f; float steer 0; if (fabs(angle) 0.5f) { // 约 30 度 steer offset * 0.3f angle * 80.0f; // 角度前馈 set_speed(300); // 入弯降速 } else { steer offset * 0.8f; // 纯位置环 set_speed(500); } // steer 限幅后给电机 if (steer 200) steer 200; if (steer -200) steer -200;offset * 0.3f和angle * 80.0f这两个系数不是拍脑袋来的offset 范围是 -80 到 80乘 0.3 后转向量在 -24 到 24 之间angle 范围是 -1.5 到 1.5 弧度乘 80 后是 -120 到 120两者叠加后锐角弯的转向量明显大于直道符合预期。set_speed里的 300 和 500 是 PWM 占空比具体值要根据你的电机和电池电压调电池从 8.4V 掉到 7.4V 时同样的占空比车速会慢 15% 左右所以赛前最好用稳压源或者做电压补偿。3.3 丢线保护与编码器辅助OpenMV 丢线时发0x7F 0xFFSTM32 收到后不能立刻停车否则车会顿挫。我的策略是丢线后先保持上一次的转向量同时用编码器差值判断车是否在直行。如果左右编码器差值很小说明车还在直线上继续走如果差值突然变大说明车在偏立刻反向修正。这个逻辑能救回大部分因为反光导致的瞬时丢线。4. 避坑与排查调车现场最常见的五个翻车点4.1 串口数据错位车一启动就乱转现象电机不转时串口数据正常电机一启动STM32 收到的偏移量变成乱码车原地打转。 原因电机启动瞬间电流突变串口地线被抬升导致电平误判。另外 OpenMV 和 STM32 如果分别供电共地没做好也会这样。 解决电机电源和主控电源分开走线在 TB6612 的 VM 和 GND 之间并一个 470uF 电解电容串口线用双绞线或者屏蔽线。如果还不行把波特率降到 57600牺牲一点帧率换稳定。4.2 OpenMV 帧率虚标实际只有 15fps现象代码里设了 QQVGA但串口收到的帧间隔明显大于 10ms车高速时反应迟钝。 原因find_blobs的pixels_threshold设得太小图像里噪点全被当成色块算法耗时暴涨。另外sensor.skip_frames没加够自动增益没稳定。 解决把pixels_threshold提到 200 以上area_threshold同步提高。在sensor.reset()后加sensor.skip_frames(time1000)确保第一帧就是稳定图像。实测这样改完QQVGA 下能稳在 35fps 以上。4.3 PID 参数整定P 大了震荡P 小了画龙现象车在直道上左右摆或者入弯时切内道切得太晚。 原因位置环 PID 的 P 和 D 没配合好或者采样周期和串口帧率不匹配。 解决先把 I 和 D 置零只调 P让车能勉强走直线但有点摆然后加 D摆会明显减小最后加一点点 I 消除稳态误差。采样周期必须和串口帧率一致OpenMV 发 10ms 一帧STM32 的 PID 就 10ms 算一次不要用定时器另开一个 5ms 的周期否则数据没更新PID 在算旧值。4.4 电池电压下降导致后半程跑偏现象刚上场时车很猛跑两圈后入弯速度明显不够甚至丢线。 原因锂电池从 8.4V 掉到 7.4V电机转速下降但 PID 参数是按满电调的。 解决在 STM32 里用 ADC 读电池电压做一个简单的占空比补偿pwm_out pwm_base * (8.4 / v_bat)。注意补偿系数不要超过 1.3否则低压时占空比过大电机电流会超限。4.5 编码器计数方向反了速度环变正反馈现象速度环一开车就飞车轮子越转越快。 原因编码器 A/B 相接反或者 TIM 的编码器模式极性配错导致速度反馈符号反了。 解决先不开速度环用手转轮子看串口打印的编码器值是不是正转增加、反转减小。如果反了把 A/B 相接线对调或者在代码里把计数取反。这个坑很隐蔽因为位置环不开速度环时表现正常一开速度环就炸。5. 进阶技巧用 VOFA 做实时波形调参调 PID 最痛苦的是盲调改一个参数下载一次效率极低。我后来养成一个习惯用 STM32 的另一个串口把偏移量、角度、转向量、左右轮速打包发给 VOFA用 JustFloat 协议实时看波形。这样 P 大了还是 D 大了一眼就能看出来。具体做法是定义一个float data[4]每 10ms 发一次VOFA 里选 JustFloat 模式波特率 115200 或更高。// VOFA JustFloat 协议发送放在 10ms 定时器里 float data[4]; data[0] offset; data[1] angle; data[2] steer; data[3] encoder_diff; uint8_t tail[4] {0x00, 0x00, 0x80, 0x7f}; // JustFloat 帧尾 USART_SendData(USART2, (uint8_t*)data, 16); // 发 4 个 float USART_SendData(USART2, tail, 4); // 发帧尾注意USART_SendData这里我简化写了实际要用 DMA 或者阻塞发送否则会丢字节。VOFA 的 JustFloat 协议要求帧尾是0x00 0x00 0x80 0x7f这个不能错错了波形出不来。调参时我一般把steer和offset放一起看如果steer明显滞后于offset说明 D 太小如果steer在offset附近高频抖动说明 P 太大。还有一个技巧是给 OpenMV 加一个「调试模式」按住 OpenMV 上的按键时串口只发数据不控车同时 IDE 里显示 ROI 框和色块。这样你可以一边推车一边看识别效果比盲猜阈值快得多。从那以后我每次调新车都强制先跑一遍 VOFA 波形和 OpenMV 调试模式确认数据链路没问题再上电机。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站