用STM32F401和nRF24L01制作无线调速小车
硬件配置
在做这个小项目前, 考察过STM32F103C8T6
, STM32F401CCU6
和STC89C52
这三个MCU, 并实际跑了一些用例
- STC89C52在代码上要简单得多, 它的问题是没有ADC功能, 所以无法用于遥控器部分, 只能用于小车部分, 而且PWM输出是软输出, 通过主循环实现的. 带ADC功能的STC单片机型号有STC12C5A系列和STC15F, STC15W系列, 但是现在市场价格都很贵, 单单芯片就需要22RMB.
- STM32F103C8T6的功能应对这个项目没问题, 在网上这个型号的代码特别丰富, 问题在于现在价格太贵了, 一个原装芯片的最小系统板价格为38RMB, CH版也要25RMB, 相对于高一代且系统板价格仅为15RMB的STM32F401CCU6没有优势.
- STM32F401CCU6最小系统板价格15RMB, 功能应对这个项目没有问题, 它的劣势是可参考的资料少. STM32F4系列在国内使用的较少, 代码例子在网上少并且很多是针对F407这些高级型号的, 如果用在F401上需要额外的调整.
最终选择的是STM32F401CCU6, 代码上的问题都不是问题, 对吗?
遥控器部分
- 电源: 3.7V 18650锂电
- 3.3V稳压: 一个1N4148二极管
- MCU: STM32F401CCU6最小系统板
- 输入: 双轴摇杆模块
- 无线: nRF24L01模块
硬件部分的说明
- 1N4148能产生0.6V的压降, 对于3.7V的锂电来说是足够的, 实际测试18650两端电压约4.0V, 通过1N4148后输出的电压是3.2V. STM32F401CCU6, nRF24L01和双轴摇杆耗电量都非常小, 1N4148应付这个没问题.
- 因为单节18650电池盒输出线是裸的多芯软线, 不方便直接连到模块上, 并且因为这部分两个模块nRF24L01和双轴摇杆都需要用到3.3V电压, 所以另外用万能板做了一个中转, PCB接线端子用来连接电源线, 3V3通过一个1N4148后接到3V3的排针上, 另一个排针是地线.
- 这个接线端子是从炸掉的L9110s模块上拆的
小车部分
- 电源: 7.4V (18650锂电x2)
- 6V稳压: 串联两个IN4007
- 3.3V稳压: AMS1117 3.3V模块
- MCU: STM32F401CCU6
- 无线: nRF24L01模块
- 电机驱动: L9110双路x2
- 小车底盘
- 48:1减速电机x4
硬件部分的说明
- 电机用的是普通的48:1减速电机, 工作电压为6V, 另外L9110s虽然标称能到12V, 市面上的L9110s大部分到不了这么高, 安全电压在7V左右, 所以这里需要将电压降到6V附近, 通过两个1N4007能产生1.2V的压降
- 运行时每个电机的电流为0.15A, 合计0.6A, STM32F401,nRF24L01的耗电可以忽略不计, 1N4007的工作电流为1A, 应该是够的
- STM32需要的3.3V和电源电压相差较远, 直接用AMS1117模块做降压了
- 因为有4个电机, 所以需要两个双通道L9110s模块
- 电池盒引出的是裸的多芯软线, 不方便直接连到模块上, 因此另外用万能板做了个中间板, 加了接线端子连接电池盒, 正极串联两个1N4007输出到正极排针, 另外还有地线排针, 以及四组排针用于L9110s的输入(各侧的两个通道共用一组PWM)
接线
遥控器部分
MCU需要的接口如下
- UART: 方便调试
- PA9 => USB2TTL的RX
- PA10 => USB2TTL的TX
- SPI: 连接nRF24L01
- PA5,PA6,PA7, PB13,PB14,PB15
- ADC: 两个pin, 连接双轴摇杆
- PA0 => 摇杆AXIS X
- PA1 => 摇杆AXIS Y
- VCC
- GND
与nRF24L01的接线
STM32 | nRF24L01 |
---|---|
PA4 SPI1_NSS | N/A |
PA5 SPI1_SCK | SCK |
PA6 SPI1_MISO | MISO |
PA7 SPI1_MOSI | MOSI |
PB13 | IRQ |
PB14 | CE |
PB15 | CSN |
小车部分
MCU需要的接口如下
- UART: 方便调试
- PA9,PA10, 同上
- SPI: 连接nRF24L01
- PA5,PA6,PA7, PB13,PB14,PB15
- PWM: 4个pin, 输出4组PWM, 分别对应左右侧的两组 L9110
- PA0,PA2: 左侧电机
- PA1,PA3: 右侧电机
nRF24L01接线同上.
功能实现
遥控器部分
这一块主要是通过两个ADC通道采集摇杆电压, ADC采集使用的DMA的模式, 在主循环中定时(几十到几百毫秒)去读取电压, 并转换到[0, FF]区间, 通过nRF24L01发射出去. ADC采集电压时, 每个通道使用4个u16做缓存, 输出的数值是对这个4个数值做平均, 抑制抖动.
涉及的技术名词: UART, ADC, DMA, TIMER, SPI
小车部分
小车部分的功能有几部分:
- nRF24L01的中断接收. 需要将nRF24L01的接收配置为中断模式, 这样只有在遥控端发出指令时, 小车才做相应的动作, 相比在循环中检测接收信息并调整输出的实现方式更及时高效.
- PWM输出控制小车的速度和方向.
- 将接收得到的X轴Y轴向量, 映射到两路电机的方向和强度.
涉及的技术名词: UART, TIMER, EXTI, SPI, PWM
以下具体说明
nRF24L01的中断接收
这部分需要在STM32上新增一个EXTI中断源, 映射到nRF24L01的IRQ PIN脚. 这个中断是低电平触发, 注意在处理完中断后, 需要清空接收缓冲, 不然下一次还会读到旧值.
中断处理的方法内根据接收到的数值调整PWM输出, 实现遥控功能.
这里还有一个定时器TIM3, 当前设置的定时时间为0.5秒, 每次中断处理时会将定时器初始化, 在定时器经过0.5秒后触发时, 会将PWM输出归零, 归零后电机都会停止. 通过这个机制, 在遥控器发出指令后小车会在当前指令下输出PWM 0.5秒, 如果持续收到指令则持续输出, 如果未收到指令, 则在0.5秒后停止输出, 体现在小车运动上, 就是每次指令下小车会移动0.5秒.
PWM输出控制速度和方向
PWM的频率选择: 48:1减速电机是淘宝上最便宜常见的减速电机, 最佳PWM频率是25Hz-50Hz. 这个频率的来源在这里, 里面有很详细的说明和实验测试结果. 我把我关心的部分内容翻译了一下, 可以看这里. 我在实际使用中观察到的结果是符合这篇文章的结论的.
这里多说几句. 关于电机的PWM频率选择, 在网上查了很久, 得到的结果大部分是错误的, 很多人文章里写的频率是6-20KHz. 这里需要注意区分一下, 如果你用的是直流有刷电机, 那么用这么高的PWM频率是会出问题的, 建议在几十到几百Hz的范围去测试.
PWM控制速度比较好理解, 但是控制方向的具体实现需要通过两个PWM配合. 尝试过通过1路PWM+1路GPIO进行方向切换, 但是无法正常工作, 最后还是要通过两路PWM. 根据方向, 设置其中一路PWM输出为0. 这里为了避免出现双高电平(网上有很多人提到双高导致L9110s烧毁), 在程序中先设置输出为0的一路PWM, 再输出另一路不为0的PWM.
X轴Y轴向量映射到左右两路电机
这一块花了我一些时间. 在网络上找到的资料看, 实现方式更多是通过Y轴计算出左右电机整体的前进后退占空比, 然后通过X轴计算左右电机占空比差值, 再将这两个结果叠加, 得到最后的左右电机占空比. 这个计算方式的问题是当工作点在Y轴区间两端的时候, 此时叠加的差值会使Y轴的值超出区间, 但是实际上这个数值是不可能的, 所以要么将两个通道的数值都往回拉, 要么就忽略Y轴超出区间的部分, 都不是很合理.
我使用的计算方式, 是先规定摇杆圆周4个方向上对应LR通道的值:
- 0° => L:FF, R:-FF
- 90° => L:FF, R:FF
- 180° => L:-FF, R:FF
- 270° => L-FF, R:-FF
将摇杆得到的XY轴的值做成向量, 将这个向量投影为圆周上某一点, 再根据圆周上这个点两端的值计算当前点的LR值.
因为摇杆得到的XY轴空间, 实际上是一个正方形, 将其映射到圆上时, 有一个有趣的现象, 当角度位于0°到45°时, 向量的长度等于X轴的值, 而在45°到90°时, 向量长度等于Y轴的值, 这个使得计算简便了许多.
遇到的问题
L9110s发热烧毁
电源为两节18650, 电压为3.7x2=7.4V, 两路pwm输出, 当从0,0 -> 0,全速时, 电机无动作, L9110s发烫然后冒烟烧毁. 这个直接导致两个模块各烧了一片L9110s. 于是上网查相关的资料
相关的讨论
- Is this the reason for burning my h-bridge? https://www.eevblog.com/forum/beginners/is-this-the-reason-for-burning-my-h-bridge/
- L9110 IC goes up in smoke https://forum.arduino.cc/t/l9110-ic-goes-up-in-smoke/367873
- L9110 up in smoke https://forum.arduino.cc/t/l9110-up-in-smoke/381488
- https://forum.arduino.cc/t/fried-my-mega-and-multiple-dc-motor-control-boards-how-can-i-prevent-this/664303
- AB全高状态,静态加电时AB=HH没有发热,但是给1kHz脉冲,9110就冒烟.正常驱动A=H,B=L时,B变H感觉有短时间刹车现象,因为如果A=0,电机停得慢,当A=H,B由L变H就快速停.按理AB=HH是可以的,只是实际结果,静态没问题,动态就烧了 https://www.amobbs.com/thread-4986052-1-1.html
- https://www.zhihu.com/question/52548517
- https://www.icxbk.com/ask/detail?tid=30203
- 对直流电机选择最优的PWM频率 https://learn.adafruit.com/improve-brushed-dc-motor-performance?view=all
因为模块已经带了输出电容和上拉电阻, 所以
可能的原因是
- 电机启动电流过大导致模块烧毁. 电机静态电阻为6.5Ω, 电压7.4V时电流超过1A,
应对方案: 串联一个5Ω的限流电阻, 可以将电流降到7.4/(6.5+5)=0.64A, 避免超出L9110s的最大电流, 运转中的电机阻抗为40Ω - 45Ω, 此时电阻上的分压不到1V, 影响不大. - L9110s耐压超限. 有人说最高到6.5V.
应对方案: 在L9110s输入电压前串联2个1N4007, 将电压降到7.4-1.4=6V, 串联2个时,启动电流1A,正向电阻0.7Ω, 空转时0.15A,正向电阻5Ω - PWM频率过高. 过高的PWM频率会导致电机在低占空比时无法启动,
- PWM同时输出高电平
最终解决方案
- 串联两个1N4007将电压降到6.2V
- PWM频率降到100Hz
有些占空比下电机不动
在逐渐增大占空比的过程中, 有些值下电机不转, 能听到吱吱声, 如果手摸着L9110s芯片, 能感觉到此时有一阵发烫, 所以此时电流到位了, 但是没能驱动电机.
这个原因和前一个问题是一样的, 因为PWM频率过高(17.5KHz), 无法驱动电机, 在将频率降到100Hz后这个问题就没再出现.
小车在运行一段时间后中断灯常亮, 失去响应
经过检查, 是因为在处理nRF24L01接收中断时, 加入了一个延时1ms的处理, 会卡在这个延时函数上, 将这个延时处理删除后就未再出现这个情况