简介:本资源是一套基于C语言开发的L298N电机驱动程序,专为正点原子STM32F103核心板设计,面向嵌入式初学者与机器人控制实践者,解决直流双电机的正反转、差速拐弯及PWM无级调速等核心控制问题。压缩包共76个文件,含33个头文件(.h)用于外设配置与函数声明、32个源文件(.c)实现GPIO初始化、PWM输出、电机控制逻辑及中断服务,另有工程配置(.uvprojx/.uvoptx)、启动代码(.s)、系统库(FWLIB)和说明文档(README.md)等,结构完整,可直接导入Keil MDK编译运行,总大小312KB。已有125人学习下载,资源提供可运行的全功能工程框架,涵盖IN1–IN4方向控制逻辑、ENA/ENB使能管理、TIM通道PWM占空比动态调节函数,以及双电机协同拐弯的典型应用示例,是掌握STM32底层驱动与电机控制原理的优质实践素材。 这段时间在折腾正点原子F103核心板的小车底盘,手头正好有一块L298N电机驱动模块,就把整套驱动逻辑从头到尾写了一遍。折腾完发现,这题比想象中更值得拆开讲:用C语言控制STM32F103输出PWM,再通过L298N驱动直流电机,表面上看就是几个GPIO高低电平的事,实际做下来牵扯到定时器配置、逻辑电平匹配、电源共地、差速转向算法这些细节。这篇文章我会把完整的驱动思路、接线方式、代码实现和踩坑记录都放出来,给同样在玩F103核心板加L298N的朋友一个能直接抄作业的参考。
我用的硬件是正点原子F103核心板,属于STM32F103C8T6这颗芯片的最小系统板,没有外挂底板上那些外设,好处是引脚全部引出,想接什么接什么,坏处是所有东西都得自己动手。L298N是双H桥驱动,能同时控两个直流电机,小车左右各一个电机就能完成前进、后退、转弯,再配合STM32的定时器PWM输出,还能实现连续调速。这套组合在智能小车入门里非常经典,写明白之后,再做巡线、避障、遥控小车,底层驱动基本不用再动。
1. 项目需求拆解与驱动方案选型
1.1 核心板、驱动模块和电机的分工
先梳理一下整个链路上每个部件到底干什么。STM32F103核心板负责“决策”,它根据代码逻辑输出控制信号;L298N负责“执行”,把单片机的逻辑信号转换成驱动电机的大电流;直流电机负责“出力”,产生实际的转动。三者之间需要明确的接口定义,否则信号会乱套。
L298N内部是两个H桥,每个H桥对应一个电机通道,命名通常叫A、B通道。每个通道有三个控制引脚:IN1、IN2和ENA,或者IN3、IN4和ENB。IN1和IN2决定电机转向:一高一低正转,反过来就是反转,两个都是低电平或者都是高电平则停止。ENA是使能端,只有这个引脚有效时电机才会转,如果把它接到高电平,相当于电机全速跑,如果给它输入PWM信号,就能通过改变占空比控制电机速度。
所以驱动逻辑一下子就清晰了:方向由IN1、IN2控制,速度由ENA的PWM占空比控制。你要让小车前进,就把两个通道的IN设置成同一方向,PWM给一个速度值;要后退,把方向电平反过来即可。PWM调速的本质是快速通断电源,让电机的平均电压变化,从而改变转速。直流电机的转速跟端电压近似成正比,这就是PWM调速的基本原理。
1.2 为什么拐弯要用差速而不是转向舵机
小车拐弯这个问题,我说过很多次,很多新手第一反应是“加个舵机转向不就行了”。舵机转向适合阿克曼结构,也就是前轮转向、后轮驱动的车型。但是两轮驱动、后轮各自带电机的底盘,最自然的方案是差速转向。
差速转向的原理很简单:左右轮转速不一致,车就会朝转速低的那一侧转弯。如果左轮停转、右轮正转,小车会以左轮为支点原地转圈;如果左右轮速度差值小一点,小车会划一个较大的弧线转弯。在我这套代码里,我同时实现了两种转弯方式:一种是不调速的原地转向,左右轮一个正转一个反转;另一种是平滑差速,在前进过程中动态调整两侧PWM占空比,实现大半径弧线过弯。
选择差速方案的另一个原因是它完全复用电机驱动代码,不需要额外的舵机控制逻辑和脉宽映射代码。L298N本身就能驱动双电机,天然支持差速控制。你要是用带底盘的成品小车,上面已经预留了左右电机接口,再额外装舵机反而破坏重心和结构。
1.3 为什么非要用PWM而不是直接通断
有人会问,我用IO直接输出高电平让电机全速转,用延时函数控制时间间隔,不也能实现“调速”吗?比如转200ms停100ms,平均速度看起来慢了。这种办法叫通断控制,确实能调速,但是有两个致命问题:一是电机是感性负载,频繁通断会产生很大的反电动势尖峰,容易干扰单片机工作,严重点直接复位;二是通断方式下电机实际处于加速、减速不断交替的状态,噪音大、发热高、响应迟钝,尤其在低速情况下会明显抖动。
PWM调速就完全不同。它的开关频率通常在10kHz以上,电机在这个频率下由于机械惯性,转速不会跟着PWM波形起伏,而是稳定在平均电压对应的转速上。电机处于连续平滑的运行状态,噪音小、发热低、控制线性度也好。所以PWM并非“看起来高级”,而是直流电机调速真实的标准做法。
从实现角度看,STM32的定时器硬件输出PWM非常方便,只要设置好预分频系数、自动重装载值和比较寄存器,硬件就会自动在引脚上输出占空比可调的方波,完全不需要CPU干预。这也是我选择用定时器硬件PWM而不是用延时翻转IO来实现调速的重要原因。软件模拟PWM在低转速时CPU占用率极高,而且精度差,实际项目里不推荐。
2. 硬件连接与供电设计
2.1 L298N引脚功能与接线对照
先看L298N模块上的关键引脚。电源部分有三个:12V输入、GND、5V输出。12V输入用来给电机供电,一般接7到12V的电池组,我实际用的是两节18650串联,大约7.4V。5V输出是模块内部稳压芯片输出的,可以用来给单片机或者其他逻辑电路供电,但是要特别注意电流能力很有限,大概几十毫安,带不动太大的负载。
逻辑部分有六个引脚:ENA、IN1、IN2、IN3、IN4、ENB。其中IN1到IN4接单片机的GPIO,ENA和ENB接PWM输出。我选用的引脚分配如下表:
| 模块信号 | 核心板引脚 | 说明 |
|---|---|---|
| IN1 | PB0 | 通道A方向控制 |
| IN2 | PB1 | 通道A方向控制 |
| ENA | PA6 | 通道A使能,接TIM3_CH1 PWM |
| IN3 | PB10 | 通道B方向控制 |
| IN4 | PB11 | 通道B方向控制 |
| ENB | PA7 | 通道B使能,接TIM3_CH2 PWM |
这个分配有两个讲究。第一,PWM引脚固定用PA6和PA7,因为这两个引脚在硬件上映射到TIM3的通道1和通道2,不需要重映射,省去配置AFIO的麻烦。第二,方向控制引脚避开PA13、PA14、PA15和PB3、PB4这几个JTAG/SWD调试占用的引脚,否则下载程序的时候会出问题。PB0、PB1、PB10、PB11都是普通IO,使用上没有坑。
电机输出端有OUT1、OUT2和OUT3、OUT4,分别接左右两个直流电机。注意两个电机不要接反,否则前进会变成后退,到时候方向控制逻辑要反过来调,排查起来很浪费时间。建议接线后在轮子上标一下左右,第一次测试直接全速前进,确认两个轮子转向一致。
2.2 供电和共地是第一个大坑
L298N模块的供电设计是我在这次调试里踩的最大一个坑。很多朋友把电机电源接到L298N的12V输入,然后把单片机用USB单独供电,结果发现怎么给IO置高电平,电机都没有反应,或者偶尔抖一下就停。
核心原因是没有共地。单片机输出的逻辑信号是以它自己的GND为参考的,L298N电路也是以自己的GND为参考的,两个参考地如果不连在一起,高电平的参考点就不一致,收端根本解读不了这个信号。用万用表量IN引脚电压的时候会发现只有零点几伏波动,自然驱动不了。
正确做法是把L298N的GND和STM32核心板的GND用导线连在一起,让两边有一个统一的参考电位。这一步是所有逻辑信号交互的前提,也是嵌入式系统里最基础也是最容易忽略的规则。之后信号线上的高低电平才有了统一标准。
电源部分我最终采用了这样的方案:电机电源用两节18650锂电串联,经过总开关接L298N的12V输入;STM32核心板用USB线单独供电,或者从L298N的5V输出接到核心板的5V引脚。使用L298N的5V输出给单片机供电时,要注意模块内部稳压芯片的发热和电流限制,如果你的电机功耗大、驱动发热严重,建议还是用单独供电,只把GND连起来就好。
2.3 3.3V逻辑电平直驱L298N是否可靠
正点原子F103核心板的IO电平是3.3V,L298N这个模块数据手册上说逻辑输入高电平门槛一般在2.3V左右,理论上3.3V是够用的。但实际应用里有个区别,不同厂家的L298N模块用料不一样,有的板子逻辑部分还加了光耦隔离,这就会导致输入阈值发生变化,甚至需要更高的驱动电流。
我实测下来,手上这块普通版L298N直接用3.3V GPIO驱动完全没问题,IN1到IN4接到PB0、PB1、PB10、PB11,高电平时引脚能稳定拉到3.3V,L298N能可靠识别。但要注意控制线和GPIO之间的走线不要过长,尽量在20cm以内,避免线间电容和串扰导致电平畸变。
如果你用的是带光耦隔离的版本,光耦输入侧一般需要5到15mA的驱动电流,3.3V GPIO可能带不动,这时候就需要在L298N的板子上加焊限流跳线,或者用输入侧共阳的方式,让光耦工作在自己的逻辑电平上。买模块之前先看清楚版本,别等到接完线才发现驱动不了。
从长远角度看,如果以后要换更复杂的项目,比如使用更高电压的电机驱动板,最好在中间加一级电平转换,或者直接选用3.3V逻辑兼容的驱动方案,比如TB6612这类专门为单片机设计的驱动芯片,内部直接兼容3.3V逻辑输入,就不存在电平匹配的问题。但L298N作为入门学习,把电平匹配这件事亲自踩一遍,其实也很有价值。
3. 驱动代码实现:从GPIO到PWM
3.1 GPIO初始化:方向控制引脚配置
代码部分,我基于正点原子提供的标准外设库工程编写,所有初始化函数单独放在motor.c文件里。先看GPIO方向控制引脚的初始化,这部分逻辑比较固定,就是把PB0、PB1、PB10、PB11配置为推挽输出,输出速度50MHz。
void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); }这里用的是推挽输出模式,能够让GPIO在输出高电平时主动拉高到3.3V,输出低电平时主动拉低到0V。开漏输出模式在这个场景下不合适,因为L298N的逻辑输入引脚需要明确的电平驱动,开漏输出还得外加上拉电阻才能得到稳定的高电平,白白增加器件和故障点。
输出速度设置成50MHz其实不是必须的,GPIO翻转PWM的速率远远达不到这么高,但保持一致也没问题,不会带来副作用。如果走线较长、干扰较大,可以把速度降到2MHz试试,有可能会改善信号完整性问题。
3.2 PWM初始化:定时器参数怎么算
PWM部分我使用TIM3的通道1和通道2,分别对应PA6和PA7。STM32F103的定时器时钟是72MHz,这个值在正点原子标准工程里已经通过SystemInit配置好了。要得到10kHz的PWM频率,我把预分频系数设为35,自动重装载值设为199,计算过程如下:
72MHz除以(35加1)等于2MHz,也就是说PWM计数器的计数频率是2MHz,每个计数脉冲耗时0.5微秒。再除以(199加1)等于200,得到10kHz的PWM频率,每个PWM周期是100微秒。占空比分辨率是1/200,也就是0.5%,控制100%量程时精度足够。
void Motor_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 199; TIM_TimeBaseStructure.TIM_Prescaler = 35; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 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_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM3, ENABLE); TIM_Cmd(TIM3, ENABLE); }PA6和PA7配置为复用推挽输出,也就是GPIO引脚的控制权交给片上外设,由TIM3硬件来控制引脚输出波形。这里如果配置成普通推挽输出,定时器是没法把波形送到引脚上的,这是新手特别容易犯的错误之一。
为什么选10kHz而不是1kHz或20kHz?1kHz以下音频范围内,电机线圈会产生明显的啸叫声,听起来很刺耳,而且电流纹波大,电机发热会比较明显。20kHz以上已经超出人耳听觉范围,但L298N这个模块的开关损耗会增大,驱动芯片容易发烫。10kHz是一个平衡点,噪音低、损耗适中、占空比分辨率也够用。
3.3 电机控制接口:正转、反转、停止和调速
有了GPIO和PWM输出之后,核心就是控制接口。我把每个基本动作封装成独立函数,这样主函数看起来非常简洁,逻辑也清晰。定义方向引脚操作宏,避免在代码里到处写位操作。
#define IN1_0 GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define IN1_1 GPIO_SetBits(GPIOB, GPIO_Pin_0) #define IN2_0 GPIO_ResetBits(GPIOB, GPIO_Pin_1) #define IN2_1 GPIO_SetBits(GPIOB, GPIO_Pin_1) #define IN3_0 GPIO_ResetBits(GPIOB, GPIO_Pin_10) #define IN3_1 GPIO_SetBits(GPIOB, GPIO_Pin_10) #define IN4_0 GPIO_ResetBits(GPIOB, GPIO_Pin_11) #define IN4_1 GPIO_SetBits(GPIOB, GPIO_Pin_11) void Motor_SetSpeed(uint16_t speedA, uint16_t speedB) { if(speedA > 199) speedA = 199; if(speedB > 199) speedB = 199; TIM_SetCompare1(TIM3, speedA); TIM_SetCompare2(TIM3, speedB); } void Motor_Forward(uint16_t speed) { IN1_1; IN2_0; IN3_1; IN4_0; Motor_SetSpeed(speed, speed); } void Motor_Backward(uint16_t speed) { IN1_0; IN2_1; IN3_0; IN4_1; Motor_SetSpeed(speed, speed); } void Motor_TurnLeft(uint16_t speed) { IN1_1; IN2_0; IN3_0; IN4_1; Motor_SetSpeed(speed, speed); } void Motor_TurnRight(uint16_t speed) { IN1_0; IN2_1; IN3_1; IN4_0; Motor_SetSpeed(speed, speed); } void Motor_Stop(void) { IN1_0; IN2_0; IN3_0; IN4_0; TIM_SetCompare1(TIM3, 0); TIM_SetCompare2(TIM3, 0); }这里用了两个速度参数,因为在实际运行中,左右两个电机不可能完全对称,电机制造公差、轮胎摩擦差异、负载分布都会导致同样的占空比产生不同的转速。如果这时候还让两边跑一样的PWM值,小车就会走偏。所以我在函数设计里保留了同时设置左右轮速度的接口,方便后续加入偏差校正逻辑。
针对“拐弯”这个需求,除了标准的原地左右转之外,我还封装了一个差速转弯函数,用于实现在行进中画出不同半径的弧线。思想和前面讲的一样,给左右轮不同的速度值,差值越大,转弯半径越小。
void Motor_Curve(uint16_t baseSpeed, int16_t diff) { int16_t speedL = baseSpeed + diff; int16_t speedR = baseSpeed - diff; if(speedL < 0) speedL = 0; if(speedR < 0) speedR = 0; if(speedL > 199) speedL = 199; if(speedR > 199) speedR = 199; IN1_1; IN2_0; IN3_1; IN4_0; Motor_SetSpeed((uint16_t)speedL, (uint16_t)speedR); }diff为正时,左轮快右轮慢,车向右拐;diff为负时左拐。基础速度和差值配合起来,就能模拟出类似汽车打方向盘小幅转向的效果,比原地转向要顺滑很多。这套函数接口写完之后,不管以后接巡线模块还是蓝牙遥控,只需要在业务逻辑里调用这些接口就行了。
3.4 主函数演示:前进、后退、拐弯、调速的完整流程
最后在主函数里把这套驱动串起来。我用了一个简单的状态机,让小车依次执行前进、后退、左转、右转、差速弧线转弯和停止,每个动作持续一段时间。这里用正点原子的Delay函数产生延时,代码直观,方便验证硬件接线是否正确。
int main(void) { delay_init(); Motor_GPIO_Init(); Motor_PWM_Init(); while(1) { Motor_Forward(120); delay_ms(2000); Motor_Backward(120); delay_ms(2000); Motor_TurnLeft(150); delay_ms(1000); Motor_TurnRight(150); delay_ms(1000); Motor_Curve(120, 60); delay_ms(2000); Motor_Stop(); delay_ms(1000); } }第一次上电测试,建议先把速度值调小一些,比如80,同时把小车架起来让轮子悬空,避免突然全速冲击撞到东西。确认每个动作都符合预期后再放到地上跑。如果发现小车后退时其实是前进,或者左转右转方向相反,不用改代码,交换同一通道的两根电机线就行。
完整源文件结构就是motor.c和motor.h,main.c里调用初始化函数和状态机。把正点原子工程模板里的SYSTEM文件夹保留,motor相关文件加进去,编译下载就能跑。这是我这次实现最精简也最清晰的代码划分,后续要加蓝牙串口或者红外避障,也只需要在main.c的业务逻辑层加内容,驱动层不需要再动。
4. 常见问题与实测排查技巧
4.1 电机不转但能听到电流声
这是最常见的故障现象,代码逻辑看着没问题,PWM输出也测量到了,但电机就是不动。我遇到这个问题的原因是L298N的ENA和ENB没有接好。有的模块ENA和ENB默认有跳线帽,把引脚拉到了高电平,这时候IN的控制才有效。如果你跳线帽摘掉了,又没有在代码里把对应IO口拉高,电机自然就不转。
排查方法很简单,用万用表直流电压档量ENA引脚对地电压,如果量程正常应该在3.3V左右。没有万用表的话,可以直接在代码里把ENA和ENB都接高电平用飞线短接,如果电机转了,说明问题出在PWM配置上,再回头查定时器初始化。
还有一种情况是PWM已经输出,但占空比太小。电机的启动需要克服静摩擦和惯性,占空比太低时平均电压不足以让电机转起来,只会发出嗡嗡声。我把最低启动占空比测试过,在这套电源和电机组合下,大概占空比超过8%才会稳定转动,低于这个值就表现为静止加电流声。所以排查时可以把速度值调到比较高的位置,比如150,先确认电机能转,再回头找最小启动值。
4.2 只能正转不能反转
如果你的小车能前进但不能后退,或者某个电机只能朝一个方向转,大概率不是硬件坏了,而是控制逻辑里有两个引脚被错误置成了同一电平,导致H桥两侧同时导通,电机处于刹车状态。
注意L298N的输入组合里,IN1和IN2同为高或同为低时,通道输出关闭或者刹车,并不会反转。要实现反转,必须把IN1和IN2高低电平对调。我在代码里写的是通过宏定义设置引脚电平,前后逻辑非常直观,但如果你自己改过代码,很容易出现两个控制脚一直保持同样状态的问题。
硬件上也有一个容易忽略的地方,如果某个IN引脚虚焊或者杜邦线接触不良,比如IN2实际上悬空,那么高电平的IN1会被内部逻辑识别为两个高,电机同样不转。我排查时习惯拿万用表毫伏档直接量L298N的IN引脚,确认每个引脚在动作切换时电压有明确变化,这样能快速缩小问题范围。
4.3 一启动电机芯片就复位
这个现象我在做四轮小车的时候遇到过。代码刚下载完,按下复位键一切正常,但只要给电机上电,核心板立刻重启或者死机。归根结底是电源扛不住电机启动电流,而单片机又和电机共用了同一条电源路径。
电机启动瞬间电流很大,尤其是空载到全速的变化过程,电流冲击会导致电源电压瞬间跌落。如果单片机从同一路电源取电,电压掉到芯片的欠压复位阈值以下,就会触发复位。解决办法有几种:第一,电机和单片机分开供电,只把GND共地;第二,在电机电源两端并联一个大容量电解电容,比如470uF到1000uF,吸收瞬态尖峰;第三,在单片机电源输入端加上电容滤波,容纳短时压降。
我在最后方案里选了电机用电池组、单片机用独立稳压电源供电的方式,效果最稳定。另外还要注意L298N模块本身的散热,大电流工作一段时间后芯片会非常烫,如果烫到不能摸,说明电机堵转或者PWM频率过低导致开关损耗大,需要从负载端找原因。
4.4 PWM频率、占空比和电机噪声的取舍
PWM参数设置虽然看起来只是几个数字,但实际体验下来很有讲究。频率太低会啸叫,太高则L298N发热增加,而且占空比分辨率会下降。我最终采用的10kHz频率是经过实测后觉得比较均衡的方案。
占空比范围上,ARR设成199意味着占空比粒度是0.5%。有些教程喜欢设成999或9999,分辨率更高,但是频率会被拉低。如果要保持10kHz,又想要更高的占空比分辨率,就得引入定时器主从结构或者用高级定时器的重复计数功能,对入门项目来说没有太大必要。0.5%的粒度在实际电机控制里已经足够平滑了。
电机噪声还有一个来源是PWM频率和机械结构共振。如果小车底盘车架或者车轮在某个转速下产生明显共振,可以适当微调PWM频率避开共振点,或者在电机安装位置加橡胶垫减震。我实测在10kHz附近没有特别明显的共振,但不同车架情况不同,这个需要自己试。
4.5 用示波器验证PWM波形的心得
如果手头有示波器,调试起来会轻松很多。把探头夹在PA6引脚上,地线夹到GND,就能直接看到10kHz的方波,占空比随代码中speed参数变化。没有示波器的朋友,可以用一个LED串联电阻接到PA6上,LED亮度随占空比变化,也能粗略判断PWM是否输出正常。
我还做过一个更土的办法,把一个小喇叭或蜂鸣器接在PA6和GND之间,PWM频率是10kHz时听不到声音,若改成较低频率就能听到音调变化。这种方式虽然不容易精确测量占空比,但至少能快速确认定时器有没有在工作,适合手边没有仪器的场景。
最后再分享一个我一路调下来最深的体会:给这类驱动模块写代码时,最花时间的往往不是代码本身,而是硬件连接和电源稳定性。代码无非就是GPIO和定时器的排列组合,把各部件电平逻辑理顺了,功能自然就通了。我建议新手朋友们先把小车架起来空转调试,摸清每个电机的最低启动占空比、左右轮一致性差异,然后再放到地上跑,那时候调转向和调速才会有参照。我这次把差速转弯的参数写在代码里反复调,就是因为左右轮实际速度不一致,占空比一样但车会跑偏,后来在Curve函数里加入速度补偿,才实现了比较直线的前进。这套调参思路放到巡线、避障项目里一样适用,驱动层写好了,后面加什么功能都轻松。
本文还有配套的精品资源,点击获取