简介:这是一套基于STM32F103平台实现的智能分拣小车完整嵌入式项目,面向计算机、自动化、电子信息等专业的本科生,专为毕业设计、课程设计及期末大作业打造。项目集成颜色识别(HSV阈值+RGB传感器协同判断)、红外/摄像头循迹、四自由度机械臂运动学解算与精准抓取三大核心功能,代码经导师验收并获99分高分评价,小白可直接编译下载运行。压缩包共28个文件,含11个头文件(.h)定义外设驱动与算法接口、10个源文件(.c)实现主控逻辑、运动控制、串口通信、定时器调度及机械臂正逆解等关键模块,辅以Keil工程文件(.uvprojx/.uvoptx)、启动配置与README说明文档,整体仅145KB,轻量易上手。目前已有203人学习下载,资源结构清晰、注释充分,提供从硬件初始化、传感器数据处理到多任务协调执行的全链路参考,是嵌入式实践与综合能力提升的优质实战范例。
1. 项目概述:一个嵌入式工程师的“全能小车”实战
最近在整理过去的项目资料,翻到了这个基于STM32的智能分拣小车。这算是我早期嵌入式开发中一个比较综合的练手项目,麻雀虽小,五脏俱全,集成了颜色识别、循迹、机械臂抓取等多个功能模块。它本质上是一个移动的、具备简单视觉和抓取能力的自动化平台,可以模拟工厂流水线上的分拣环节,或者作为一个智能搬运的演示原型。对于想从点灯、按键跳到综合系统开发的STM32学习者来说,这个项目能帮你把GPIO、定时器、PWM、ADC、串口通信、传感器驱动、电机控制、简单的图像处理算法等知识点串起来,形成一个完整的认知闭环。
我当时做这个项目的初衷,就是想验证一下能否用一块主控芯片(STM32F103C8T6,也就是常说的“蓝桥杯”或“最小系统板”核心)来协调完成相对复杂的多任务。结果证明,通过合理的软件架构设计和外设资源分配,STM32完全有能力胜任。整个项目涉及硬件选型、电路设计、底层驱动编写、传感器数据处理、控制算法实现以及多任务调度,是一个典型的嵌入式系统开发全流程实践。无论你是电子相关专业的学生在做课程设计、毕业设计,还是刚入行的工程师想提升综合能力,这个项目都能提供不少有价值的参考。
2. 核心功能模块与硬件选型解析
这个小车的功能可以拆解为三个核心子系统:感知系统(颜色识别+循迹)、决策控制系统(STM32主控)和执行系统(小车底盘驱动+机械臂)。硬件选型直接决定了项目的成本、复杂度和最终效果。
2.1 主控芯片:为什么是STM32F103?
项目核心是STM32F103C8T6,一款经典的Cortex-M3内核MCU。选择它基于几个现实考量:
- 资源与性价比:72MHz主频,64KB Flash,20KB RAM,对于这个多任务项目来说内存稍显紧张,但通过优化可以应对。它拥有丰富的定时器、ADC、USART、I2C、SPI接口,正好匹配我们所需的多传感器和外设。最关键的是,它价格低廉,资料极多,社区支持好,试错成本低。
- 开发生态:无论是标准外设库(Standard Peripheral Library)还是后来主流的HAL库,都有完善的支持。使用STM32CubeMX进行图形化初始化配置,能极大提升开发效率,尤其是配置多个外设的时钟和引脚时,不易出错。
- 实时性需求:小车需要实时响应传感器数据(如循迹信号)并控制电机,Cortex-M3内核和中断机制能满足基本的实时控制要求。对于更复杂的任务调度,可以移植轻量级的RTOS(如FreeRTOS),我在项目后期也确实引入了它来管理颜色识别和运动控制任务。
注意:如果预算更充足或对图像处理速度有更高要求,可以考虑性能更强的F4系列(如STM32F407,带FPU)或H7系列。但对于学习和大多数演示场景,F103完全足够。
2.2 感知模块:眼睛与触须
- 颜色识别传感器:我选用的是TCS3200颜色传感器模块。它内部集成了光电二极管阵列和电流-频率转换器,直接输出与RGB分量成比例的方波频率信号。
- 工作原理:STM32通过两个GPIO控制其S2、S3引脚来选择滤波模式(红、绿、蓝、清除)。另一个GPIO(配置为输入捕获模式)测量其OUT引脚输出的方波频率。通过测量在R、G、B三种模式下的频率值,经过白平衡校准后,即可判断当前物体的颜色。
- 选型理由:相比需要复杂I2C通信的某些颜色传感器,TCS3200的接口简单(仅需数字IO和定时器捕获),编程容易,且成本低。缺点是精度和抗环境光干扰能力一般,需要配合遮光罩使用。
- 循迹传感器:采用常见的五路红外循迹模块。这是项目的“触须”。
- 工作原理:每个模块包含一个红外发射管和一个接收管。发射管发出红外光,地面(白色)反射强,接收管导通,输出低电平;地面(黑色)吸收红外光,反射弱,接收管截止,输出高电平。STM32通过读取五个GPIO的电平状态,即可判断小车相对于黑色引导线的位置。
- 五路的优势:相比三路,五路能提供更精确的位置信息(如“偏左一点点”和“偏左很多”),便于实现更平滑的PID控制,减少小车在循迹过程中的“画龙”现象。
2.3 执行模块:手脚并用的实现
- 小车底盘与驱动:采用两轮差速驱动方式,这是最灵活、控制算法最成熟的移动平台方案。
- 电机:N20减速直流电机,体积小,扭矩适中,带编码器反馈(可选,用于更精确的速度闭环控制)。
- 驱动芯片:TB6612FNG双路电机驱动模块。它比经典的L298N效率更高,发热更小,支持PWM调速和正反转控制。STM32通过三路PWM输出(两路用于速度,两路GPIO用于方向)即可控制两个电机。
- 机械臂:为了控制成本,我使用了一个三自由度(3-DOF)的舵机机械臂套件。
- 舵机控制:舵机通过PWM信号控制角度。STM32的定时器可以轻松产生多路独立的PWM信号。关键点在于,要规划好机械臂的运动轨迹(如从待机位置移动到目标物上方、下降、夹取、抬起、旋转、放置),避免舵机之间发生干涉,并且动作要平滑,防止负载突变导致舵机堵转。
- 夹取机构:通常是一个由舵机驱动的夹爪。需要根据待分拣物体的重量和尺寸,设计或选购合适夹爪,并调整夹持力(通过PWM脉宽微调),既要抓得稳,又不能损坏物体。
2.4 其他关键硬件
- 电源管理:这是项目稳定的基石。电机和舵机启动瞬间电流很大,会导致电压骤降,可能使STM32复位。我的方案是使用两路独立供电:一节7.4V锂电池通过降压模块(如LM2596)降为5V给STM32、传感器供电;同时该锂电池直接(或通过另一个驱动电路)给电机和舵机供电。务必在电源入口处加足够容量的电解电容(如470uF~1000uF)进行缓冲。
- 调试与交互:预留了串口(USART1)连接USB转TTL模块,用于程序调试(printf重定向)和上位机通信。另外,可以添加一个OLED显示屏(I2C接口),实时显示小车状态、识别到的颜色、传感器数据等,方便现场调试。
3. 系统软件架构与多任务调度设计
当硬件堆叠起来后,如何让它们有序、协同地工作,就是软件架构的任务了。初期我使用“超级循环”(Super Loop)配合中断,后期引入了FreeRTOS,体验了两种设计模式的差异。
3.1 基于“超级循环+中断”的裸机架构
这是最直接的起步方式。主函数里一个while(1)循环,不断查询或处理各个任务。
int main(void) { // 硬件初始化 System_Init(); // 时钟、GPIO、定时器、PWM、ADC、串口等 Motor_Init(); Sensor_Init(); Servo_Init(); while(1) { // 1. 循迹传感器状态采集与处理 Track_Process(); // 2. 电机PID控制计算与输出 Motor_PID_Control(); // 3. 颜色识别(需要时触发) if (need_color_detect) { Color_Detect_Process(); } // 4. 机械臂控制状态机 Arm_StateMachine(); // 5. 其他任务,如按键扫描、OLED显示更新 Key_Scan(); OLED_Refresh(); // 这里可能需要一些延时,防止循环过快 Delay_ms(10); } }- 优点:简单直观,不需要额外的系统开销,对于任务量不大、实时性要求不苛刻的场景够用。
- 缺点:
- 实时性差:如果
Color_Detect_Process()或Arm_StateMachine()某个函数执行时间较长,会阻塞整个循环,导致循迹控制不及时,小车可能冲出轨道。 - 任务调度不灵活:所有任务优先级相同,无法紧急处理某个事件。
- 代码耦合度高:随着功能增加,
while(1)循环会越来越臃肿,难以维护。
- 实时性差:如果
3.2 引入FreeRTOS进行任务重构
为了解决上述问题,我决定引入FreeRTOS。它将不同的功能模块封装成独立的任务(Task),由内核进行调度。
任务划分:
- 循迹控制任务(
Track_Task):优先级最高。它需要以固定频率(如10ms)运行,及时读取五路红外传感器数据,计算偏差,执行PID算法,并更新电机PWM占空比。这是一个硬实时任务。 - 颜色识别任务(
Color_Task):优先级中。当小车行驶到分拣区时,由循迹任务或一个专门的“导航状态机”触发。它控制TCS3200进行颜色测量,将结果存入共享变量或发送到消息队列。 - 机械臂控制任务(
Arm_Task):优先级中。它接收来自颜色识别任务的消息(何种颜色),执行预设的抓取、移动、放置动作序列。动作序列本身可以用一个状态机来实现,任务中调用vTaskDelay()来产生动作间隔。 - 系统监控与通信任务(
Sys_Task):优先级最低。负责OLED显示更新、串口调试信息发送、电池电压检测等非实时性工作。
关键实现细节:
- 任务间通信:颜色识别任务和机械臂任务之间使用FreeRTOS的队列(Queue)传递颜色指令。避免了全局变量访问的冲突。
- 资源互斥:多个任务可能都需要使用串口打印调试信息。这里使用互斥信号量(Mutex)来保护串口资源,防止打印内容交错。
- 定时器应用:循迹任务的固定频率,可以通过FreeRTOS的
vTaskDelayUntil()函数实现精确的周期性延迟,这比简单的vTaskDelay()更准确,能保证控制周期的稳定性。
实操心得:从裸机切换到RTOS,初期会有点绕,但一旦理清任务划分和通信机制,整个系统的结构会清晰很多。尤其对于这种多事件并发的系统,RTOS的优势非常明显。记得要给每个任务分配合适的栈空间,并利用FreeRTOS提供的钩子函数或工具(如
uxTaskGetStackHighWaterMark)监控栈使用情况,防止栈溢出。
4. 核心算法与功能实现细节
有了硬件和软件框架,接下来就是填充各个功能模块的核心算法。
4.1 五路循迹与PID速度控制
这是小车平稳运行的基础。五路传感器通常安装在小车前端,排列成一排。
- 位置偏差计算:给每一路传感器赋予一个位置权重值,例如从左到右为:-2, -1, 0, +1, +2。当某一路检测到黑线(输出高电平)时,就将其权重值计入总和。最终的位置偏差
error可以用加权平均值来计算。例如,只有最左侧传感器检测到黑线,偏差就是-2;只有中间传感器检测到,偏差就是0;如果左边两个传感器都检测到,偏差可能是-1.5。这种计算方式比简单的“左偏右偏”判断要精细得多。 - PID控制器设计:采用增量式PID控制电机的转向。偏差
error作为输入,输出是左右轮的速度差(或直接是转向PWM增量)。- P(比例):快速响应偏差,值太大会振荡,太小则响应慢。
- I(积分):消除静态误差(如长时间保持沿直线走),但在这个动态系统中,积分项要非常小,否则容易积分饱和导致控制失控。
- D(微分):预测偏差变化趋势,抑制振荡,提高稳定性。
- 参数整定:这是我调试时间最长的部分。我的经验是“先P,再D,最后微调I”。先将I和D设为0,逐渐增大P直到小车沿直线走但开始出现轻微振荡;然后加入D,抑制振荡;最后加入很小的I。整个过程需要在真实赛道上反复测试。
- 速度控制:除了转向,还要控制小车的基础速度。在直道加速,弯道减速。可以建立一个速度-偏差的映射表,或者使用另一个PID控制器,将偏差的绝对值作为输入,输出基础PWM占空比。
4.2 TCS3200颜色识别与校准
颜色识别的准确性直接决定分拣的正确率。
- 频率测量:将STM32的一个定时器配置为输入捕获模式,连接到TCS3200的OUT引脚。在捕获中断中,计算固定时间内(例如100ms)的脉冲数,即可得到频率。分别测量在红色滤波器、绿色滤波器、蓝色滤波器模式下的频率值,记为
Freq_R,Freq_G,Freq_B。 - 白平衡校准:这是关键步骤!传感器对不同颜色的敏感度不同,且环境光影响巨大。校准方法:
- 在均匀且充足的环境光下,将传感器对准一张纯白色的标准白纸。
- 分别读取
Freq_R_raw,Freq_G_raw,Freq_B_raw。 - 计算校准系数。通常让最大的频率值对应255(RGB最大值),其他颜色按比例缩放。例如,假设
Freq_G_raw最大:scale_R = 255.0 / Freq_R_raw * (Freq_G_raw / 255.0);(原理是归一化) - 实际测量物体时,将读取的原始频率乘以对应的校准系数,得到归一化的RGB值。
- 颜色判断:得到校准后的RGB值后,如何判断是什么颜色?有两种方法:
- 阈值法:最简单。例如,判断红色:如果 R > G1.5 且 R > B1.5,且R值大于某个阈值,则认为是红色。需要为每种颜色定义一组阈值规则。调试麻烦,但速度快。
- 色差法(欧氏距离):在RGB色彩空间中,预先定义好标准颜色的坐标(如纯红[255,0,0],纯绿[0,255,0])。计算当前颜色RGB向量与每个标准颜色向量的欧氏距离,距离最小的即判定为该颜色。更科学,但计算量稍大。
- 我的选择:为了在STM32F103上快速运行,我采用了改进的阈值法,并结合了HSV色彩空间的H(色调)分量进行辅助判断,因为色调对光照强度变化不那么敏感。可以通过公式将RGB转换到HSV,虽然增加了计算,但识别鲁棒性提高了不少。
4.3 机械臂抓取的动作规划与舵机控制
机械臂控制不是简单地给几个角度,而要规划一条安全、高效的路径。
- 动作序列设计:一个完整的分拣动作可以分解为以下状态:
ARM_HOME:待机位置,不妨碍小车移动。ARM_MOVE_ABOVE:移动到目标物体正上方(需要结合小车停止的位置来推算)。ARM_DOWN:下降至抓取高度。ARM_GRAB:闭合夹爪。ARM_LIFT:抬起物体。ARM_ROTATE_TO_DROP:旋转至投放区方向。ARM_MOVE_TO_DROP:移动至投放点上方。ARM_DROP:松开夹爪。ARM_BACK_HOME:返回待机位置。
- 舵机平滑控制:直接让舵机从角度A跳到角度B,会导致机械臂抖动、噪音大,甚至损坏齿轮。需要使用“软移动”算法,例如线性插值。
// 伪代码:平滑移动舵机 void servo_smooth_move(int servo_id, int target_angle, int time_ms) { int start_angle = current_angle[servo_id]; int steps = time_ms / 20; // 假设每20ms更新一次 for(int i=1; i<=steps; i++) { int angle = start_angle + (target_angle - start_angle) * i / steps; set_servo_angle(servo_id, angle); vTaskDelay(20 / portTICK_PERIOD_MS); // FreeRTOS延时 } current_angle[servo_id] = target_angle; } - 多舵机协同:在
ARM_MOVE_ABOVE等动作中,可能需要2个或3个舵机同时运动。此时需要为每个舵机构建一个角度数组(轨迹),然后在控制循环中同时更新所有舵机的角度。使用RTOS的任务或定时器中断可以方便地实现这种多轴协同控制。
5. 系统集成、调试与问题排查实录
将所有模块整合在一起并调通,是最考验耐心和工程能力的阶段。这里记录几个关键问题和解决方法。
5.1 电源噪声导致传感器误触发
现象:小车电机一启动,循迹传感器读数就乱跳,颜色传感器数据波动巨大。排查:
- 首先用万用表测量给STM32和传感器供电的5V电压。在电机启动瞬间,电压被拉低至4.3V左右,并伴有毛刺。
- 用示波器观察TCS3200的OUT引脚波形,在电机动作时,原本稳定的方波上叠加了高频噪声。解决:
- 物理隔离:严格按照前述方案,将电机/舵机电源与数字电路电源在物理上分开(两节电池或使用带隔离的DC-DC模块)。
- 加强滤波:
- 在5V电源入口处并联一个100uF电解电容和一个0.1uF陶瓷电容,分别滤除低频和高频噪声。
- 在每个红外循迹模块的VCC和GND之间,就近焊接一个0.1uF电容。
- 在TCS3200模块的电源引脚处也添加滤波电容。
- 软件滤波:对于循迹传感器,采用“多次采样取众数”或“中值滤波”算法。例如,连续读取5次,取出现次数最多的状态作为最终值,能有效滤除单次尖峰干扰。
5.2 机械臂动作导致小车位置偏移
现象:机械臂在抓取或移动物体时,由于重心变化和舵机反作用力,导致小车底盘发生轻微转动或平移,等机械臂执行完,小车已经偏离了原来的循迹位置。解决:
- 机械加固:尽量降低机械臂和负载的整体重心,并将机械臂底座牢固地安装在小车底盘中心位置。
- 软件补偿:在机械臂开始执行动作序列前,让小车完全停止(电机PWM输出为零,甚至使能刹车功能)。等待机械臂动作完成后,再重新启动循迹任务。虽然损失了一点时间,但保证了定位准确性。更高级的做法是,在机械臂动作时,循迹控制任务依然运行,但只进行非常缓慢的“微调”来抵抗扰动,这需要更精确的动力学模型和更复杂的控制算法。
5.3 颜色识别受环境光影响大
现象:在实验室灯光下校准的颜色传感器,拿到窗边自然光下,识别就完全错误。解决:
- 硬件改善:为TCS3200设计一个遮光罩(用黑色热缩管或3D打印一个筒状结构),只让正下方的反射光进入,极大减少了环境侧光的干扰。
- 动态参考法:放弃绝对的“白平衡”校准。在每次进行分拣前,先让传感器读取一下“背景色”(即分拣区台面的颜色),将此作为参考基准。然后识别物体时,计算物体颜色与背景色的相对差值。这种方法对均匀变化的环境光有一定适应性。
- 采用更稳定的色彩空间:如前所述,将RGB转换到HSV,主要使用H(色调)分量进行判断。色调对光照强度的变化相对不敏感。虽然增加了计算量,但对于STM32F103来说,一次转换的计算负担是可以接受的。
5.4 FreeRTOS任务栈溢出
现象:程序运行一段时间后,发生HardFault死机。排查:使用FreeRTOS的uxTaskGetStackHighWaterMark()函数检查每个任务的任务栈高水位线。发现Color_Task的高水位线值非常接近分配的栈大小。解决:增加Color_Task的栈分配大小(例如从128字增加到256字)。同时优化该任务中的函数,减少大型局部数组的定义,将一些常量数据移到全局区或使用static定义。
6. 项目优化与扩展方向思考
完成基础功能后,可以从多个方向对这个项目进行深化和扩展,使其更智能、更实用。
6.1 感知升级:从颜色到形状与二维码
- 升级视觉传感器:将TCS3200替换为OV7670等摄像头模块,结合STM32的DCMI接口和有限的图像处理能力(或使用专用的图像处理协处理器如K210),可以实现形状识别、物体定位甚至简单的二维码/条形码识别,从而进行更复杂的分拣。
- 增加距离传感器:在机械臂末端或小车前方加装超声波传感器或TOF激光测距模块(如VL53L0X),用于精确测距,辅助机械臂抓取和避障。
6.2 控制升级:更精确的定位与导航
- 编码器反馈闭环:为驱动电机加上编码器,实现精确的速度闭环和里程计计算。结合PID控制器,可以让小车速度控制更平稳,并能实现粗略的定位(走了多远,转了多少度)。
- 惯性导航辅助:加入MPU6050(六轴陀螺仪加速度计)模块,通过传感器融合算法(如互补滤波、卡尔曼滤波),估算小车的姿态角和位置变化,弥补轮子打滑造成的里程计误差,实现更鲁棒的位姿估计。
6.3 通信与组网:从单机到系统
- 无线遥控与监控:增加蓝牙模块(如HC-05)或Wi-Fi模块(如ESP8266),通过手机APP或电脑上位机进行遥控,并实时接收小车传回的传感器数据、图像信息等。
- 多车协同:如果构建多个智能小车,可以尝试通过ZigBee、LoRa或Wi-Fi组成简单网络,研究多车路径规划、任务分配等课题,向真正的“智能仓储”系统迈进。
6.4 软件架构升级
- 状态机框架:将整个小车的运行逻辑(寻线、到达分拣区、识别、抓取、投放、返回)用一个清晰的状态机(如
switch-case或专门的状态机库)来管理,比散落在各个任务中的if-else逻辑更清晰,易于调试和维护。 - 参数可配置化:将PID参数、颜色阈值、机械臂动作角度等所有可调参数,存储在STM32的Flash中(模拟EEPROM)。通过串口命令或上位机可以动态修改并保存这些参数,无需重新烧录程序,极大方便现场调试。
这个基于STM32的智能分拣小车项目,就像一块嵌入式开发的“试金石”。它逼着你去面对和解决电源完整性、信号干扰、实时调度、传感器融合、运动控制等一系列实际问题。过程中踩过的每一个坑,调试通过的每一个模块,都会让你对嵌入式系统的理解加深一层。最终当小车稳稳地沿着黑线前进,准确地识别并抓取起彩色方块时,那种成就感是单纯点亮一个LED无法比拟的。它不仅仅是一个作品,更是一份涵盖了硬件、软件、算法、调试的完整工程实践笔记。
本文还有配套的精品资源,点击获取