多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32黑线循迹小车实战:L293D驱动与PWM差速控制

STM32黑线循迹小车实战:L293D驱动与PWM差速控制 简介针对STM32F103C8T6智能小车黑线循迹运动实验这是一份可直接编译运行的完整工程源代码适合正在学习STM32嵌入式开发或智能车循迹控制的初学者参考。程序基于Keil4开发处理器为STM32F103C8T6小车端采用L293D电机驱动芯片与TT直流减速电机配合红外循迹模块完成黑线识别与差速纠偏已由作者在自用小车上亲测可用。压缩包共44个文件、约356KB包含8个h头文件、3个c源文件以及hex烧录文件、uvproj工程文件另附txt程序说明文档和L293D电机驱动电路jpg图片工程内SYSTEM、OBJ、USER等目录划分明确既能直接下载烧录也方便在Keil4中二次修改。当前已有1791人学习下载对于正在做智能小车课程设计、电子竞赛备赛或初次接触STM32电机控制的同学来说这份源码能帮助快速跑通黑线循迹功能并理解L293D驱动、TT电机调速与循迹传感器配合的完整实现思路从代码到硬件接线都有对应材料整体循环逻辑清晰也适合在工程基础上继续扩展其他智能功能。1. 黑线循迹小车还是那个熟悉但常翻车的入门项目我拆过几十块STM32F103C8T6最小系统板但每次有人拿STM32F103C8T6智能小车黑线循迹运动实验程序源代码来问我都要先问一句白底黑线还是黑底白线这决定了红外模块的输出极性判断也决定了你后面是改代码还是改硬件。这套源码是STM32标准外设库风格的工程目标芯片STM32F103C8T6驱动芯片L293D电机是TT直流减速电机配合至少两个红外循迹模块做双路检测。开发环境锁定Keil4压缩包里放了SYSTEM、OBJ、USER三个目录、L293D电机驱动电路.jpg和程序说明必看.txt是典型的学校实验室出品但工程可复用性比网上的零散例程高不少。它适合谁刚学会GPIO点灯、想跑通第一个闭环运动控制的人也适合为工创赛智能物流小车练手先把黑线循迹吃透的人。真正决定小车跑得好不好的往往不是代码量而是供电和阈值。2. 硬件选型与接线L293D和红外循迹模块的坑先排干净2.1 引脚分配与GPIO输入模式循迹模块TCRT5000常见数字输出白底输出高电平黑线输出低电平。但有一些套件的模块是反逻辑所以第一步不是连IO而是把模块放白纸上用万用表量输出脚。如果是0V则说明输出低有效后面代码必须反转。原源码用的双路检测通常占用两个普通GPIO我习惯用PB0左、PB1右这样能把PA9/PA10的串口留出来做调试输出。GPIO初始化如下// 双路循迹传感器输入初始化 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入适配开漏输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure);这段代码把PB0和PB1配置成上拉输入。GPIO_Mode_IPU之所以重要是因为TCRT5000模块的数字输出通常是三极管开漏结构内部集电极开路必须有外部上拉才能把高电平拉到稳定电位。GPIO_Speed对输入模式其实没有影响但保留它可以让后续把引脚复用成TIM功能时少一次配置遗漏。如果模块输出是推挽结构上拉输入也兼容改成浮空输入也可以但抗干扰差一些。2.2 L293D驱动电路与电源接法L293D是双电源工作的四通道半桥驱动VCC1是逻辑电源接5VVCC2是电机电源接电池正极。源码里如果只给一个电源电机一启动STM32会反复复位这是黑线循迹实验里最常见的问题。实验包的L293D电机驱动电路.jpg里一般画得很清楚VCC2与GND之间要有大电容VCC1和VCC2需要共地并且VCC2的电压不能超过36V实际TT电机建议不超过12V。接线参考表信号L293D引脚STM32引脚左电机PWM使能EN1PB5左电机方向1IN1PB6左电机方向2IN2PB7右电机PWM使能EN2PB8右电机方向1IN3PB9右电机方向2IN4PB10左循迹模块输出OUT_LPB0右循迹模块输出OUT_RPB1注意上面是常用引脚分配实际工程以main.c顶部宏为准。L293D用IN1/IN2的电平组合控制电机正反转EN1用PWM控制速度。TT直流减速电机额定电压一般是6V如果电池是7.4V锂电池PWM占空比建议限制在70%左右否则L293D里的功率管长时间满占空比工作发热会非常快外壳烫手后进入热关断小车突然停摆。提示L293D逻辑电源和电机电源必须共地不共地时PWM电流回路建立不起来电机只会嗡嗡叫不转。实验电路图上通常还会看到VCC2引脚对地的电解电容和VCC1对地的陶瓷电容。这两个电容不能省电解电容吸收电机启停时的反向尖峰电流陶瓷电容滤掉逻辑电源的高频噪声。没有它们STM32在电机急停时容易检测到电源跌落而进入硬件错误中断。2.3 红外阈值标定比代码更关键的环节黑线循迹失败七成原因是模块电位器没调好不是代码问题。把小车放在白色地面模块距地面1到2厘米用一字螺丝刀慢慢旋电位器让指示灯刚好熄灭或刚好点亮然后用黑色胶带挡在模块下方确认指示灯状态反转。这个过程要重复三次每次都从偏移大的方向旋回来消除电位器机械回差。如果模块使用的是可调电位器旋向不一致时最笨但有效的方法是白底调灯亮、黑线调灯灭把检测点卡在黑白交界处。你也可以在代码里加数字滤波同一引脚连续读三次三次一致才认为状态有效。这个消抖对双路循迹很重要因为黑白交界处传感器输出会在几百微秒内跳变直接导致小车沿锯齿路径跑。传感器安装高度也同样关键。距离地面太近模块外壳容易刮到跑道上的不平处电位器产生机械偏移太高黑色胶带的反光不足以触发翻转。我一般用铜柱把模块固定在小车底板离地约1.5厘米固定螺丝用尼龙材质而不是金属因为金属螺丝的反光会把传感器状态拉回高电平造成误判。3. 从库函数到主循环GPIO读入与L293D差速转向3.1 压缩包目录与程序说明文件解压后根目录有SYSTEM、OBJ、USER三个文件夹外加程序说明必看.txt和L293D电机驱动电路.jpg。SYSTEM里是delay、sys、usart三个基础模块提供延时和串口重定向OBJ是编译输出目录默认把生成的.hex放这里USER里的main.c、stm32f10x_it.c才是逻辑主体。先读程序说明.txt里面写了目标芯片、驱动芯片、电池电压等关键参数避免拿51的思路来改STM32代码。这套目录结构里有一个容易被忽略的细节USER工程里会定义USE_STDPERIPH_DRIVER宏Keil的魔术棒 - C/C - Preprocessor Symbols里如果没有这个宏标准外设库的dma、gpio头文件会少一部分映射编译报一堆typedef找不到。打开工程第一件事检查这个宏而不是直接点Rebuild。3.2 电机PWM初始化的参数选择主循环控制速度前要先让定时器输出两路PWM。用TIM2的CH1/CH2初始化代码// 电机PWM初始化1kHz周期1ms RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructInit(TIM_TimeBaseInitStructure); TIM_TimeBaseInitStructure.TIM_Prescaler 72 - 1; // 72MHz时钟分频到1MHz TIM_TimeBaseInitStructure.TIM_Period 1000 - 1; // 1MHz/1000 1kHz TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC2Init(TIM2, TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE);这里预分频必须配置成72-1因为STM32F103C8T6默认系统时钟72MHz。TIM_Period设为1000-1得到1kHz的PWM频率。L293D在这个频率下开关损耗和噪声平衡较好如果降到500Hz电机会更响但发热略低升到10kHz占空比分辨率下降且驱动芯片发热明显。TIM_Pulse初始为0防止上电瞬间电机猛冲等主循环里设置好方向引脚后再给PWM。PWM频率的选择还要看定时器的分辨率TIM_Period 1000时占空比从0到999步进足够做60%-70%的精细调节。如果要用PID建议把Period提到5000这样每次调节的最小步进是0.02%避免PID输出震荡时电机一顿一顿的。3.3 双路循迹主循环差速转向状态表循迹核心在主循环里做状态判断。这段逻辑用标准外设库读引脚// 主循环读传感器并计算左右PWM while (1) { uint8_t left GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0); uint8_t right GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1); if (left 0 right 0) // 两传感器都在黑线上 { TIM_SetCompare1(TIM2, 600); // 直行 TIM_SetCompare2(TIM2, 600); drive_forward(); } else if (left 0 right 1) // 左传感器压线车偏右 { TIM_SetCompare1(TIM2, 300); // 左轮降速 TIM_SetCompare2(TIM2, 650); drive_forward(); } else if (left 1 right 0) // 右传感器压线车偏左 { TIM_SetCompare1(TIM2, 650); TIM_SetCompare2(TIM2, 300); drive_forward(); } else // 两传感器都在白底丢了线 { TIM_SetCompare1(TIM2, 350); TIM_SetCompare2(TIM2, 350); spin_in_place(); // 原地旋转找线 } }这段逻辑里最容易被忽略的是“两传感器都压线”的问题窄黑线不可能同时压住两个传感器所以直行状态实际应该对应左右都白。但这又和丢线状态冲突。双路模块的标准做法是左右都白时短暂维持上一次转向方向或者原地旋转。上面代码选择原地旋转适合90°直角弯如果跑大弯建议改成“左转优先”来回扫线。传感器状态与动作对应如下左PB0右PB1小车状态左PWM右PWM00黑白交界纠偏60060001偏右左转30065010偏左右转65030011直线丢失旋转找线350350注意1和0的极性取决于模块是否反逻辑。如果模块黑线输出高就在宏里把LINE_ACTIVE_LEVEL改成1主循环判断改成 LINE_ACTIVE_LEVEL不要逐行改if。这样的宏定义带来的好处是换一套小车底盘时只需要改引脚映射和有效电平不用碰控制算法。还要注意上文drive_forward和spin_in_place两个函数源码里一般会封装成L293D的IN引脚组合。方向引脚和PWM比较值需要分开设置因为PWM更新后方向动作立即生效如果两个轮子方向不一致小车会原地打转而非转向。4. 实战调试Keil4工程编译与循迹抖动参数修正4.1 Keil4工程打开与烧录设置用Keil uVision4打开USER文件夹下的.uvproj工程文件。打开后第一次编译一般会报找不到STLink驱动只有这个原因。在Options for Target - Device确认芯片是STM32F103C8T6Debug里选择ST-Link或J-LinkUtilities里勾选算法文件。如果Keil4装过标准外设库直接编译会生成OBJ下的hex。命令行方式下载也可以STM32_Programmer_CLI -c portSWD modeNRST STM32_Programmer_CLI -w OBJ\\line_follow.hex -v -rst说明这里-c portSWD表示使用SWD四线下载modeNRST是常见复位时序-w写入hex并校验-v对比写入内容-rst下载后复位运行。如果下载器找不到芯片先查BOOT0是不是接地再查L293D逻辑电源是否和STM32共地。Keil4在Windows 10及以上偶尔会有工程文件关联异常。如果双击.uvproj没反应用管理员身份运行Keil再打开如果编译报“Unknown type name u8”这类错误多半是SYSTEM目录的头文件没有加进Include Paths。C8T6是64KB Flash编译出现ROM不足时把调试器里的Download Function勾掉一部分或者把优化级别从Level 0调到Level 2立即能省下不少空间。4.2 画龙、甩尾、冲出赛道的调参表小车在弯道和直道的动态不同不能靠一个占空比调完。建议按照下表顺序操作现象最先检查参数参考直道画龙左右电机空载转速差快侧PWM减50弯道冲出转向差速不够大650/300改成700/250原地旋转太猛丢线PWM过高350降到280电机啸叫PWM频率不合适1kHz降到500Hz电池没电后无力电池电压下降全速阈值升到700调参时只动一个参数改一次跑一圈不要同时动转向差速和直行速度。差速越大转向角速度越大但过大的差速会让车尾甩起来尤其在低速高摩擦地面。直行速度设定在60%左右比较合适也就是占空比600/1000这样给小电池电压波动留了余量。判断左右电机是否一致最简单的办法是把两个轮子悬空调用一个固定PWM输出函数看两轮转速是否一样。快的那一侧在程序里把PWM调低10%。如果两边转速差异超过15%更换电机因为差速控制算法默认两电机特性一致差异大时黑线永远走不直。4.3 用串口输出传感器状态SYSTEM里的usart模块已经把printf重定向好主循环里加一条printf(L:%d R:%d PWM1:%d PWM2:%d\r\n, (int)left, (int)right, (int)last_pwm1, (int)last_pwm2);注意printf重定向到串口需要勾选Keil的Use MicroLIB否则浮点支持会占大量FlashC8T6只有64KB很容易超。调试时串口波特率通常设置为115200PA9发送、PA10接收。串口输出的意义在于你可以拿着小车贴着黑线缓慢推过赛道看日志里的left/right状态变化是否符合预期。如果状态序列是01 - 11 - 10说明传感器间距太大需要把两个模块往中间收一点。最后推荐加一个弯道减速的细节。检测到左或右传感器压线后不要直接把PWM从600切到300否则车尾会因为惯性甩出去。常见做法是做一个斜坡函数// 弯道减速斜坡每次主循环降低50最小不过200 if (turning_speed target_speed) { turning_speed - 50; TIM_SetCompare1(TIM2, turning_speed); }逻辑说明这个斜坡让电机从600降到300的过程有4~5个控制周期每周期约10ms车体有足够时间把重心移到前轮而不是瞬间甩尾。参数50和200要根据实际跑道摩擦系数调整太慢则进弯前不够减速太快等于瞬间切换。5. 进阶扩展把双路循迹升级成灰度检测与FreeRTOS任务化5.1 灰度传感器的动态阈值改造双路数字量只输出0/1在阳光直射下阈值点飘得厉害。常见做法是换成四路灰度模块把模拟输出接到PA0~PA3用ADC规则组采集。上电时做一次自动校准记录白底和黑线的基准值再取中间值作为判断阈值// 灰度传感器阈值校准白黑基准取中值 uint16_t white_ref[4], black_ref[4], threshold[4]; for (int i 0; i 4; i) { threshold[i] (white_ref[i] black_ref[i]) / 2; } // 运行中读取灰度值并判断是否压线 if (adc_value[i] threshold[i]) { line_state | (1 i); }逻辑说明灰度值越黑数值越小所以小于阈值认为是黑线。校准最好在赛道现场进行一次白底基准取小车正常跑动时采集到的最小值防止地砖纹理或阴影导致白底值波动。四路灰度比双路多的不只是状态而是可以通过左右两路灰度值的差计算小车中心与黑线的横向偏移这个偏移量就是PID控制器的误差输入。5.2 把裸机循环改成FreeRTOS任务如果你要把这个源码扩展成工创赛智能物流小车裸机while循环很快会被超声波、摄像头、电机闭环占满。FreeRTOS移植后建议拆两个任务// 5ms周期采样传感器10ms周期控制电机 void vSensorTask(void *arg) { for (;;) { read_line_sensors(); vTaskDelay(5); } } void vMotionTask(void *arg) { for (;;) { update_line_follow_pwm(); vTaskDelay(10); } }任务间通过全局结构体或者队列传递line_state不要在中断里直接修改PWM比较值。运动任务优先执行传感器任务可以被打断。栈空间上运动任务因为调用printf要分配256 words以上否则调用完第一次printf任务栈就溢出HardFault_Handler入口会被踩进去。提示FreeRTOS的SysTick会抢占业务代码是正常的但PWM定时器中断优先级不能低于FreeRTOS可管理的最低优先级否则占空比更新会被SysTick延迟出现电机抽动。5.3 一层感知一层运动摄像头循迹复用差速模型黑线循迹、电磁三轮、摄像头循迹区别只在上层感知下层的L293D差速控制完全可以复用。摄像头方案里把vSensorTask替换成摄像头处理任务输出一个left_offset值再映射到左右PWM差其余逻辑不需要动。这也是拆这套源码最大的价值先在一个简单的双路循迹上验证差速模型再往复杂感知方向迁移不用重写电机驱动。如果你还有时间可以把灰度模块的四路状态归一化成一个-100到100的偏差值然后套一个位置式PID。P项负责回正I项负责消除长时间跑偏的稳态误差D项抑制过弯时的抖动。PID参数从P30开始调I和D先置0等P项让小车不冲出赛道后再慢慢加D到15左右最后加I。这个调法在大多数TT电机小车上都能稳定跑。本文还有配套的精品资源点击获取
返回列表