做嵌入式,如何优雅地debug?
Bug,是码农的眼中刺。在古老的电子管计算机年代,赫柏抓住人类历史上的第一只bug后,码农和bug开展了几十年的斗争。
鲁迅(没)说过:“赛场如战场,bug满天飞。”
平时1%才发生的bug,比赛时就200%地出现了,电控人员肩负着debug的重任。
嵌入式眼中的赛场
然而,人类就喜欢作死,平时放任bug不管,事后转发锦鲤祈祷。
某不愿意透露姓名的RoboMaster工程师实在看不下去,气得写了一篇嵌入式方法论。它不会手把手教你写代码,但是会告诉你如何避免写bug。
建议朋友们敲bug代码前看一看。毕竟,天道好轮回,该背锅时就背锅。
本期作者
RoboMaster嵌入式工程师 罗小p
不管你们说什么,XX才是世界上最好的语言!
嵌入式是啥?
专业一点说,嵌入式系统(Embedded System)是一种嵌入机械或电气系统内部、具有专一功能和实时计算性能的计算机系统。
而说人话,嵌入式就是把代码写进单片机里,再把单片机塞进一堆冷冰冰的机械中,代码就能控制机械动起来。
电路板
我们要学的,就是如何写出最顺滑、不卡机的代码。
机器人上的嵌入式技术
嵌入式一般用c语言编程,只需要上手c语言,就可以玩单片机啦。
用了单片机,是不是还要设计电路板?不不不,我们只要看懂原理图的管脚接入就够了,设计的事就丢给硬件的队友吧。
在机器人身上,有哪些嵌入式需要做的事呢?
比如步兵,最简单的是LED控制,让LED灯亮和灭。
接着就是串口通信,这是最常见的通信方式,就像一根电话线,各部分数据用它来通信。
串口的DMA用在遥控器的接收,遥控器能不能控制机器人,就看它了!
DMA工作流程
再比如传感器,电流传感器会用ADC模拟量,掌握了ADC采样原理也会方便开发。
还有PWM,它的输出可以控制摩擦轮的转速,调节枪口射击的速度。
TIM定时器用于常用的定时任务,可用于任务分配。比如爪子倾倒弹药箱后,停顿0.5秒等弹丸掉完后再放回箱子。
英雄取弹
如果要控制官方产品(例如电机),就会用到通信总线CAN。如果它崩了,电机就疯了。
而SPI通信板搭载陀螺仪,可以采集角速度值;CRC校验可以用在裁判系统的通信。
创建和调配任务,还能用freeRTOS(一种操作系统)。
嵌入式知识框架
假设你看懂了以上知识,那么你已经成为半个嵌入式人了,我们将进入下一个环节——收集需求。
如果你没看懂,不要方,因为小R也没看懂,还是骄傲地进入了下一环节。
1. 确定需求
磨刀不误砍柴工,在下手之前,要确定好需求,也就是搞清楚机器人想怎么动。
比如英雄机器人登岛取弹,得知道登岛取弹机构的运动顺序。
首先打开爪子→开到柱子旁→云台看屏幕→按键关闭爪子→机器人自动抬升→打开涵道等等……最后下岛过程反向即可 。
英雄登岛取弹流程图
而且,写代码不是一个人的事。自从开始写代码,我的头不秃了,眼睛不瞎了,人际关系也变好了。
昨晚,操作手就跪着把需求递给我,让我帮他实现:按右键开摩擦轮,按左键发射,按WSAD键就能前后左右跑。
不过得记着,没确定的按键要用宏定义,方便以后操作手再跪着找你改需求。
比赛遥控器
作为高贵的嵌入式高级的搬砖工,我们不在乎爪子的机械结构,只关心爪子的电机怎么转。
所以,还要给机械队友讲电机的类型和使用范围,让他们去算力矩校核;还得普及传感器的精度和用法,才能合作选出最适合的电机。
毕竟,他们啥都不懂,唉。
轮子上用的电机
硬件是嵌入式的战友,一般用来甩锅,只需要沟通电子元件的布置和外部接口。
根据以上,可以列出hin多软件需求。以下不要求背诵,可快速浏览。
1. 主要功能的需求,比如奔跑、取弹和射击,包括:
a. 触发功能的各种条件(如:控制流,运行状态,运行模式)。
b. 定义各种条件下所有可能的输入(包括合法的输入和非法的输入)。
c. 各个功能间可能的相互关系。
d. 功能性的主要级别(如:基本功能、逐步实现的功能,和可变化的功能)。
e. 落实待定功能的实现时间、负责人员,和内容说明。
2. 辅助性功能的需求,也就是可有可无,但有了会更好的需求。比如上位机发送数据到PC端,会存在数据延迟。如果列出数据,就可以对比两边的数据准确度。
a. 描述输入和输出的关系。
b. 描述输入和输出,其编号和名称必须唯一。
3. 软件和硬件,或者其他外部系统接口的需求:
a. 硬件接口:端口号,指令集,输入输出的内容,初始化过程,通道号和信号处理方式。
b. 软件接口。
c. 通讯接口 :指定通讯接口和通讯协议等。
4. 程序上的非功能性需求:
a. 时间性能指标:软件处理、功能恢复,和输入响应的时间。
b. 精度性能指标:软件处理ADC的精度,控制的精度等。
c. 稳定性指标:也就是程序遇到故障时,能有多稳。比如遥控器掉线时,如果有掉线保护,机器人就不会失控。
5. 程序不能做什么。例如“遥控器按关闭,机器人就必须停止”的代码应该独立于其他代码,不然其他代码一崩,机器人就不受控了。
2. 程序编写
确定完需求,终于到了敲代码环节。
这是嵌入式人最隆重、最端庄的仪式,需要遵守格式的规则。万一稍不小心触犯的某条,会有损程序员的格调。
写代码时,风格要统一。别上面一段是清新简约风,空格只打2下,下面就粗犷豪迈风,空格狂敲5、6、7、8下。
不仅影响阅读心情,不利后期维护,还容易危及自己的生命安全,被原地暴打。
下面举几个我对自己的要求:
1. 命令方式统一
常见的命名方式有帕克斯命名法,驼峰命名法和匈牙利命名法等。
哪种方法都可以,但是作为一名嵌入式人,我们只选一种,绝不花心。
例如机器人运行循环函数,它们分别长这样——
驼峰命名法 :robotRunLoop
帕斯卡命名法:RobotRunLoop
匈牙利命名法:fnRobotRunLoop(其中fn代表function 函数的意思)
下划线命名法:robot_run_loop
选一个你喜欢的,用就是了。
2. 命名准确无歧义
命名应该准确表达,别支支吾吾的。
例如计数——
错误示例: int i,a,b,c…
正确示例: int temp_count;(temp可以代表局部变量)
如果是具体含义的计数,比如底盘电机的计数,那就是:
int tempChassisMotorCount;
如果计数是为了求数组的最大值,那就是:
int countForBufMax;
3. 排版整齐
a. 适当加空行,能逻辑分割代码。
b. 缩进不要混用制表符和空格,因为编辑器打开它们时,显示的缩进量不一样。
c. 太长的语句,合理换行。
格式统一
格式不统一
4. 定期检查代码
好马不吃回头草,优秀的嵌入式人才不吃这一套。
我们都会定期检查代码,及时debug,以免太久远的代码被遗忘,改起来太费劲。毕竟,贵人多忘事啊。
编程规范可参考《华为技术有限公司c语言编程规范》来制定。网上搜一下就能下载。
3. 风险同步
ddl是第一生产力,我们要预估任务时间,给自己定一个每日小目标,在ddl来临之时能够收工回家。
计划很重要,因为优秀的嵌入式人是需要合作的,你不知道队友会不会给你挖出个坑来。
比如调试哨兵是机械、嵌入式和视觉一起完成的。机械设计结构和装配,完成后,嵌入式让它动起来、控制云台和射击,最后视觉调试自动识别功能。
用装甲板测试识别功能
如果机械设计不合理,导致嵌入式调试出bug,机械背锅!如果嵌入式参数有问题,导致机械撞墙损伤,嵌入式背锅!
万一信息不通畅,导致机械做完后嵌入式还在外花天酒地,一起背锅!(视觉表示:反正我不背锅。)
最后的结果是,整体进度被延迟,谁都逃不掉锅。
为了避免成为背锅侠,我用血泪史总结出了常见的风险类型:
机械背锅侠请点击看:《机械方法论》
1. 需求风险:在前期要测试部分模块(比如一个爪子),讲究快速验证;中期调试整台机器,实现功能的稳定;后期配合队友调试视觉。
在整个项目,嵌入式要化身多种角色,完成各种需求。
2. 人员风险:有时会遇到人员不足的情况,我相信优秀的嵌入式人靠个人魅力也能把他们骗回来。
3. 时间风险:学生嘛,课业很多,希望你在进度完成的同时,也不要挂科。
4. 技术风险:优秀的我们也会偶尔犯错,由于代码设计不合理,平时不注意维护上,机器人有可能一上场就疯了。一定要提前发现问题,找到解决方案。
5. 备料风险:曾有人不小心烧掉电路板,还没有备用板,差点在赛场原地痛哭。
如果你放不下颜面当众大哭,一定要备好货,常坏的零件更要囤多点。
备用零件
不过有时,你跑得再快,ddl还是追上了你。
不要方,项目延期很正常,我们应该大声地同步风险,避免坑了队友。因为,嵌入式人永不认输。
4. 代码测试
现在,到了隆重的测试程序环节。是驴是马,牵出来遛一遛就知道了。
作为优秀的嵌入式人,需要有条理地、系统地准备测试。
假设要测步兵的功率控制,就要列出——
测试目的:机器人在模拟场上运行,保证底盘功率不超限制。
测试对象:步兵的底盘功率控制策略。
测试人员:了解步兵操作、裁判系统设置的人。
测试时间:排除太过寒冷的气候。因为电池放电受到温度的影响。
精度要求:场地的沙地纹地胶厚度3mm,坡度最大17°,还要考虑实际使用时,机器人会先跑一会再冲上通过斜坡。
测试还要考虑机器人的使用环境。不管它是在-10℃的水里潜伏,还是在80℃的沙滩上狂奔,都要尽量满足实际的工况。
像传感器的数据会受温度影响。如果机器人在冬天的哈尔滨测好,拿到夏天的深圳可能就崩了。
赛场上检测机器人
测试分成静态测试和动态测试,一般是先静态,后动态。
1. 静态测试就是,我看看代码,但我不运行。通过肉眼观察代码、界面和文档,来找出错误。静态也有两种方法:
a. 队友互相打脸纠错,不仅可以看别人代码的组成,还能为别人debug。
b. 如果不放心队友,还可以用静态代码检测工具。历经千辛万苦,我终于找到了一个免费工具:cppcheck。
cppcheck界面
2. 动态调试就是,输入测试数据,看代码跑得顺不顺。
一般有下面几种方法:
a. 监控任务:监控电机、传感器等模块的工作状态。比如发现电机疯转就赶紧关停。
b. 心跳任务:看LED、OLED屏等显示设备,来判断工作状态。比如看到红灯闪烁8下,就是第8个电机有问题。
电路板测试
c. 错误码显示:用LED灯、显示屏、串口等显示错误码。还是上一张图,假如第6个绿灯变红,就说明第6个电机有问题。
d. 运用调试工具:比如J-link调试器可以使用J-Scope工具来完成绘图(而且软件是完全免费的)。
J-Scope
甚至还可以用来调试云台的PID。
绿线是 pitch的角速度设定值
黄线是pitch角速度反馈值
紫线是pitch的角度设定值
蓝线是pitch的角度反馈值
熟练使用各种工具简直可以事半功倍,是优秀嵌入式人居家旅行的必备良药!
测完后,你就会发现,想象中是完美的,运行中是崩溃的。当初脑子是进了多少水,才会写出如此反人类的代码。
5. 代码需要持续优化
优秀的嵌入式人会不断地完善代码,这让程序更加高效,功能结构更健全。
如何优化代码?首先要了解程序的性能,再找到不合理的地方来优化。
比如陀螺仪的通信,常用SPI等待读取。但是在CPU在读取时,需要等待通信完成,这是个无意义等待,如果用SPI的DMA方式就可以节约时间。
还应该了解代码的函数运行。对于函数用了多少次除法、乘法运算、调用了多少次、是否存在函数递归等等,心里要有数。
之后再优化那些调用次数较多,或者使用除乘法较多。
生活在于不断学习
机器人的程序一般没有复杂的运算和处理,CPU不会满足不了计算要求。如果哪天发现程序跑不通了,请不要怪CPU,先从自己身上找问题。
在RoboMaster论坛,有官方和参赛队的开源程序。有事没事看看别人的代码,你会发现,同样的功能竟然有各种方法实现。
比如PID调试,下图代码虽然短,但是每用一次就要粘贴一遍,整个程序会非常长。
而下图是一套PID结构体,想用的时候,改改参数就可以,整个程序就会干净整洁。
6. 阶段性胜利
这时,希望你的计划都已经完成,作为阶段的结束,可以去聚餐庆祝啦需要复盘一下工作。毕竟,我们可是要做优秀的嵌入式工程师啊!
首先反思错误,把代码问题、延期原因记录下,避免下次再犯。
然后回顾工作,把完成的任务、画过的软件框架图,和代码的介绍写成文档,为后人种树。
作为优秀工程师,写技术文档也很严格,要包括:
1. 需求说明要点:例如机械设计需求、机器人性能需求,和底盘控制需求。
2. 操作使用、维护的要点:例如遥控器上和键盘的操作,机器人如何维护。
3. 硬件结构要点:例如IO口分配,硬件接线图。
4. 软件设计框架和流程图:例如软件架构、任务分配、任务功能,和内存分配。
5. 核心代码讲解。
6. 软件测试报告。
7. 嵌入式大礼包
最后再安利一些工具和开源代码,希望未来的嵌入式工程师们能够飞速成长,等哪天有缘相遇了,还望轻虐。
RoboMaster 2016步兵开源代码:
https://www.robomaster.com/zh-CN/resource/download
RoboMaster电控、视觉算法开源代码:
https://github.com/Robomaster
Git:用于代码托管,方便版本迭代回溯。
Vscode:常用的代码编辑,调试软件,可以在上面进行代码编写。
Beyongcompare:文本比较,用于不同版本的代码文本之间对比。
Xmind:思维导图,帮助思维发散以及记录要点。
Staruml:画图软件,用于流程图等绘制。