ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

智能小车系统设计:STM32与双视觉模块的工程实践与抗干扰策略

智能小车系统设计:STM32与双视觉模块的工程实践与抗干扰策略 1. 项目背景与核心挑战解析2021年全国大学生电子设计竞赛电赛的F题“智能送药小车”可以说是我参加过的所有竞赛项目中对系统集成、算法鲁棒性和现场调试能力要求最高的一次。题目要求小车在模拟的医院病房环境中自主识别房间号、病床号并完成从药房到指定病床的药品配送任务。听起来像是经典的循迹识别项目但实际做起来坑是一个接一个。最核心的挑战在于环境的高度不确定性——赛场的光线可能忽明忽暗赛道上的引导线可能存在反光或磨损房间号和数字的张贴位置、角度、光照条件都是变量。这直接决定了你不能用一个“调好参数就一劳永逸”的简单方案必须构建一个具备一定自适应和容错能力的系统。我们团队最终拿到了国一回头来看成功的关键不在于用了多炫酷的算法或多贵的芯片而在于对几个核心矛盾的精准把握和务实解决高速稳定运行与精准识别停顿之间的矛盾、有限算力与复杂图像处理需求之间的矛盾、以及赛题规则严谨性与现场突发状况之间的矛盾。我们的方案主体基于STM32F407作为主控负责小车的运动控制、传感器融合与决策调度视觉部分则采用了K210和OpenMV双视觉模块的架构。这个选择在当时引起了一些讨论很多人觉得用一个就够了何必增加复杂度但正是这个“冗余”设计让我们在关键时刻稳住了阵脚。2. 系统架构设计与核心器件选型逻辑一套可靠的系统始于清醒的硬件选型。我们的核心思路是“专芯专用主从分明”避免将所有任务堆砌到一个处理器上导致实时性崩溃。2.1 主控MCU为什么是STM32F407主控选择STM32F407ZGT6这是经过深思熟虑的。F题小车需要处理多路传感器输入编码器、陀螺仪、超声波等产生精确的PWM控制电机同时还要与视觉模块、无线模块等进行高速通信并对所有信息进行融合决策。这要求主控必须有足够的定时器、通信接口和运算能力。性能与资源Cortex-M4内核带FPU主频168MHz应付多任务调度和浮点运算如PID控制游刃有余。丰富的TIM资源可以轻松实现4路带编码器接口的电机控制多路PWM输出。多个USART和SPI接口满足了与K210、OpenMV、蓝牙/WIFI模块、屏幕等外设的通信需求。实时性与可靠性相比于使用Linux系统如树莓派的方案纯裸机或RTOS的STM32在实时控制上具有绝对优势电机控制环的定时中断可以精确到微秒级这是小车平稳高速运行的基础。我们最终使用了FreeRTOS将电机控制、传感器读取、视觉通信、决策逻辑分别放在不同优先级的任务中确保了关键任务不被阻塞。生态与成本ST的生态成熟资料丰富出了问题容易排查。在紧张的备赛期间时间的价值远大于芯片本身的价差。F407的性能和价格对于电赛小车来说是一个非常好的平衡点。注意当时也有队伍使用STM32H7系列或F4系列其他型号。H7性能更强但功耗和发热也更大且一些外设驱动在赛前准备阶段可能不如F407成熟稳定。我们的原则是在满足需求的前提下选择最成熟、最熟悉的方案降低不可控风险。2.2 视觉系统K210与OpenMV双模块的优劣博弈这是整个设计中最具争议也最体现策略的一点。我们放弃了常见的“单视觉模块补光灯”方案采用了K210与OpenMV协同工作。OpenMV的角色巡线主导者OpenMV Cam H7 的核心任务是循迹。为什么用它而不用K210循迹因为OpenMV的IDE和内置的find_blobs函数对于颜色追踪寻找特定颜色的引导线太友好了。它可以通过简单的阈值设置和ROI感兴趣区域划分快速、稳定地返回引导线的偏移量、角度等信息。其脚本语言基于MicroPython开发调试效率极高。我们将OpenMV固定在小车前端向下倾斜拍摄车头前方的地面专门处理循迹算法并通过串口将线路中心偏移量、是否到达十字路口/丁字路口等信息发送给STM32。K210的角色识别与定位核心K210我们用的是Sipeed Maix Dock的核心任务是数字识别与房间定位。K210搭载了Kendryte K210双核64位RISC-V处理器内置KPU神经网络处理器可以进行卷积神经网络CNN模型的加速推理。这正是我们需要的。数字识别我们使用TensorFlow/Keras训练了一个轻量化的CNN模型识别0-9数字然后通过NNCase等工具转换为K210支持的.kmodel格式。K210负责在行驶到病床附近时对拍摄到的病床编号进行实时识别准确率远高于传统的模板匹配或特征提取方法尤其抗光照变化和角度畸变。房间号识别房间号通常更大我们采用了类似的深度学习模型但在图像预处理阶段加入了透视变换矫正使得即使摄像头不是正对门牌也能先矫正再识别大大提升了复杂角度下的成功率。备用巡线K210通过OpenMV的串口获取基础的循迹状态同时自己也运行一个简单的颜色识别程序作为巡线备份。当主控判断OpenMV传回的数据异常如长时间丢失线路时可以切换至信任K210的巡线数据实现视觉层面的冗余。双视觉方案的价值比赛现场有一支队伍的单OpenMV模块因为现场顶光太强导致地面反光严重颜色阈值完全失效小车原地“鬼畜”。而我们的系统当OpenMV受干扰时STM32会立即检测到其数据异常例如连续若干帧返回“未找到色块”并短暂切换到K210提供的巡线数据同时通过PID控制让小车减速慢行通常能撑过干扰区域待OpenMV数据恢复后再切回。这相当于为视觉系统上了“双保险”。2.3 通信架构如何让三颗“大脑”高效对话STM32、K210、OpenMV三者之间的通信稳定性和效率至关重要。我们采用了主从式串口通信协议。物理连接STM32的USART1连接OpenMVUSART3连接K210。两者均采用115200波特率经验证在此速率下稳定且延迟可接受。协议设计自定义简单的帧结构。例如STM32向OpenMV发送的查询帧为0xAA 0x01 CRCOpenMV回复的循迹数据帧为0xBB 状态字 偏移量高字节 偏移量低字节 角度 CRC。状态字包含了“找到线”、“十字路口”、“左转路口”、“右转路口”等信息。与K210的通信类似但增加了“识别模式”命令字用于切换K210运行数字识别模型还是房间号识别模型。数据融合与决策STM32作为大脑综合处理来自两路视觉的数据、编码器的里程计信息、IMU的陀螺仪数据用于矫正转弯角度。例如当OpenMV报告“十字路口”同时编码器里程计显示距离上一个路口大约符合地图房间间距时STM32才会判定“到达目标房间路口”然后发送指令让K210切换至房间号识别模式进行确认。这种多传感器校验机制极大减少了误判。3. 核心算法实现与调参心得硬件是骨架算法才是灵魂。下面拆解几个最关键算法的实现细节和那些“踩过坑才懂”的参数。3.1 基于OpenMV的鲁棒性巡线算法OpenMV的巡线代码看似简单但想在各种光照下稳定工作需要不少技巧。# OpenMV 巡线核心代码片段 (MicroPython) import sensor, image, time, pyb # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240 速度与精度的平衡 sensor.skip_frames(time 2000) sensor.set_auto_gain(False) # 必须关闭自动增益否则阈值会飘 sensor.set_auto_whitebal(False) # 必须关闭自动白平衡 # 定义寻迹线的颜色阈值实验室环境与赛场环境可能完全不同 line_threshold (30, 80, -20, 20, -20, 20) # (L_min, L_max, A_min, A_max, B_min, B_max) in LAB color space # 设置ROI感兴趣区域我们只关心图像下方的一块区域加快处理速度 roi (0, 120, 320, 120) # (x, y, w, h) clock time.clock() while(True): clock.tick() img sensor.snapshot() # 在ROI内寻找色块 blobs img.find_blobs([line_threshold], roiroi, pixels_threshold50, area_threshold50, mergeTrue) if blobs: # 找到最大的色块认为是引导线 largest_blob max(blobs, keylambda b: b.pixels()) # 计算色块的中心点x坐标 line_center_x largest_blob.cx() # 计算与图像中心160的偏移量 offset line_center_x - 160 # 计算色块的角度通过矩形的旋转角度简单估算 rotation largest_blob.rotation_deg() # 打包数据通过串口发送 # ... 串口发送代码 ...关键调参心得颜色空间选择RGB色彩空间对光照极其敏感。强烈推荐使用LAB颜色空间。L通道代表亮度A和B通道代表颜色。在LAB空间下我们可以把亮度L的范围放宽甚至不限制如0-100而严格限定颜色范围A和B。这样无论光线强弱亮度L变化只要颜色A,B在范围内就能识别。这是我们抗光照变化的“法宝”。ROI的妙用不需要处理整幅图像。小车在行进时引导线总是在图像下方区域。设置ROI能大幅减少运算量提升帧率。更高的帧率意味着控制器能更及时地响应线路变化。阈值动态调整进阶我们为比赛准备了一个“阈值学习”功能。在小车启动前将其置于赛道上按下按键OpenMV会自动采集当前画面中指定区域的色彩值计算出一个临时的阈值范围。这能快速适应不同赛场的底色和灯光条件。“虚化”处理应对反光有些赛道反光严重会形成破碎的、亮白色的高光块容易被误识别。我们在find_blobs前对图像进行了轻微的高斯模糊(img.gaussian_blur(1))可以有效平滑这些噪声点让真正的色块更连续。3.2 基于K210与深度学习的目标识别流程K210上的识别流程是一个标准的生产-部署 pipeline。模型训练在PC端使用TensorFlow收集大量病房号、床号图片自己拍摄并做数据增强旋转、缩放、调整亮度对比度、添加噪声训练一个简单的CNN模型如MobileNetV2的剪裁版或自建的小网络。准确率在测试集上达到99%以上即可模型不宜过大。模型转换使用NNCase工具将TensorFlow Lite或Keras模型转换成K210支持的.kmodel格式。这里要注意输入输出的维度匹配。K210端侧部署# K210 (MaixPy) 识别核心代码片段 import sensor, image, lcd, time from maix import KPU # 初始化摄像头和LCD sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.run(1) lcd.init() # 加载KPU模型 kpu KPU() kpu.load_kmodel(0x300000, /sd/num_model.kmodel) # 模型烧录在SD卡或Flash kpu.init_yolo2(...) # 如果是目标检测模型需要初始化锚点参数 clock time.clock() while True: clock.tick() img sensor.snapshot() # 图像预处理缩放到模型输入尺寸例如224x224 img_processed img.resize(224, 224) img_processed.pix_to_ai() # 运行模型推理 kpu.run_with_output(img_processed) # 获取输出结果 dect kpu.regionlayer_yolo2(...) if dect: for obj in dect: # 获取识别结果、置信度、坐标 class_id obj.classid confidence obj.confidence x, y, w, h obj.x, obj.y, obj.w, obj.h # 将坐标映射回原图QVGA尺寸 # ... # 通过串口将识别结果发送给STM32 # uart.write([0xAA, class_id, ...]) lcd.display(img)避坑指南模型输入尺寸训练时图片尺寸和K210推理时img.resize的尺寸必须完全一致。内存管理K210内存有限大模型或高分辨率图像容易导致内存不足。务必使用kpu.memtest()检查内存使用情况。我们的策略是使用两个小模型一个用于房间号一个用于床号按需加载而不是一个庞大的多任务模型。图像预处理对齐PC端训练时的预处理归一化、均值方差调整必须与K210端的预处理完全一致否则识别率会骤降。置信度阈值设置一个合理的置信度阈值如0.7低于此阈值的结果视为无效避免误识别干扰决策。这个阈值需要在真实场景中反复测试确定。3.3 STM32端的运动控制与多传感器融合STM32负责将视觉和传感器的信息转化为精准的电机动作。核心是分层控制思想。底层电机双闭环PID控制我们为每个驱动轮配备了增量式编码器实现了速度-位置双闭环控制。内环电流环由电机驱动芯片如TB6612本身完成响应最快。外环速度环以编码器反馈的实际速度作为输入与目标速度比较通过PID计算输出PWM占空比。这是保证小车匀速行驶的关键即使负载变化、电池电压下降也能维持设定速度。位置环在需要精确移动固定距离时如进出房间、对准病床以外环输出的“速度”作为内环以编码器累计的脉冲数作为位置反馈构成位置PID。用于厘米级的精确定位。中层循迹控制算法我们采用了模糊PID与状态机结合的策略。偏差处理从OpenMV获取的横向偏移量offset输入到一个模糊控制器中。模糊控制器将offset和offset的变化率作为输入输出PID参数的调整量如Kp, Kd。这样当偏差大时控制力度强当偏差小时控制柔和避免在中心线附近振荡。转向控制转向角度并非直接与offset成正比。我们引入了一个前馈量即OpenMV提供的线条rotation角度。如果线条本身是弯的小车需要提前打方向。最终的转向电机控制量 PID(offset) Kf * rotation。状态机管理STM32维护一个行驶状态机直线、左转、右转、十字路口、停车等。例如当OpenMV报告“十字路口”且里程计到位状态机切换到“路口处理”状态此时循迹PID暂时失效小车根据任务要求执行直行、左转或右转的固定动作序列结合陀螺仪积分角度确保转弯精确度为90度±1度。上层任务调度与决策在FreeRTOS中有一个高优先级的“决策任务”。它根据读取到的K210识别结果房间号A3、床号2、自身状态当前所在房间、已配送药品以及预设的地图信息决定下一个目标点并下发速度、转向指令给中层控制器。这里的关键是超时与重试机制。例如命令K210识别房间号如果2秒内未收到有效结果则决策任务会命令小车轻微前后移动或摇头通过云台转动K210再次尝试识别最多重试3次。这种机制有效应对了临时遮挡或识别失败。4. 机械结构与调试中的“血泪”经验电路和代码固然重要但小车的机械结构是这一切的基础。很多队伍算法写得很好却败在了机械不稳定上。底盘与重心我们采用了四轮差速结构两个主动轮后轮独立驱动两个万向轮前轮支撑。主动轮必须使用带编码器的减速电机并且两个电机的型号、减速比必须严格一致否则即使PID参数相同两轮速度也会有细微差异导致跑偏。电池等重物应尽量放置在底盘中心偏低的位置降低重心防止急停急启时车身晃动过大影响图像采集。摄像头安装这是最大的坑点之一。OpenMV和K210的摄像头必须紧固不能有任何松动。我们最初用扎带固定跑一段时间后摄像头会因为振动轻微下沉或转动导致寻迹视野变化或识别区域错位。后来改用3D打印的刚性支架并用螺丝锁死问题才解决。此外摄像头镜头平面要与地面平行用于巡线或与目标平面垂直用于识别安装时需要用水平仪仔细校准。供电系统电机驱动、主控、舵机、视觉模块必须独立供电或使用大电流DCDC模块隔离。电机启停时会产生巨大的电流尖峰和电压跌落如果共用一个线性稳压器如LM2596会导致主控或视觉模块瞬间复位。我们的方案是大容量锂电池12V直接给电机驱动供电通过一个大的DCDC降压模块12V转5V/3A给主控、编码器、传感器等供电视觉模块单独使用另一个小电流的5V稳压模块供电。并在各电源入口处加了大容量电解电容1000uF和多个104瓷片电容进行滤波。布线艺术所有线缆必须捆扎整齐远离车轮和传动部件。特别是编码器的线信号容易被电机产生的电磁干扰影响我们使用了带屏蔽层的双绞线并将屏蔽层单点接地。串口通信线也尽量短并远离电源线。5. 赛前调试与赛场应变策略电赛不是实验室里的完美实验赛场上的突发状况才是真正的考验。系统性调试流程单元测试先让每个模块独立工作。例如单独测试OpenMV巡线在电脑上观察识别框是否稳定单独测试K210识别用固定图片验证准确率单独测试STM32电机PID用手挡住轮子看能否保持力度。集成联调模块两两结合。先让STM32接收OpenMV数据控制小车巡线不加入识别。巡线稳定后再加入K210识别但识别成功后只打印日志不执行动作。最后才将完整的决策逻辑加入。压力测试在赛道上以最高速度通常不是最快而是稳定与速度的平衡点连续跑20圈观察是否有死机、数据异常、机械松动等问题。用手电筒模拟现场可能出现的侧光、顶光干扰。赛场“急救包”多种颜色阈值配置文件针对强光、弱光、暖光、冷光等不同环境我们预存了多组LAB阈值参数通过按键可以快速切换。软件“慢速模式”比赛时紧张容易出错。我们设置了一个按键按下后小车进入“慢速演示模式”所有动作速度减半方便评委观察也降低了意外撞车的风险。丰富的状态指示利用STM32的LED和蜂鸣器定义不同的闪烁模式和响声代表不同的状态如“寻线中”、“识别中”、“出错”。当系统出现异常时能第一时间通过声光提示而不是傻站着方便快速定位问题。参数现场微调接口我们留出了一个蓝牙串口连接到手机APP用串口调试助手类APP即可。在比赛开始前的准备时间如果发现场地光线与预期不符可以通过手机快速微调OpenMV的色块面积阈值、K210的识别置信度阈值等关键参数而无需重新烧录程序。应对规则细节F题规则往往对药品装载、交付动作有明确要求如必须模拟机械臂抓取、必须亮灯提示等。这些看似简单的“动作分”反而是很多高手翻车的地方。我们仔细研读规则将这些动作设计成独立、可靠的函数并在调试时反复验证其触发条件是否严格符合规则描述。例如“到达病床”的判定我们结合了里程计、视觉识别结果和超声波测距检测到前方近距离有障碍物三个条件确保万无一失。回顾整个项目从最初的方案论证到最后的赛场稳定运行最大的体会是电赛比拼的不是单一技术的深度而是对“系统工程”的理解和掌控能力。如何在有限的时间、预算和算力下做出最稳健、最抗干扰的设计如何通过软硬件的协同与冗余来应对不确定性这些思考远比学会某个芯片或某个算法更有价值。我们的双视觉方案或许不是最简洁的但在那个充满变数的赛场上它给了我们最终夺冠所需的、最宝贵的“稳定性”。
返回列表