1 开发平台
主控芯片:英飞凌TC264或TC377(我是用逐飞主板的)
编程平台:Aurix Development Studio(简称ADS),或者Hightec(需要在乾勤科技那里获取可set active的例程)
烧录工具:ADS的集成烧录工具或者Infineon Memtool (它也可擦除所有残留)
上位机:Vofa+上位机、匿名上位机、串口助手sscom、逐飞助手等。
新朋友如果想快速上手这些基础工具,只需要好好阅读逐飞或者龙邱提供的说明文档pdf,然后拿着芯片、主板、下载器,跑几个例程就知道怎样使用了。毕竟嵌入式开发的基础也是学会调用库函数。值得注意的是,ADS有一些常见的报错项,有些在百度上也可以搜到,比如target pattern contains no ‘%‘,stop。详情可以查阅逐飞科技或者龙邱科技整理的《ADS软件常见报错汇总》。当时就是因为ADS软件一直编译不了,导致我中间有一个月的时间没法正常开发...最后还让技术人员帮我远程诊断了一下。
一般该芯片的使用范式都是:CPU0负责初始化,CPU1负责视觉或定位信息,中断负责读取传感器和输出PWM。具体如何分配资源因人而异,但是注意CPU0是弱核,性能略差,CPU1和CPU2才是强核。
至于上位机,CSDN或者百度上有大量教程。上位机的基本作用也需要知道,一个是显示参数及其波形,还有一个是进行无线调参(尤其是PID参数)。
附上一个无线调参的学习链接(需要自备无线接收模块):
【<PID调参>VOFA+实现实时PID调参 (附源码)】
<PID调参>VOFA+实现实时PID调参 (附源码)-CSDN博客
2 整车速览
-
无RTK的全貌
-
有RTK的全貌
最重要的传感器(保护好,别接反线):
①陀螺仪②编码器③摄像头④GPS/RTK
几个安装的细节点:
(1)取消了飞轮保护罩,防止飞轮卡壳;加装了胶布,防止割手。
(2)车头前方增加挡板(3D打印),用于碰撞目标板。
(3)自制了RTK支架,和电池仓配适,压低重心。
(4)MPU6050陀螺仪放在了摄像头支架上,显得中心对称些(放其他地方调出来的直立效果有点一般)。
(5)灯驱或者其他模块的VCC和GND线一定不能接反,不然会烧!
不同车友的机械结构或者硬件方案不同,要多摸索,找到最适合自己的。当然照抄逐飞或龙邱的也可以。
3 平衡原理
首先摆出一些参考资料供大家查阅:
逐飞的:
18届独轮解析:独轮组之逐飞演示车模浅析
19届独轮解析:独轮组之逐飞演示车模浅析
19届独轮组准备流程及思路介绍:第十九届独轮组准备流程及思路介绍-逐飞科技
龙邱的:
18届独轮【基础原理】:十八届智能车竞赛独轮组——基础原理篇
18届独轮【资源分享】:十八届智能车竞赛独轮组---资源分享
18届独轮【总结开源】:十八届智能车竞赛独轮组——总结开源篇
19届独轮方案:十九届智能车竞赛-独轮组室外方案分享
推文中也有许多注意事项需要认真考虑,比如安装位置或者松紧。
此外,我们需要了解一些机器人学和计算机控制的先导知识:
【姿态角讲解】
机器人行业中我们常说的roll、yaw、pitch是什么? - anobscureretreat - 博客园 (cnblogs.com)
【位置式PID与增量式PID区别浅析】
位置式PID与增量式PID区别浅析_增量式和位置式pid的优缺点-CSDN博客
【智能车培训—并级串级pid】
下文的概念中,动量轮等价于飞轮,受无刷电机控制;行进轮等价于底轮,受直流电机控制。
独轮车的平衡原理可以化用倒立摆模型对其平衡控制系统进行建模,相较于单车,独轮车的平衡控制可以看作是两个倒立摆模型(图片源自逐飞)。
(Pitch俯仰角方向)
(Roll横滚角方向)
暂时先不考虑用于指导转向的Yaw(偏航角)方向。
如左上图所示,如果车模在Pitch(俯仰角)方向右侧倾倒,电机需要带动底轮顺时针转动,从而产生回复力(上方大箭头所示)使得车身保持直立。同理可分析车身在Pitch方向左侧倾倒。
如右上图所示,如果车模在Roll(横滚角)方向右侧倾倒,电机需要带动飞轮顺时针加速旋转,从而产生逆时针的回复力(上方大箭头所示)使得车模保持直立。同理可分析车身在Roll方向左侧倾倒。
在我刚入门独轮车时,想要建立一个函数关系,即车身倾斜多少度,就给电机多大的PWM来保持平衡。但是后来发现,自己的理论功底确实有点匮乏,我不太会运用理论力学的知识来对独轮进行受力分析。也没有具体的实验参数来对独轮车进行动力学建模。因此也就放弃了建立精密控制函数的想法。最终只运用了普通的PID控制。
当然后来和车友交流时发现,除了串并级PID,其实也是可以采用多种控制思路的,比如模糊控制(Fuzzy Control)、线性二次型调节器(LQR)控制、LADRC线性自抗扰控制等。大家可以多多摸索!
那么具体该如何控制电机输出的转矩大小呢?这里采用串级(位置式)PID的方法来实现控制。
首先要解释几种叫法,免得新朋友入门时容易产生误会:
内环中间环外环:指的是串级的三个环。
直立环和速度环:串级的内环和中间环指的是直立环,外环指的是速度环(因为用了编码器)。
平衡环和转向环:平衡环一般指的是Pitch和Roll方向上的平衡控制,转向环一般指的是Yaw方向上的转向控制。
(1)原地平衡:
对于Pitch方向
即底轮(行进轮)的前进方向。采用三级的串级:速度环(外环)、角度环(中间环)、角速度环(内环),各环的名称来源于对应的反馈量,即分别为编码器采集到的速度、陀螺仪解算出的倾斜角度、陀螺仪感受到的倾斜角速度。反馈极性分别为外环用正反馈、中间环用负反馈、内环用负反馈。原因是,我们希望中间环的角度变化和内环的角速度变化几乎被扼制为0,所以是负反馈,而底轮一旦移动,我们希望它的速度越来越快以及时跟上倾倒趋势,所以是正反馈,跟上之后才慢下来。调试PID参数时,极性的正负可由手感来判定。有时候编码器或电机接线接反,参数极性可能就会改变。
主控芯片需要获取编码器和陀螺仪的传感数值,进行相应的解算换算,之后根据指示信息来控制直流电机的PWM,进而控制输出转矩。
控制框架如图所示,原地平衡时给定速度为0,即最外环输入为0。当车模有倾倒的趋势时,陀螺仪最先感受到倾斜角速度,于是内环进行PID运算;当开始产生倾斜角时,中间环也开始进行PID运算,此时电机已开始工作带动底轮行进一小段以维持车身平衡,根据车轮的转速,又传到最外环进行PID运算,直到满足速度为0的约束。由此可见,三个环的工作时序不同,可以每隔一段时间采取一次控制,即可以用2ms、10ms、20ms的时序来从内到外工作(设置个定时器)。
对于Roll方向
即动量轮(飞轮)的旋转方向。也采用三级的串级:速度环(外环)、角度环(中间环)、角速度环(内环)。反馈极性分别为外环用正反馈、中间环用负反馈、角速度环用负反馈。原因是,我们希望中间环的角度变化和内环的角速度变化几乎被扼制为0,所以是负反馈,而飞轮一旦转动,我们希望它的速度越来越快以让车身及时跟上倾倒趋势,所以是正反馈,跟上之后才慢下来。调试PID参数时,极性的正负可由手感来判定。有时候电机接线接反或者陀螺仪换了个方向,参数极性可能就会改变。
主控芯片需要采集无刷电机集成编码器的值和陀螺仪信息,进行相应的解算换算,之后根据指示信息来控制无刷电机的PWM,进而控制输出转矩,决定动量轮的旋转速度。
控制框架如图所示,飞轮的目标转速总是为0的,我们希望原地平衡且不转向时,飞轮能尽可能保持静止。当车模有倾倒的趋势时,陀螺仪最先感受到倾斜角速度,于是内环进行PID运算,输出PWM使得无刷电机工作,当开始产生倾斜角时,中间环也开始进行PID运算,此时飞轮已经旋转,由于最外环正反馈的作用,使得飞轮越转越快,从而尽快让车体恢复平衡,并在平衡之后使得飞轮转速降低接近为0。三个环的工作时序同样可以用2ms、10ms、20ms来从内到外工作。
当Pitch方向和Roll方向都能保持平衡后,独轮车就实现了直立。它可以在原地前后10cm的范围内来回移动,不会跑远。具体实现可参考如下代码(放在CPU中断里执行):
void Balance(void)
{
VelocityTime_ms += SystemData.interrupt_time; //计时器启用
AngleTime_ms += SystemData.interrupt_time;
//外环
if(VelocityTime_ms==SystemData.velocity_time)
{
VelocityTime_ms=0;
//获取编码器值
Get_Encoder();
Pitch_v = PID_Calculate(&Balance_Pitch.v, SystemData.velocity_set-(float)SystemData.Encoder_C, 1); //此处先忽略速度设定
Roll_v = PID_Calculate(&Balance_Roll.v, (float)(SystemData.Encoder_A-SystemData.Encoder_B), 1);
}
//中间环
if(AngleTime_ms==SystemData.angle_time)
{
AngleTime_ms=0;
Pitch_a = PID_Calculate(&Balance_Pitch.a, Pitch_v-(posture.data.pitch-SystemData.Pitch_Zero), 0);
Roll_a = PID_Calculate(&Balance_Roll.a, Roll_v-(posture.data.roll-SystemData.Roll_Zero-Bend_Alpha), 0);//这里涉及压弯角和机械零点的概念
}
//内环
Pitch_w = PID_Calculate(&Balance_Pitch.w, Pitch_a-((float)posture.data.gyro_origin.y),0);
Roll_w = PID_Calculate(&Balance_Roll.w, Roll_a-((float)posture.data.gyro_origin.x), 0);
//输出PWM
SystemData.Motor_A = (int)(Roll_w+Yaw_w);//转向部分先忽略
SystemData.Motor_B =-(int)(Roll_w-Yaw_w);
SystemData.Motor_C = (int)(Pitch_w);
Motor_Out(SystemData.Motor_A, SystemData.Motor_B, SystemData.Motor_C);
//可增加倾倒保护,当倾斜程度太大时,如倒地,清空PWM并将相关数据重置
}
(2)按给定速度行驶:
只需要在Pitch的串级PID外环输入预期给定速度即可。直流电机会追随给定的速度来工作,并由编码器检测来确定速度是否跟上。
(3)转弯:
独轮车的转向是依靠两个无刷电机控制动量轮的反向旋转达到的,是通过前后动量轮的差加速运动,对偏航角方向提供转向力,从而达到独轮车的转向。如图所示,两个动量轮都往顺时针旋转,会带动车身往右转(图片源自逐飞)。
控制时,只需要在两个飞轮的PWM输出部分±转向PWM即可。
SystemData.Motor_A = (int)(Roll_w+Yaw_w);
SystemData.Motor_B =-(int)(Roll_w-Yaw_w);
这里引入一个概念,转向饱和:控制飞轮的无刷电机,所输出的PWM一般有个限幅。当大于这个幅度时就不再产生更高的输出,由于输出不再变化,此时也就失去了控制作用,除非退饱和。当转向程度很大,或者连续转向时,会让输出的PWM比较接近限幅值,于是发生转向饱和,使得车身既无法完成转向,又失去平衡而倒地。
那么,一般这个转向PID的输入偏差来自于室外定位或者室内惯导的计算值,即转向角度。这是转向环的中间环,即角度环。
//纯RTK,没找到目标板的时候用RTK自己的转向参数
if(SystemData.Controller == RTK)
{{Yaw_a = PID_Calculate(&RTK_DT.PID, RTK_DT.expect_yaw.theta, 0);} }
//纯惯导,没找到目标板的时候用专属的惯导参数
if(SystemData.Controller == Navigation)//调用转向优化函数,将角度进行切片,但显示的是实际的角度偏差
{Yaw_a = PID_Calculate(&NavigationDT.PID, NavigationDT.turn_angle, 0);}
//RTK或惯导,且找到目标板的时候,可以用视觉偏差加以修正
if(SystemData.Controller == Navigation_Camera || SystemData.Controller == RTK_Camera)
{Yaw_a = PID_Calculate(&Balance_Yaw.a, (float)SystemData.Visual_Offset, 0);}
但在实际操作中,由于独轮车车模自身存在一些偏差,如:行进轮左右面接触不均匀、传动轴位置不正、传动皮带松动等。最终会导致在没有加入转向控制时,独轮车会向一个方向自然而然走歪。所以这时,需要加入角速度环,即转向环的内环,提供一个阻尼力来阻止独轮车因自身机械结构而产生的偏转。
Yaw_w = PID_Calculate(&Balance_Yaw.w, Yaw_a-((float)posture.data.gyro_origin.z), 0);
将上述两环,即角度环和角速度环,进行串联,即可达到较好的转向效果。有时候可以引入转向外环(负反馈),能一定程度上减弱转向饱和。
(4)压弯(动态零点):
车身在转弯过程中,由于独轮车是靠动量轮的差加速度进行的,长时间循迹会造成动量轮转速加至极限,达到饱和,最终不能控制平衡和转向。除了增加转向环的负反馈差速度环(外环)可以缓解之外,还有个合适的办法是压弯,也称之为动态机械零点。
在这里,压弯并不是为了减少路程而跑的更快,而是用于减缓飞轮PID的饱和。通过压弯让离心力与重力分量相抵消,可以更自然地保持姿态平衡,从而使得飞轮可以更低负担地实现转向(以下推导引自逐飞科技)。
首先建立离心力等于重力在横滚角方向的分力的数学表达式得:
sin(θ) × mg + m×v²/r = 0
等式中的θ为我们需要压弯的角度,v是当前移动速度,但是需要压弯角度我们并不能非常直接的计算出来,我们只能通过传感器得到小车需要转向的角度α,因此增加一个转向pid计算,令:θ=α× K2。并且根据路径的分析可以得知,小车倾斜越多,绕圈的半径越小,因此以上公式中的半径r和压弯角θ可以视为固定的比例关系,记作r=θ×K1,
因此我们得到了最终的压弯公式:
sin(α× K2) ×g + v²/ (α×K2 ×K1) = 0
将其增加到横滚角的角度环影响机械零点即可,得:
Machine_zero = Machine_zero + sin(α× K2) ×g + v²/ (α×K2×K1)
我们可以尝试简化一些,其中sin(α×K2)×g可以看作是转角α与一堆比例运算的结果,v²/(a×K2×K1)可以看作是速度v与转角α一堆比例运算的结果,那么如果将这些运算直接看作一个系数,就可以得出一个极简的计算公式:
Machine_zero = Machine_zero +α×K2 + v²×α×K1
在直行时,K1和K2以0来处理即可;进入转弯状态时,将它设置为调出来的合适的参数,就可以比较好地实现压弯。这样的动态机械零点,就是为了解决转向饱和而产生的。
我的代码如下,只随意用了一个压弯参数SystemData.Bend_K,将求出来的压弯角放在平衡代码的中间环部分即可:
float Bend_Alpha=0; //压弯角度
void Bend_Handle(void) //转向压弯角的计算处理
{
float turn_angle=0;
if(SystemData.Turn_Flag)//切点才压弯
{
if(SystemData.Controller == RTK) turn_angle = RTK_DT.expect_yaw.theta;
if(SystemData.Controller == Navigation) turn_angle = NavigationDT.turn_angle;
Bend_Alpha = (SystemData.Run_Speed*SystemData.Run_Speed) * turn_angle * SystemData.Bend_K; //可调压弯参数,注意极性的正负和数量级
}
else Bend_Alpha = 0;
}
有时候动态机械零点的思想可以用于Pitch方向上,促进变速、撞击板子和过凸起。
以上全部就是平衡部分所依赖的原理。
创作不易,如果觉得对您有帮助,可以打赏作者予以支持喔~