摘要
运动底盘是移动机器人的重要组成部分, 不像激光雷达, IMU, 麦克风, 音响, 摄像头这些通用部件可以直接买到, 很难买到通用的底盘. 一方面是因为底盘的尺寸结构和参数是要与具体机器人匹配的; 另一方面是因为底盘包含软硬件整套解决方案, 是很多机器人公司的核心技术, 一般不会随便公开. 出于强烈的求知欲与学习热情, 我想自己 DIY 一整套两轮差分底盘, 并且将完整的设计过程公开出去供大家学习. 说干就干, 本章节主要内容:
1.stm32 主控硬件设计
2.stm32 主控软件设计
3. 底盘通信协议
4. 底盘 ROS 驱动开发
5. 底盘 PID 控制参数整定
6. 底盘里程计标
2.stm32 主控软件设计
上一节搭建好了底盘的 stm32 主控硬件, 现在就来说说怎么开发配套的 stm32 软件. 关于建立 stm32 工程, 使用 stm32 开发库, stm32 软件调试方法等基础知识就不多说了, 有需要的可以查阅相关资料学习, 我觉得 http://www.openedv.com/ 《正点原子》的开发资料写的还可以. 我就直接从底盘控制的项目入手, 直接进行项目中各个功能需求开始分析讲解, 如图 11, 是我的底盘控制 stm32 工程项目.
(图 11) 底盘控制 stm32 工程项目
2.1. 电机控制
电机控制分为两个部分 (电机转向控制, 电机转速控制), 这些都集成在了电机驱动芯片 TB6612FNG 里面, 所以只需要用单片机的 IO 口产生控制转向的高低电平和控制转速的 PWM 波就能实现.
首先, 初始化 IO 口作为输出脚, 用于产生高低电平输出来控制转向, 实例代码如图 12.
(图 12) 电机转向控制 IO 口初始化
然后, 用通用定时器 TIM4 的通道 CH1 和 CH2 分别产生两路 PWM 输出用于两个电机的转速控制, 定时器默认引脚分配如图 13.
(图 13)stm32 定时器通道默认引脚分配
初始化通用定时器 TIM4 的通道 CH1 和 CH2 为 PWM 输出, 实例代码如 14.
(图 14) 电机转速控制 IO 口初始化
最后, 将电机转向和速度控制的操作封装在一个函数中, 便于其它地方调用, 实例代码如图 15.
(图 15) 电机转向和速度控制封装
2.2. 编码器数据读取
编码器对底盘来说至关重要, 一方面底盘通过编码器的反馈进行 PID 闭环速度控制, 另一方面底盘通过编码器进行航迹推演得到里程计用于后续的定位与导航等高级算法中. 这里用到的编码器是正交编码器, 所以直接使用通用定时器的输入捕获中的编码器模式来读取编码器. 采用通用定时器 TIM2 的通道 CH1 和 CH2 捕获 encoder1 的 A 相和 B 相脉冲, 采用通用定时器 TIM3 的通道 CH1 和 CH2 捕获 encoder2 的 A 相和 B 相脉冲.
先初始化 TIM2 作为编码器 encoder1 的捕获, 实例代码如图 16.
(图 16) 初始化 TIM2 作为编码器 encoder1 的捕获
然后, 将读取编码器计数值的操作封装在一个函数中, 便于其它地方调用, 实例代码如图 17.
(图 17) 读取编码器 encoder1 计数值封装
最后, 编写 TIM2 计数溢出时的中断处理函数, 实例代码如图 18.
(图 18)TIM2 计数溢出中断处理函数
同理可得 TIM3 捕获 encoder2 的代码实现, 这里就不在赘述了.
2.3. 串口数据收发
串口 2 是数据接口, 负责接收上位机发送过来的控制指令, 同时将编码器值返回给上位机; 串口 1 是 debug 接口, 负责接收上位机发送过来的版本信息请求, PIDm 默认值恢复, PID 值设定等调试指令, 同时将程序中的 debug 打印信息返回给上位机. 但是在底盘正常工作时, 只需要连接串口 2; 串口 1 是预留出来给有需要自己动手修改 PID 参数使用的.
首先, 配置串口 1, 先对串口 1 的输出进行 printf 函数打印支持, 实例代码如图 19.
(图 19) 串口 1 的输出进行 printf 函数打印支持
然后, 初始化串口 1, 实例代码如图 20.
(图 20) 初始化串口 1
最后, 编写串口 1 接收中断处理函数, 此函数主要进行对上位机发过来的数据进行协议解析, 实例代码如图 21.
(图 21) 串口 1 接收中断处理函数
接下来, 介绍串口 2, 初始化串口 2, 实例代码如图 22.
(图 22) 初始化串口 2
然后, 将串口 2 发送数据的操作封装到函数中, 便于其它地方调用, 实例代码如图 23.
(图 23) 串口 2 发送数据封装
最后, 编写串口 2 接收中断处理函数, 此函数主要进行对上位机发过来的数据进行协议解析, 实例代码如图 24.
(图 24) 串口 2 接收中断处理函数
到这里, 串口有 1 和串口 2 的数据发送与接收都编写好了, 依据我们定义的 usart2 数据通信协议和 usart1 调试通信协议, 上位机就可以编写对应的程序来跟底盘的串口 2 和串口 1 进行通信了. 关于通信协议的具体内容, 将在后续做展开.
2.4. 电机速度 PID 控制
我在底盘中采用的是增量型 PID 算法, 编程涉及到的数学表达式有 3 个, 分别是:
- e(k) = target_value - current_value
- delta_u(k) = Kp*[e(k)-e(k-1)] + Ki*e(k) + Kd*[e(k)-2*e(k-1)+e(k-2)]
- u(k) = u(k-1) + delta_u(k)
将这 3 个数学表达式封装到函数中, 便于其它地方调用, 实例代码如图 25.
(图 25) 串口 2 接收中断处理函数
电机 1 与电机 2 采用同样的 PID 算法, 所以电机 2 的 PID 算法代码实现就不赘述了. 关于 PID 参数的整定方法, 将在后续做展开.
2.5. 周期性控制
通过上面的讲解, 各个模块的驱动代码都准备就绪了, 现在需要产生一个周期性的过程, 在里面实现编码器计数值采样, PID 控制等具体实现. 这里采用定时器 TIM1 产生一个周期性的中断, 在中断处理函数中实现各模块的具体操作.
首先, 配置定时器 TIM1, 实例代码如图 26.
(图 26) 配置定时器 TIM1
然后, 编写中断处理函数, 实例代码如图 27.
(图 27)TIM1 中断处理函数
2.6.stm32 主控软件整体框图
通过上面的讲解, 对底盘控制的 stm32 程序实现有了一定的了解, 接下来就来做一个总结.
先来看看 main() 函数实现, 如图 28.
(图 28)main() 函数实现
结合上面 TIM1 中断处理函数, 不难发现, 整个 stm32 程序的执行过程:
a. 在 main() 函数中初始化各个模块;
b.TIM1 中断处理函数周期性的读取编码器值, 反馈获取的编码值, PID 控制;
c. 剩下的就是串口 1 和串口 2 的通信交互.
具体 stm32 主控软件整体框图如图 29.
(图 29)stm32 主控软件整体框图
需要说明的是, 在周期性循环体中, 要首先读取编码器的值, 来保证严格的等间隔采样.
来源: https://www.cnblogs.com/hiram-zhang/p/10403822.html