机器人共享项目原因制作是他的第一个基础RISC-V机器人项目。以下文字详细记录了机器人制作的软硬件组成和核心算法。对机器人感兴趣的朋友可以收集或关注他(https://github.com/myyerrol/line_tracking_robot),与他沟通。文章中提到的代码可以,先看机器人演示效果:
巡线解迷宫机器人是一种小型三轮机器人,可通过红外反射传感器实现自主巡线和解迷宫(尚未实现)。基于芯的硬件核心GD32VF103处理器独立设计RV-STAR开发板主要负责处理红外反射传感器收集的模拟数据,并将处理后的数据发送到电机驱动模块,以准确控制两个直流电机的转向和转速。为了提高项目的整体开发速度,机器人的所有部件都被使用TB其机械结构和电气连接采用孔板直接焊接。
在软件层面,经典的机器人内部集成PID闭环控制算法可以根据模拟输入数据自动调整整个系统的动态平衡,使机器人最终能够快速平稳地沿着黑线移动。除此之外,该项目最大的亮点是我根据GD32VF103处理器库函数手册等相关信息RV-STAR开发板包装了一个类别Arduino语法的静态链接库,取名为RVStarArduino,不仅如此,我还可以基于以前的自己Arduino机器人项目中的一些代码无缝移植,大大提高了项目的开发效率,还有一些刚接触GD32嵌入式开发的小白用户可以快速实践,消除他们对传统嵌入式应用开发的恐惧。毕竟,不是每个人都喜欢研究底层技术。
最后,这个项目的代码可以来自我GitHub有兴趣的朋友可以下载到当地玩。当然,如果您在使用过程中遇到一些问题,欢迎使用GitHub上给我提交Issues或者在文章评论区留言,有空肯定会及时回复。
巡线解迷宫机器人图1
电机电源层是整个机器人硬件架构中最底层、最基本的单元。它主要包括三个部分:直流减速电机、电机驱动模块和电源管理模块。关于直流减速电机,我这次使用的是目前市场上广泛使用的小型电机,考虑到机器人总装后的负载和我对机器人巡线速度的需求,我最终购买了它N20电机减速比为30:1,额定电压为6V时,其空载转速为300RPM(即每分钟300转),可以保证动力系统在大负提供相对稳定的扭矩和速度输出,使机器人的巡逻过程更加高效。
电机电源层正面
我选择了智能车中最常用的电机驱动模块,与其他模块相比,它最大的优点是,它可以在驱动两个大电流电机的同时保持非常小的体积,因为我的巡逻迷宫机器人非常小,以节省宝贵的内部空间,TB6612FNG模块几乎是我唯一的选择。至于如何使用这个模块,网上有很多教程供参考,这里就不解释了,但是有两点需要注意:一是TB6612FNG的3个GND引脚是互通的;二是它STBY引脚必须连接高电平才能正常驱动电机。
最后,我用了电源管理部分TB上一个低成本,这个模块最强大的部分是它不仅可以外部使用USB接口给锂电池充电,锂电池放电时电压从3.7V升到5V,功能相当于原充电模块 对于寸土寸金的机器人内部空间来说,升压模块简直就是梦幻般的存在。此外,当外部负载所需的电流小于50时,该模块还具有智能省电功能mA电源输出将在30秒内自动关闭,从而有效减少电池电量的损耗。
电机电源层背面
作为上层决策单位,主控处理层是整个机器人硬件架构的核心。在本次巡线解迷宫机器人中,主要用于采样红外反射传感器回传的模拟数据,并根据PID算法进行数学操作,最后以逻辑电平信号的形式将计算结果反馈给电机控制板,实现直流电机转向和转速的精确控制。当然除了最核心的功能之外,主控处理层这块儿将来还会涉及到与带有IIC接口的OLED显示屏和基础ESP8266的Wi-Fi模块之间的数据交互和其他内容可能会充分发挥主控性能。
RV-STAR开发板示意图(引用官方论坛)
接下来,为了让大家对主控有一个基本的了解,让我们简要介绍一下这款芯推的款式(以下内容引用官网论坛)
RV-STAR是一款基于GD32VF103 MCU的RISC-V评估开发板,提供板载调试器,Reset和Wakeup用户按键、RGB LED、USB OTG,以及EXMC、Arduino和PMOD扩展接口等资源。
事实上,与开发板本身的良好性能和丰富的外设资源相比,我更关注它与硬件完全兼容Arduino这一点。玩过Arduino所有的学生都知道,Arduino一之所以一经推出就风靡全球,是因为它在软硬件层面的易用性,而且RV-STAR支持硬件Arduino扩展,这无疑可以让其复用很多现有的Arduino扩展板,如果能在后期打开软件语法的壁垒(有兴趣的话可以关注我写的RVStarArduino库),就能真正实现RV-STAR无缝接入巨大而成熟的无缝接入Arduino在生态学中,我认为这可能是未来国内处理器生态环境建设的捷径之一。
外设模块层位于机器人硬件架构的顶层,主要用于与外部环境进行数据交互。本项目最早的规划应为红外反射传感器OLED显示屏与Wi-Fi由三个核心外设组成,如透传模块,但由于时间原因,我没有将后两个添加到机器人中,所以这次我将详细介绍它们
如下图所示,机器人底盘前端布置的一排黑色物体是红外反射传感器。考虑到机器人的体积和主控的操作能力,我使用了设备选择DIP封装的
电机电源层背面的四路红外反射传感器
古人曾经说过:
具体如何配置IDE官方手册和论坛详细介绍了设备驱动的方法,这里就不赘述了。接下来我主要想说说我是怎么做到的。
由于新版的PlatformIO最低仅支持Python 3.6.所以像我一样使用旧版本GNU/Linux不能使用系统的人PlatformIO唯一的解决方案是通过第三方软件源安装新版本Python 3.具体操作如下:
$> sudo add-apt-repository ppa:deadsnakes/ppa $> sudo apt update $> sudo apt install python3.8 python3.8-distutils
安装完成后,需要手动操作系统ython 3环境变量指向最新安装的这个版本:
$> sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1
$> sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 2
$> sudo update-alternatives --config python3
配置成功后即可按照正常流程在VSCode中安装PlatformIO扩展插件,但是注意如果你的系统使用的是类GNOME桌面环境的话,那切换到高版本Python 3很有可能会导致GNOME命令行终端软件无法使用,所以在装完PlatformIO后需要再次使用下面这个命令将Python 3的版本切换回去:
$> sudo update-alternatives --config python3
至此,如果你能在重启VSCode后看到如下界面即表明PlatformIO扩展插件已成功安装。
PlatformIO的主界面
接下来我介绍一下如何在PlatformIO中安装RVStarArduino库,其实这个步骤非常简单,如下图所示,各位只需要在PlatformIO的库管理器中搜索RVStarArduino关键词,并在其介绍界面中点击【Add to Project】即可将库文件添加到自己的项目中。除此之外,你也可以直接在platformio.ini配置文件中添加库依赖参数,这样PlatformIO在编译项目代码的时候会自动下载对应版本的库文件。
RVStarArduino库的主界面1
RVStarArduino库的主界面2
巡线解迷宫机器人的软件部分主要涉及
// 获取每个红外反射传感器的模拟值
// Get the analog value of each infrared reflection sensor
uint16_t a0 = analogRead(A0);
uint16_t a1 = analogRead(A1);
uint16_t a2 = analogRead(A2);
uint16_t a3 = analogRead(A3);
// 使用加权求和的方式来表示系统误差
// Use weighted summation to represent systematic errors
float turn_error = 0.0;
turn_error = turn_error + g_turn_error_k0 * a0;
turn_error = turn_error + g_turn_error_k1 * a1;
turn_error = turn_error - g_turn_error_k1 * a2;
turn_error = turn_error - g_turn_error_k0 * a3;
接下来就到了最经典的PID部分了,所谓PID就是比例、积分和微分的英文缩写,主要应用于各种闭环控制系统中。根据反馈器件的类型,PID中常见的控制环有:速度环、角度环和转向环,那大家可以猜猜本项目中使用的是哪种控制环吗?没错,因为我选择的是可以返回位置信息的红外反射传感器,所以机器人实际应用的是转向环PID算法。关于PID算法的原理网上有很多详细的资料可供参考,这里我就不再过多介绍了,大家可以尝试直接阅读下面的代码来理解其中的精髓。
// 计算转向环PID
// Calculate turn PID
g_turn_p = g_turn_kp * turn_error;
g_turn_i = g_turn_ki * (g_turn_i + turn_error);
g_turn_d = g_turn_kd * (turn_error - g_turn_error_last);
float turn_sum = (g_turn_p + g_turn_i + g_turn_d) / 100;
// 根据计算结果与当前所处状态控制电机的转速或转向
// Control the speed or direction of the motor according to the
// calculation result and the current state
int16_t motor_speed_a = MOTOR_SPEED_REF;
int16_t motor_speed_b = MOTOR_SPEED_REF;
// 规定机器人水平向左为负向右为正,此时如果PID计算结果为正,则机器人需要向右偏移,
// 右侧电机应降低一定的速度,速度变化值由PID计算结果的绝对值表示,反方向同理
// It is stipulated that the horizontal left of the robot is negative
// and the right is positive. At this time, if the PID calculation
// result is positive, the robot needs to shift to the right, and the
// right motor should reduce a certain speed. The speed change value is
// represented by the absolute value of the PID calculation result.
// The same goes in the opposite direction
if (turn_sum > 0) {
motor_speed_b = motor_speed_b - turn_sum;
}
else {
motor_speed_a = motor_speed_a - abs(turn_sum);
}
setMotorSpeed(MOTOR_A, motor_speed_a);
setMotorSpeed(MOTOR_B, motor_speed_b);
核心算法完整代码如下所示:
float g_turn_kp = 20.0;
float g_turn_ki = 0.0;
float g_turn_kd = 5.0;
float g_turn_p = 0.0;
float g_turn_i = 0.0;
float g_turn_d = 0.0;
float g_turn_error_k0 = 2.0;
float g_turn_error_k1 = 1.0;
float g_turn_error_last = 0.0;
uint32_t g_timer_turn_pid = 0;
void cacluteTurnPID(void) {
uint32_t ms = millis();
if (ms - g_timer_turn_pid >= 50) {
#ifdef DEBUG_PID_CYCLE
printf("Time turn: %lums\n", ms);
#endif
// 获取每个红外反射传感器的模拟值
// Get the analog value of each infrared reflection sensor
uint16_t a0 = analogRead(A0);
uint16_t a1 = analogRead(A1);
uint16_t a2 = analogRead(A2);
uint16_t a3 = analogRead(A3);
#ifdef DEBUG_ADC_VALUE
printf("A0: %u, A1: %u, A2: %u, A3: %u\n", a0, a1, a2, a3);
#endif
// 使用加权求和的方式来表示系统误差
// Use weighted summation to represent systematic errors
float turn_error = 0.0;
turn_error = turn_error + g_turn_error_k0 * a0;
turn_error = turn_error + g_turn_error_k1 * a1;
turn_error = turn_error - g_turn_error_k1 * a2;
turn_error = turn_error - g_turn_error_k0 * a3;
// 计算转向环PID
// Calculate turn PID
g_turn_p = g_turn_kp * turn_error;
g_turn_i = g_turn_ki * (g_turn_i + turn_error);
g_turn_d = g_turn_kd * (turn_error - g_turn_error_last);
float turn_sum = (g_turn_p + g_turn_i + g_turn_d) / 100;
#ifdef DEBUG_PID_VALUE
printf("Turn E: %.2f, Turn P: %.2f, Turn I: %.2f, Turn D: %.2f, "
"Turn Sum: %.2f\n",
turn_error, g_turn_p, g_turn_i, g_turn_d, turn_sum);
#endif
// 根据计算结果与当前所处状态控制电机的转速或转向
// Control the speed or direction of the motor according to the
// calculation result and the current state
int16_t motor_speed_a = MOTOR_SPEED_REF;
int16_t motor_speed_b = MOTOR_SPEED_REF;
// 规定机器人水平向左为负向右为正,此时如果PID计算结果为正,则机器人需要向右偏移,
// 右侧电机应降低一定的速度,速度变化值由PID计算结果的绝对值表示,反方向同理
// It is stipulated that the horizontal left of the robot is negative
// and the right is positive. At this time, if the PID calculation
// result is positive, the robot needs to shift to the right, and the
// right motor should reduce a certain speed. The speed change value is
// represented by the absolute value of the PID calculation result.
// The same goes in the opposite direction
if (turn_sum > 0) {
motor_speed_b = motor_speed_b - turn_sum;
}
else {
motor_speed_a = motor_speed_a - abs(turn_sum);
}
#ifdef DEBUG_MTR_VALUE
printf("Motor A: %d, Motor B: %d\n\n", motor_speed_a, motor_speed_b);
#endif
setMotorSpeed(MOTOR_A, motor_speed_a);
setMotorSpeed(MOTOR_B, motor_speed_b);
g_turn_error_last = turn_error;
g_timer_turn_pid = ms;
}
}
遇到的问题
1)我在测试TB6612FNG驱动板的时候,发现无论自己怎么设置电机始终不转,在排查硬件问题的过程中,我先后用万用表测试了电源管理模块输出和TB6612FNG驱动板的逻辑输入引脚,发现电源供电电压与信号高低电平均正常,所以首先可以排除电源供电不足和逻辑信号错误所导致电机不转的可能,之后我对驱动板的相邻引脚做了短路测试,偶然发现其BIN0、BIN1和GND引脚短接了,而短接出现的原因比较特殊:因为我是将两个电机直接固定在双面敷铜的洞洞板上,此时电机的外壳与正面的焊盘紧密接触,而背面的BIN和GND信号正好走在电机外壳的区域内,焊接时焊锡过孔导致两个信号与电机外壳联通,从而出现短接。但神奇的是解决短路问题后电机依旧不转,最后我是在面包板上复现TB6612FNG驱动电路的时候才发现自己竟然犯了一个非常低级的错误——忘记在代码中设置电机的PWM信号,我当初下意识认为PWM只用于调速,只要AIN或BIN信号设置对就能让电机转起来,可惜事与愿违。这个经历给我的启示在于做项目时要以官方提供的芯片或模块手册为准,不要想当然地按照自己的想法去做,否则后期出现问题的概率会比较高。
2)在测试四路红外反射传感器的时候,我发现板子串口输出的数值竟然全部一样,经过单步调试后最终确定是自己编写的RVStarArduino库里的analogRead函数存在Bug,使得函数每次只能返回第一次设置的ADC通道的数值。解决方法是根据网上STM32 ADC多通道数据采集范例,将通道配置库函数从初始化阶段移动到analogRead函数中,这样系统就能根据不同的引脚编号来读取相对应的ADC通道数值了。
3)转向环PID算法中存在浮点变量和与之相关的运算过程,但是令人费解的是,程序在执行过程中始终无法在串口里打印出浮点数值,后来经过仔细排查发现系统自带的newlib为了优化编译后的体积,默认并没有开启浮点数运算功能,所以程序一遇到浮点变量就直接跳过执行了。解决方法其实很简单,只需要在编译器的链接参数中指定【-u _printf_float】即可。
巡线解迷宫机器人是我基于国产处理器来开发嵌入式应用的首次尝试,通过一个月左右的理论学习与动手实践,我学会了如何在项目正式开始前做需求分析,如何根据机器人所要实现的功能来做软硬件方案的选型,如何在遇到Bug的时候通过调试机制来定位并解决问题,如何规划机器人内部空间使硬件布局更加合理与美观等等。虽然最后因为其他项目节点的缘故,我并没有给机器人添加OLED显示和Wi-Fi传输模块,同时也没有实现解迷宫功能,但是我至少验证了采用RISC-V架构的国产嵌入式处理器无论是在运行时性能还是在开发便捷性等方面均不输于目前主流的STM32,所以本项目成果从原理验证的角度上来看还是达到了自己的预期。
最后我一直坚信学习某个理论知识最好的办法就是
END