校电赛记

  校电赛终于结束了,说实话效果还算不错,尤其是对于我们十分落后的硬件而言。

  树莓派是找学长白嫖的,摄像头是拼夕夕14块先用后付的,stm32是f103c8t6的,电机编码器精度是不够的,红外对管有个是坏的,6050是接触不良的,oled屏幕是3块钱的,摄像头延伸架子是撞断的,车架是stm32学习套件自带的,滚珠轴承是生锈的,车轮是打滑的。在以上种种限制下,我们竟然能够做出来一个能完成任务的智能循迹小车,真是不可思议。

  说起此次参赛的原因,我倒也算是受邀参加。前段时间一直在做我的EGGER Vision项目,然后隔壁寝室的老哥经常路过。看到我在捣鼓这些小玩意,又正好他自己也在学一些嵌入式开发的东西。于是我俩一拍即合,打算一起参加明年的智能车竞赛。不过在此之前,可以借此次校电赛的机会积累经验。毕竟我还从未有过此类比赛的经验。

  刚开始被老哥拉进队伍之后,我原以为都是大佬可以带我飞,而我只需要躺赢看他们操作就行。后来发现事情不太对。

  首先是在和那位老哥第一天接触后,我就发现了一个大问题:他的电脑上充满了开屏广告以及各种垃圾软件,时不时就会跳出来一个弹窗。这对于一个计算机专业的学生来说是非常不可思议的。

  然后在赛题发布后,我们需要开始对项目进行分析、计划与筹备。在这一阶段首先需要的是为项目设计方案并构建总体框架,需要从宏观上去思考,这一步是抽象的,不是具体的。有关具体功能实现的问题这类旁枝末节的内容不应该在这一环节被重点讨论。然而,我的队友们似乎有点本末倒置了。尤其是那位老哥,总是针对于某一个环节大做文章。

  好在我暑假时候作为组长带领团队开发过游戏项目,因此略懂一些项目方案设计与规划。最后我根据赛题要求给出了四种设计方案,分别是单侧红外对管循迹、双侧红外对管循迹、侧边摄像头循迹和中间摄像头循迹。我还针对每种设计方案分析了其优缺点。在和队友根据目前的硬件情况以及时间期限进行一番敲定后,最终决定采用方案三:侧边摄像头方案。主要原因是因为赛题有数字识别的要求,而数字是摆放在赛道侧边的。然后根据方案,他们和我都领到了各自的任务。

  当我问起我的队友们过去的比赛经历,他们的回答和我当初设想的截然不同——和我一样没有任何的成功的比赛经历。看来想要抱大腿是不现实的了。这个观念在第一天晚上去完实验室后变得更加深刻。

  那天白天我上完课,晚上收拾下便去了实验室。由于老哥和另外一位队友已经在实验室待了一下午了,我当时觉得他俩应该完成了电机转速差转向了吧。至少电机总能动起来了吧。但是令我惊讶的是,他们居然工程的环境都没有配置好,更不用说敲代码烧录的事了。我很难想象,两个人一整个下午竟然没有配置好一个开发环境。后来我了解后知道,原来是原先两个人的版本不统一,会导致奇怪的报错。因此需要统一版本,而那位老哥的电脑杂七杂八的广告占用又会不断将这一进度滞后。所以第一天就在一事无成的惊诧中结束了。

  后来的几天,我的进展还算顺利。完成了树莓派上opencv的配置、赛道边线识别代码的编写还有OLED屏幕驱动部分代码的编写,一切都在按照计划发展。当我满心欢喜地将电机驱动模块和屏幕显示模块对接后,期待着串口能够顺利传输数据并且在屏幕上显示。然而,现实的执行结果给我当头一棒:OLED根本没有显示数据。

  为了排查问题所在,我花费了整整一天的时间。先是查看代码逻辑,没有问题后用串口助手查看数据能否被正确发送和接收。发现两个模块单独运行时候都能顺利接收和发送,但是一旦整合起来就会出现问题。我怀疑过是发送和接收频率的问题,怀疑过是通信协议的问题就,怀疑过是接线错误的问题,怀疑过是线路接触不良的问题,但是都无济于事。最后事情的转机出现在我的一次插拔txrx数据线的时候,我发现将tx拔掉,OLED屏幕居然就能正常接收数据了!真是诡异至极。只能说嵌入式最怕就是机魂不悦了。但是这个问题归根到底我认为还是线路的问题。

  在解决了这个问题后,小车的开发得以继续进行。现在,电机驱动32能够接收到树莓派发来的数据了。那么眼下的任务就变成了如何让电机驱动32能够根据树莓派的数据对左右电机进行控制从而实现循迹左转右转。

  最初的方案是在电机驱动32上通过pid调节根据输入信号和反馈信号的比较产生输出信号。理论上来说这个并不难实现,但是那位老哥的电机驱动32项目架构太混乱了,包括但不限于:代码缩进混乱、括号嵌套层级混乱、命名混乱、变量声明定义混乱、模块划分混乱。这就导致了整个工程的耦合性极高,改动一个模块可能会对另外几个模块造成不可估量的影响。其中最让我难以置信的是在主循环中有一个printf函数不能删去,一旦删去,电机的速度环控制就崩溃了。然而时间紧迫,重构工程已经是来不及了,因此只能将错就错,我哭笑不得得在那行printf后边加上注释——“绝对不能删掉!”。

  正式因为电机驱动32工程结构的混乱,导致在实现电机转速差pid控制的时候,始终不能达到理想的效果。我们不得不采用它的下位替代方案,只采用比例控制。幸运的是,在电机驱动32上使用比例控制非常顺利,至少小车可以循迹拐弯了。但是随之而来就会出现一个新的问题:小车会出现严重的蛇形行驶。这是比例控制始终无法避免的一个问题。一开始我们仍然打算在比例控制的基础上做文章,添加死区、分段控制、添加输出限位等等。虽然效果有所改善,但是由于误差的累积,小车运行到后边仍然会开始蛇行,像是一头雄狮在巡视它的领地。

  所以,仅仅采用比例控制必然不是最终的解决方案。要想实现流畅的转向以及尽可能避免蛇行,我们必须要对其进行优化。最初,我试图通过数学上的分析,希望能将系统运行的整个过程以一个或者多个函数表达式来呈现。但是受限于我不高的数学水平以及对自动控制原理这门课的一知半解,失败是必然的。于是我开始求助ai,希望能从它们的建议中得到一些启示。

  “pid”,这是它们众多不同回答中的共同点,都无一例外地提到这个词。看来我们在兜兜转转了一圈之后,还是要回到pid上了。为了优化过去的比例控制,设计出一个更好的解决方案,我必须更进一步去了解pid的本质。p,比例环节,i,积分环节,d,微分环节,通过这三个环节对系统进行改造,从而让系统能够稳定完成任务,这就是我总结出的pid本质。但是我认为在实现电机转速差控制这个任务中,并不是这三个环节都要考虑。对系统起到关键作用的是比例环节和微分环节。积分环节是用于记录累积误差的,这部分我认为是不需要的。

  最后,一通分析猛如虎,我将原先的比例控制改造成了比例微分控制。但是这一次为了避免在电机驱动32上运算导致难以预知的问题,我决定让这部分控制的代码放在树莓派上执行。树莓派执行完成后只需要将得到的转速差数据通过串口发送给电机驱动32即可,然后电机驱动32再根据转速差对左右电机设定速度。

  经过一番有如炼丹般的调参后,小车终于能够顺利循迹了!不过有一个问题,就是速度有些慢了。我试图将电机速度调快些,然后根据新的速度调一个新的参数,但是最后的效果都不尽人意。这就和另一个问题有关系了,时延。在树莓派摄像头拍摄到画面,到计算出与赛道边线的像素距离,然后再到计算得到目标转速差,最后到这个转速差作用于电机的时候,各个环节都存在时延。正是这无处不在的时延导致高速下的控制不能及时,经常会出现转向延迟和转向过度的情况,就像法拉利sf25。往往转向过度会伴随着转向延迟同时发生,当树莓派计算出转速差之后,在转速差作用于电机之前,电机仍然会保持先前的状态运行。这在宏观上体现就是明明到弯道了,小车需要转弯了,但是却还没有转弯,会继续向前走一小段距离。当转速差正式作用于电机之后,小车会发现和赛道边线距离远了,需要近一些,导致多转一些,造成了转向过度。

  其实想要解决这个问题也好办,最简单粗暴的办法就是让摄像头往前移一些,提前拍到一些画面去抵消控制上的延时。但是这个方案被硬件原因限制了——摄像头延伸杆断了。不过归根到底,还是当时在设计方案的时候没有考虑周全,并没有将控制时延计算在内,这大概就是侧边摄像头方案的劣势了。如果采用前置摄像头方案,那么它就能拍到更多的画面,能看到更远的距离,但是对于侧边数字识别任务它就无能为力了。所以我们当初敲定侧边摄像头方案主要是因为我们只有一个摄像头,但是又要实现循迹又要实现侧边数字识别。

  虽然小车速度提不起来,但是好在我们有个保底,起码它能顺利跑完两圈了。

  在完成了OLED驱动和基础部分的循迹控制后,我的任务就只剩下了树莓派上数字识别的部署了。最开始,实现数字识别这个任务是交由另外两位女生去做的。她们顺利实现在自己电脑上进行静态和动态的数字识别。但是由于是在自己电脑上运行的,因此我在将工程移植到树莓派上之前需要对图像处理的代码进行优化,以尽可能减少运算量以及提高识别准确率。

  我在接手了她们的工程后,首先让我最惊讶的是,发给我的Python代码竟然是写在word文档里面的!?我还是第一次遇到这种情况,大为震撼。第二点是在代码中有着大量的ai注释痕迹,似乎她们把ai调教得不错。但是关于她们对这个代码实际了解多少以及能否清楚各个函数是用来干什么的,这我就不得而知了。

  我不得不对这些代码进行大刀阔斧地改动。或许是受到前段时间阅读马斯克传的影响,我一上来就将无关的图像处理函数统统删去,只留下二值化。马斯克主张删掉没有用的东西,“如果删除的部分最后加回不到 10%,说明删得还不够”。事实证明我删掉的太多了,对于只有二值化的数字图像根本无法实现识别要求。为了提高准确率我不得不加回一部分图像处理的函数。最终的实现效果确实还不错,我可以很自信的说静态识别的准确率达到了100%!但是动态识别的话就一言难尽了。由于摄像头硬件性能的限制,一旦小车开始跑起来,那么捕捉到的一帧画面就是模糊的,根本识别不了数字,为此我也无能为力。

  赛题的最后一个要求是让小车实现无引导线八字绕行。由于是无引导线,摄像头便失去了用武之地。我们决定用陀螺仪来实现惯性制导。然而我们没有像那些国赛选手一样有很高精度的陀螺仪器件,只有最最低端的mpu6050,它的控制精度远远不足以支撑小车准确完成八字绕行的任务。不过我们还是没有放弃,即使是使用开环控制,也要让小车能够跑起来,哪怕跑得不好。

  说起这个6050,我们真是为它操碎了心。首先是它的初始化,不知道是接触不良还是本身硬件问题,iic通信的初始化每次都要复位好几次才行。然后是零漂问题,一旦出现零漂就会让开环控制的角度值出现很大的偏差,导致无法完成八字绕行的任务。即使我们在比赛前不断安抚机魂,它仍然在比赛最后一圈中出现零漂,最后一圈的成绩也因此无效了,可惜啊可惜。

  不过总体上来看,我们的作品完成度还是挺高的。即使最后没有拿奖,我还是积累了不少的经验。求人不如求己,哈哈。

  话说回来,参加这个比赛是真累啊。比赛前一天晚上,在实验室调试到两点,然后和老哥去学校外边打算搞点吃的,但是马路上一片冷清,店门都早已打烊,只有零零散散几个打扮妖艳的小情侣在你侬我侬和零星几辆车匆匆驶过,扬起的风令我瑟瑟发抖。

  终于有家面馆开着,我俩各要一碗面填了填肚子就回去睡觉了。