十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F103ZET6循迹小车实战:从PWM调速到PID闭环的完整避坑指南

STM32F103ZET6循迹小车实战:从PWM调速到PID闭环的完整避坑指南 简介一套基于STM32F103ZET6的循迹小车完整工程代码面向智能车竞赛、嵌入式课程设计等场景。小车采用舵机加双编码电机结构配合一字排布的灰度传感器阵列识别赛道黑线以位置式PID算法控制舵机转角与双电机差速适用于直道、环岛、S弯和连续直角弯等常见赛道元素。资源共207个文件压缩后约5.48MB以C源文件、头文件、Keil工程文件为主同时包含hex、axf等编译产物及少量说明文本可直接用Keil打开工程查看和重新编译。目前已有8429人学习下载。代码模块涵盖PWM输出、编码器反馈、ADC灰度采集和PID控制等关键环节工程结构清晰便于结合实物调试也可作为循迹算法学习和竞赛方案改造的实用参考。 事情得从一次失败的走线说起。那天我拿着刚焊好的STC51小车跑直线结果车头疯狂画龙过个十字弯直接冲出赛道我蹲在地上捡轮子的时候就在想换个正经的32位主控是不是就能把这破车救回来于是就有了这次基于STM32F103ZET6的循迹小车项目。说实话STM32F103ZET6这块芯片在智能车圈子里算是老熟人了Cortex-M3内核、72MHz主频、512KB Flash外设接口多到用不完做循迹小车绰绰有余。我可太清楚买它的人都是什么心态——不是不知道51也能跑而是想借着做小车把定时器、PWM、中断、I2C这些STM32的核心外设一次性吃透顺便给以后做电赛、毕设攒点底子。这篇文章不是给你照抄代码完事的我用一整个周末把这台车从硬件搭到代码写完又从画龙调到贴线跑中间踩了不少坑烧了一路MOS管也报废过一块核心板。今天就把这台车从方案选型、硬件搭建到代码调度、参数整定完完整整复盘一遍包括TCRT5000红外传感器的阈值标定、TIM3输出四路PWM时的引脚重映射细节还有纯阈值控制和PID控制的体验差距。无论你是刚学会点灯想搞台小车玩玩的新手还是已经在用51写循迹但想跨到STM32平台的进阶玩家这篇应该都能让你少走弯路。1. 项目总体方案设计1.1 系统组成与硬件架构先看整体架构。一台循迹小车说白了就四件事感知路径、控制决策、驱动电机、供能跑路。我这台车的具体选型如下主控STM32F103ZET6最小系统板直接买现成的带8MHz晶振、复位电路、USB转串口省得自己画板路径感知4路TCRT5000红外循迹模块一字排开模拟量输出配合比较器转数字量电机驱动L298N模块逻辑电源和电机电源分开接ENA/ENB使能脚接PWM动力4个带减速箱的TT电机左右各两个并联转速比大概1:48扭矩够用电源12V锂电池组给电机和驱动供电AMS1117-3.3稳压给主控板单独供电绝不共地掺和刚开始有人会问为啥不直接用灰度传感器或者摄像头TCRT5000一对才几块钱4路总共不到二十块而且它在黑白分明的跑道上抗干扰能力很强。摄像头方案不仅处理帧率高还牵扯到图像识别对新手来说一上来就玩视觉有点难度超标。我始终觉得第一个STM32项目别一上来就追求最先进而应该追求每一行代码、每一根连线都能搞明白。1.2 为什么选STM32F103ZET6而非51这个事儿其实挺得罪人的但我实话实说51单片机做循迹小车完全可行网上大把教程我也做过能跑。但用STM32F103ZET6的体验完全是两个级别。首先是资源维度。51的定时器通常就俩一个做PWM调速、一个做循迹采样计时就差不多了想再加个超声波避障或者蓝牙遥控就得挤破头。而STM32F103ZET6有8个定时器TIM1和TIM8还是高级定时器能输出互补PWM再加上USART有5个、I2C有2个、SPI有3个后面你想往这车上加蓝牙模块、OLED显示屏、编码器测速、PID闭环它全都能稳稳接住基本不用换主控。然后是性能维度。72MHz主频带来的最直观感受就是循迹传感器的采样频率可以从几十赫兹直接拉到几百赫兹控制周期一短小车对弯道的反应就快得多过弯不再“思考人生”。而且Cortex-M3内核有硬件乘法器后面跑PID运算的时候那点浮点计算根本谈不上压力。最后是学习价值。说真的如果你以后想走嵌入式这条路51是启蒙STM32才是敲门砖。寄存器操作、标准外设库、HAL库、中断嵌套、DMA传输这套知识体系在STM32上能打得很扎实以后换GD32、换NXP甚至换其它架构的ARM芯片迁移成本都低。2. 核心原理与关键技术点拆解2.1 循迹检测原理红外反射率差异循迹赛道通常是白底黑线或者深色底板白线TCRT5000模块的原理说白了就是一个红外发射管和一个光电接收管配对工作。发射管持续发出红外光波长大概950nm肉眼看不见光打到地面后发生反射接收管根据反射光强度输出不同的电压。这里有个关键物理现象黑色物体对红外光的吸收率远高于白色物体。所以当传感器在黑线上方时反射光弱接收管导通程度低输出高电平或者低电平取决于模块电路是上拉还是下拉当传感器在白底上方时反射光强接收管导通程度高输出低电平。模块板载的LM393比较器就是干这个事的——把模拟电压和一个可调电位器决定的阈值电压比较最终输出一路干净的数字电平直接进单片机的GPIO就行。我实测下来的数据是TCRT5000在距地面1cm左右时黑白区分度最好太近容易怼到赛道上的灰尘和杂物太远反射光衰减得厉害输出就开始飘。装车时我特意调了支架高度让传感器离地大约8-12mm然后用螺丝刀微调板载电位器直到小车在黑白交界处翻转电平的瞬间指示灯亮灭干脆不拖泥带水。2.2 调速核心PWM占空比与电机转速的关系双电机小车跑直线最大的问题是左右电机即使出厂参数一致实际转速也会有细微差别更别说地面上摩擦力不一样、电池电量波动这些变量。所以要实现“直道加速、弯道减速”这种分级控制光靠给电机通断电源是不够的必须用PWM调速。PWM的全称是脉冲宽度调制你可以把它理解成一个高速开关——通和断不断切换通过改变“通”的时间占整个周期的比例来改变输出的平均电压。这个比例就是占空比取值范围0%到100%。对直流电机来说有效值电压 电源电压 × 占空比。我用的12V电源占空比50%时电机等效电压大约6V转速差不多是满速的一半。在STM32上产生PWM的方式特别简单定时器配置成PWM模式计数器不断从0数到自动重装载值ARR再和一个比较值CCR比较。计数值小于CCR时输出高电平大于CCR时输出低电平CCR除以ARR就是占空比。这背后全是硬件在做CPU只需要在一开始把寄存器配置好后面PWM波形就一直自动输出了完全不用软件干预。2.3 TIM3引脚重映射与多通道PWM输出的细节这章要专门讲讲TIM3因为我的四路PWM就是靠它输出的。TIM3是一个通用定时器有4个通道默认情况下CH1映射到PA6CH2映射到PA7CH3映射到PB0CH4映射到PB1看到问题没PA6和PA7同时是SPI1的MISO和MOSI引脚在某些板子上还可能被其它外设占用。而且如果你像我一样几个电机驱动模块的控制脚布局和这些默认引脚冲突就得用到重映射功能。STM32的引脚重映射算是Cortex-M3时代非常有特色的功能一个定时器的输出通道并非锁定在固定引脚上通过配置复用重映射寄存器AFIO可以把通道映射到另一组引脚。TIM3的部分重映射是CH1 - PC6CH2 - PC7CH3 - PC8CH4 - PC9这样四个通道齐齐整整地排在一起接线舒服得多。配置代码里有个坑开了重映射必须先使能AFIO时钟也就是RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_TIM3, ENABLE);顺序反了或者忘了开AFIO时钟重映射不生效PWM死活不出波形这时候你拿着示波器量PC6引脚什么也量不到还以为是板子坏了。顺嘴提一句热搜里那个“用STM32F103ZET6的TIM3制作呼吸灯”其实就是PWM最基础的玩法把CCR从0逐渐增加到ARR再逐渐减回来LED的亮度就会像呼吸一样缓慢变化。我当初就是用呼吸灯验证了TIM3的PWM配置没问题再接到电机驱动上的从点灯到驱动电机中间只差一个引脚和一段电机驱动逻辑。3. 硬件选型与电路连接实战3.1 传感器模块接线及其原理图解读我这次用的是4路TCRT5000模块针脚定义其实每家略有差异但大差不差。以我手里这块为例模块引脚功能接到STM32的引脚VCC电源正3.3V-5V5V从L298N的5V输出取GND电源地公共地D1通道1数字输出PB4D2通道2数字输出PB5D3通道3数字输出PB6D4通道4数字输出PB7注意图上虽然印着D1到D4但有些模块还会引出A1到A4的模拟输出如果你用的是带模拟量输出的版本想接ADC做更精细的灰度识别别把模拟输出直接怼到5V单片机的GPIO上。我这次是纯数字量方案不用ADC所以直接接D口就行。每个通道的板载比较器都有一个电位器可以调阈值。调试时有诀窍把模块放在白底上用万用表量比较器输出端此时应该是低电平或高电平看模块设计再放到黑线上输出应该反转。如果没反转就慢慢拧电位器直到反转点刚好处于黑白交界这个位置。四个通道逐一标定确保装到车上之后每路都能在同样的位置触发。3.2 电机驱动模块L298N的接线避坑指南L298N模块是双H桥电机驱动器意思是它能同时驱动两个直流电机并且支持正反转。它上面的接线柱和跳线帽有讲究12V输入接锂电池组正极我用的12V同时模块板载一个78M05稳压芯片能从12V降压出5V给单片机供电但如果你同时让两个电机全速转和大电流舵机工作这个5V输出压降会很厉害我选择单独供电不共用GND电源负极必须和单片机的GND连接形成共地否则控制信号没有参考电平电机压根不转OUT1/OUT2驱动左边的两个电机并联OUT3/OUT4驱动右边两个IN1-IN4控制电机转向的逻辑输入接STM32的GPIOENA/ENB使能端。把跳线帽拔掉然后接STM32的PWM输出引脚这样才能实现调速不拔跳线帽的话ENA被直接拉高电机只能全速转没法调速我踩过一次坑一开始图省事没拔跳线帽PWM信号接上去完全没反应查了半天发现是跳线帽把ENA硬拉到了高电平PWM输进去根本起不了作用。我的接线规划L298N引脚接到STM32功能说明ENAPC6TIM3_CH1左电机PWM调速IN1PD0左电机方向AIN2PD1左电机方向BENBPC7TIM3_CH2右电机PWM调速IN3PD2右电机方向AIN4PD3右电机方向B控制逻辑也很简单IN11、IN20时左电机正转反过来就反转IN1和IN2同时为1或0就刹车。配合PWM就能实现“多少速度往哪个方向转”的组合控制。3.3 电源系统的地线处理经验这个必须单独拿出来讲因为新手最容易在这里翻车。电源系统我分了三路12V电池正极 - L298N的12V输入L298N的5V输出 - 循迹传感器模块的VCC12V电池正极 - 降压模块LM2596降到5V - 再接AMS1117降到3.3V - STM32核心板的3.3V引脚很多人问为什么STM32不直接用L298N板载的5V我解释一下电机启动瞬间电流很大L298N的5V输出电压会有明显跌落而STM32对供电稳定性的要求更高万一电压跌到复位阈值以下直接系统重启小车就会在赛道上“抽搐”。让主控和电机驱动分开供电只有GND连接在一起能大幅降低干扰。后来我用示波器测过共地之后两边的GND电位一致信号传输没有任何问题。4. 软件代码实现流程4.1 GPIO初始化与传感器读取配置传感器数字输出接的是PB4到PB7。初始化时候有个重点STM32的GPIO默认可能是复用功能或者模拟输入这里必须明确配置成上拉输入。void Sensor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); }上拉的逻辑我解释一下TCRT5000模块的数字输出是开漏输出结构模块内部的比较器只能把输出拉低拉高需要外部电阻。如果GPIO配置成浮空输入也就是不上拉也不下拉电平就会悬浮不定读回来的数据一抖一抖的直接导致小车原地抽搐。配置成上拉输入后当模块输出管脚不主动拉低时引脚被电阻稳稳拉在高电平读数就干净多了。读取的时候其实最推荐的方式是状态寄存器一次性读取而不是挨个调用GPIO_ReadInputDataBit。一次GPIO_ReadInputData就能把整个GPIOB的输入电平拿回来配合位运算和掩码一行代码就能得到四个传感器的状态组合速度还快uint8_t sensor_state 0; uint16_t gpio_value GPIO_ReadInputData(GPIOB); sensor_state | ((gpio_value GPIO_Pin_4) ? 0x01 : 0); sensor_state | ((gpio_value GPIO_Pin_5) ? 0x02 : 0); sensor_state | ((gpio_value GPIO_Pin_6) ? 0x04 : 0); sensor_state | ((gpio_value GPIO_Pin_7) ? 0x08 : 0);这样sensor_state的低4位就分别对应4个传感器的状态后面的循迹判断逻辑只需要比较这个值就行。4.2 TIM3初始化与PWM输出配置接下来是重头戏配置TIM3输出四路PWM。我选择的输出引脚是PC6、PC7、PC8、PC9TIM3部分重映射。void TIM3_PWM_Init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE); // 关键使能AFIO时钟后再执行重映射 GPIO_PinRemapConfig(GPIO_Remap_TIM3, ENABLE); // PC6、PC7、PC8、PC9 配置为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); // 定时器基础配置72MHz/(psc1) 计数频率 TIM_TimeBaseStructure.TIM_Period arr; TIM_TimeBaseStructure.TIM_Prescaler psc; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 四路PWM模式配置输出极性为高 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, TIM_OCInitStructure); TIM_OC2Init(TIM3, TIM_OCInitStructure); TIM_OC3Init(TIM3, TIM_OCInitStructure); TIM_OC4Init(TIM3, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }频率计算72MHz主频psc设为71那么计数时钟就是72MHz / (711) 1MHz也就是说计数器每1微秒加1。如果arr设为999PWM周期就是1000微秒即1kHz。对直流电机来说1kHz到10kHz是最常用区间。太低了电机转起来能听到明显的“嗡嗡”断续声扭矩也会波动太高了驱动模块的MOS管开关损耗变大而且驱动芯片可能根本反应不过来。我最后选择了psc71、arr999PWM频率1kHz。实测下来电机声音比较细腻转速也稳定发热正常。调整占空比也很简单运行中随时调用void Motor_SetSpeed(int left_speed, int right_speed) { // left_speed/right_speed 范围 0~999 // 低电平使能还是高电平根据L298N模块实际接线调整 TIM_SetCompare1(TIM3, left_speed); TIM_SetCompare2(TIM3, right_speed); }这里要说一下PWM1模式和PWM2模式的区别。PWM1模式下计数值小于CCR时输出有效电平这里配置为高大于CCR时输出无效电平低。也就是说CCR越大每个周期内高电平的时间越长平均电压越高电机转得越快。如果你用着用着发现占空比调高反而转速变慢八成是极性配反了把TIM_OCPolarity_High改成TIM_OCPolarity_Low就行。4.3 循迹控制逻辑阈值分区与转向策略说到控制策略这是整台小车“有没有灵魂”的分水岭。我先把左右两个传感器的位置讲清楚四个传感器一字排列从左到右编号1到4对应sensor_state的bit0到bit3。当小车完全压在黑线上时一般会出现两种情况中间两个2号和3号都压线或者只有其中一个压线。所谓“阈值分区”就是根据传感器状态组合把赛道情况分成几类每类对应一组电机输出值。这是最经典的查表法void Track_Control(void) { uint8_t state sensor_state; switch(state) { // 2号、3号都压线车在直道上全速前进 case 0x06: Motor_SetSpeed(699, 699); break; // 2号压线车略微偏右向左微调 case 0x04: Motor_SetSpeed(499, 699); break; // 3号压线车略微偏左向右微调 case 0x02: Motor_SetSpeed(699, 499); break; // 2号、3号、4号压线车偏右严重向左大调 case 0x0E: Motor_SetSpeed(299, 799); break; // 1号、2号、3号压线车偏左严重向右大调 case 0x07: Motor_SetSpeed(799, 299); break; // 1号压线弯道太急向左急转 case 0x01: Motor_SetSpeed(0, 799); break; // 4号压线向右急转 case 0x08: Motor_SetSpeed(799, 0); break; // 全部压线或者全部离线可能是停车标志或者冲出赛道 case 0x00: case 0x0F: default: Motor_SetSpeed(0, 0); break; } }这套策略的本质就是哪边传感器检测到黑线就说明车偏向了另一边得让偏向侧电机减速、另一侧电机加速把车头“掰”回线上。pid0.5代码里的499/699这种比例是我反复试出来的直道不会太肉弯道也不会因为修正过猛而冲出赛道。其实这里还可以用PID闭环来替代查表法。查表法的缺点是控制输出是离散的不管偏差是1毫米还是10毫米只要落在同一分区电机输出都一样而PID可以根据偏差大小连续地调整修正力度过弯更顺滑。但PID参数整定需要时间先查表跑通再改PID是更合理的路径。4.4 主循环调度与中断设计思路主函数结构我设计得非常简单粗暴int main(void) { SystemInit(); Sensor_GPIO_Init(); TIM3_PWM_Init(999, 71); Motor_GPIO_Init(); while(1) { Sensor_Read(); // 读取传感器状态 Track_Control(); // 根据状态控制电机 delay_ms(5); // 控制周期约5ms } }这里边有个讲究控制周期不能太长也不能太短。5ms对应200Hz的控制频率对循迹场景来说是个甜点值。周期太长小车过弯时路径校不过来容易冲出去周期太短频繁切换电机电压驱动模块发热会增加还可能引入额外抖动。网上有人喜欢用外部中断让传感器状态变化直接触发中断处理我试过实际体验是在循迹这种场景里没必要中断频繁触发会干扰主循环里其它任务的执行而且万一处理函数写得不够短中断嵌套会把系统搞得很难排查。轮询足够用了。5. 常见问题排查与实战避坑5.1 电机不转或单侧不转这是最常见的问题。排查顺序我建议按“供电 - 控制逻辑 - 接线 - 代码”四步走用万用表量L298N的12V输入有没有电压再看板载5V指示灯亮不亮把IN1和IN2直接接高/低电平用杜邦线手动跳一下如果电机不转问题可能出在L298N或者电机本体测量STM32的PD0、PD1引脚有没有正确的逻辑电平输出确认ENA跳线帽是否拔掉PWM有没有从PC6输出用示波器或者逻辑分析仪我遇到过一次很诡异的现象左边电机正常、右边电机完全不动查了一下午最后发现是ENB引脚上那个跳线帽接触不良导致ENB悬空。L298N的使能脚是低电平有效还是高电平有效要看具体批次我这块是高电平有效悬空时状态不确定一半时间不转。解决办法很简单使能脚不要悬空要么接PWM要么明确接高电平。5.2 循迹传感器输出不稳定症状小车走着走着突然原地转圈或者过弯时机飘忽不定。原因通常有三个第一是供电不足。传感器模块用的是L298N的5V如果电池电压掉到10V以下78M05稳压输出就可能低到4.5V传感器即使有比较器也可能工作不正常。解决方法是给传感器模块单独用一个AMS1117-5V的稳压模块从电池直接取电。第二是阈值没调好。电位器拧过头传感器在白底上也输出“检测到黑线”的电平那控制逻辑肯定全乱套。解决办法是我在3.1节写的白底/黑线标定流程。第三是安装高度不对。四个传感器装得高低不一有的离地5mm、有的离地15mm反射强度完全不一样。我最后用热熔胶固定传感器支架时特意放了一把尺子卡在底下保证四个探头离地距离一致。你别笑这个细节真的能决定成败地面稍有不平装得歪的传感器就先触发小车就会莫名其妙地偏。5.3 STM32无法下载程序这真是“劝退级”问题。好多新手拿到板子连上ST-LINK点了下载然后提示“No target connected”就开始怀疑人生。常见原因和排查检查BOOT0跳线帽是否在正确位置。正常下载程序时BOOT0应该接GND如果BOOT0被拉高芯片会进入ISP模式ST-LINK连不上检查核心板的3.3V供电是否正常电源指示灯亮不亮确认ST-LINK的SWDIO和SWCLK有没有接反GND有没有接上按住复位键再点下载看到“Connecting...”的瞬间松开复位有时候能救回来我自己的教训是有一次把PB4和PB5JTAG的NJTRST和NTDI默认复用引脚当普通GPIO用了结果忘了禁用JTAG功能程序死活下载不进去。后来在GPIO初始化之前加了一行GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);把JTAG关掉只保留SWDPB4和PB5才被释放成普通GPIO。这块太容易坑人了用PB3、PB4、PA15这些引脚时第一件事就是想清楚它们是不是JTAG复用引脚。5.4 弯道过甩与直道画龙“画龙”指的是小车在直道上蛇形走位左右摆来摆去“过甩”指的是弯道里修正幅度太大车尾甩出去。画龙的原因通常是传感器阈值分区太灵敏稍微有一点偏差就触发大修正或者控制周期太短电机还没执行完上一个指令就被下一个指令打断。我的解决办法是给控制指令加一点“死区”逻辑也就是当检测到的偏差很小时维持上一次的输出不做调整避免系统一直在震荡。过甩则相反说明修正太猛了。把急转分支的左右速度差调小一点比如从(0,799)改成(299,799)让小车过弯时既能转向又不会突然掉速度。调这个的过程就是调“车感”我建议你先在直道上把最大速度调到600左右感觉不画龙了再上弯道测弯道过甩了再逐步降急转修正幅值一步一个脚印来。6. 进阶方向与调试工具推荐6.1 从查表法到PID闭环查表法跑通之后我强烈建议你试试PID闭环。PID在循迹小车上的核心是“位置偏差”这个概念不再只看哪路传感器压线而是综合四个传感器的状态算出一个数值比如int error 0; error (sensor_state 0x08) ? 3 : 0; error (sensor_state 0x04) ? 1 : 0; error (sensor_state 0x02) ? -1 : 0; error (sensor_state 0x01) ? -3 : 0;这个error就是小车相对于黑线的横向偏差负数表示偏左正数表示偏右。然后PID输出int output Kp * error Ki * integral Kd * derivative; left_speed base_speed - output; right_speed base_speed output;Kp先调只保留比例项看小车能不能稳住然后加Kd减小超调最后加点Ki消除静差。参数整定的经验值先给个参考Kp20左右、Kd5左右具体还得根据你的机械结构、PWM频率慢慢试。PID调好之后最直观的感受是过弯顺滑了不再是查表法那种“开开关关”的顿挫感。6.2 调试工具串口打印与OLED显示调试循迹小车最痛苦的事情是车在地上跑你蹲在旁边根本看不清传感器的实时状态。我强烈推荐用一个无线串口模块HC-05蓝牙或者NRF24L01把传感器状态和控制指令实时发到电脑上看或者直接怼一块0.96寸OLED把每一路的电平状态显示成图形。OLED的I2C接口正好可以练练STM32的I2C外设代码量不大但实时画面非常直观。我调阈值的时候就把四路传感器的电平打在OLED上压根不用低头看模块上的LED灯清楚得多。6.3 未来扩展蓝牙遥控、电磁循迹与平衡车这台车跑稳定之后扩展方向多得是加HC-05蓝牙模块通过手机APP遥控同时在遥控模式和循迹模式之间切换把红外传感器换成电磁传感器电感线圈检测赛道中线通入的交变电流产生的磁场就是电磁循迹小车的玩法受环境光干扰小但需要运放电路调理信号是电赛的经典题型往平衡车方向走把四个轮子改成两轮加MPU6050陀螺仪跑平衡算法那就又是一个大项目了反正STM32F103ZET6的所有外设都能用上我打算下一步先加编码器测速给电机加个闭环让直道速度再上一个台阶。一点个人体会这周末做下来我最深的感受是循迹小车的难点从来都不在循迹本身而在于你愿不愿意一层层去排查问题。电机不转的时候能不能静下心从电源量到GPIO传感器误判的时候会不会想到阈值标定和安装高度这些细节PID参数调不出来的时候敢不敢用控制周期去反推哪一环出了问题我做这台车的时候报废过一块核心板烧过三颗MOS管也一度想把小车从窗户扔出去。但等它真的沿着黑线稳稳跑完一圈的时候那种成就感确实很顶。记住一个原则每次只改一个变量改完就上车测试不要一次动三四处地方。这样即使出了问题你也能很快定位到哪里改坏了。如果你也在做类似的项目欢迎回来告诉我你的小车跑得怎么样。尤其是你卡在哪一步了大概率我当年也卡过咱们可以一起把坑填平。本文还有配套的精品资源点击获取
返回列表