现实世界应用——2019 电赛经验与教训

/ 2评 / 1

总地而言,电赛终于结束了。我的苦难也告一段落了。

舒适区

大学是一个持续的走出舒适区的阶段。在这个阶段如果你愿意踏出你所熟悉的领域,那么你的收获自然颇丰。当然,你也可以呆在自己的舒适区里安稳地混四年,但这样总觉得对不起自己交的学费。

但是走出舒适区远比走路要困难许多。尤其是当你踏入一个完全未知的领域的时候,很难保证自己不惊慌失措。而更恐怖的是,你将再也无法从你之前的知识中获取任何参考,能够陪伴你探索未知领域的,只有被成为「素质」的那一部分——将你在学校学过的知识完全刨掉之后留下来的那部分。

但是我向来是不怯去尝试新东西的。于是,一个写惯了软件的孩子,毅然决然地踏入了嵌入式硬件设计,具体而言,四轴飞行器的设计的深潭。

我们都知道,水域是一个具有很奇特性质的地理结构,尤其是河流。河流从两岸到中心,其深度的变化并不是均匀的。两岸或许是很浅的坡,但是再往中央进发,就会遇到一个断崖,深度突然增加,让无防备之心者沉入深渊。

于是我,作为一名愣头青,就一个猛子扎入了这通向地心的裂缝。「无知者无畏」,已经不能分辨这句话是褒义还是贬义。

有限的一切

如果说什么东西给我留下了深刻的印象,那么就是一切东西都很有限。

在之前写软件的时候,我们(作为码畜级别的程序员)通常采用图灵-冯·诺依曼模型。通俗地讲就是:我们认为计算机资源是无限的:我添加的任何功能都可以全速执行、我可以随意调取内存和磁盘,而且我可以从数学上证明我的代码,因而它将永远不会出错——完美!如果这个程序运行太慢或者有问题,我们可以通过性能分析和调试立刻找出问题所在,再不济继续加内存和 CPU 就好了,反正过一段时间就会有更好的硬件出现的。

但是当我们开始在单片机上写程序的时候,上面那个假设就直接跨了。单片机的性能严重受限,计算资源更是少得可怜。主频 75MHz, 32KiB 的编程空间、1KiB 的持久存储空间、只能硬件处理单精度浮点数。这样一片指甲盖大小的东西,就要用来控制一架四轴飞行器冲向天空!

幸而我们可以通过外围设备分担计算压力。但是外围设备也要占用计算资源!你可以驱动一块屏幕,但是这个绘图行为将会吃掉接近 45% 的处理器时间;你可以选择同时采样多个传感器数据,但是其中一些传感器可能无法在采样时足够快地返回需要的数据;你可以做十分复杂的计算力求获得完美的数据,但是你的滤波器将会吃掉 30% 的存储空间,以及占用 65% 的处理器时间,而且在你把参数调好之前,只会让数据变得更糟。

另一个有限就是,你的尝试次数十分有限。每一次失败的尝试都有概率会损坏当前的硬件。而且并不是每次尝试都是有意义的——你没法暂停世界来观察你的飞行器的状态是否是你期望的那个状态、是谁导致飞行器进入了这个状态,你能看到的只有两种情况:飞机飞起来了和飞机坠毁了。至于为什么能飞,以及为什么坠毁,就需要长期的调试经验才能猜出个所以然,而且还不一定正确。如果坠毁次数足够多,你会耗尽硬件,从而无法继续调试,只能再去购买新硬件。

这又将我们带到了时间的有限性。四天三夜听起来是一个很长的时间段,但是实际算一下不过 85 个小时(从第一天 7:30 到第四天 20:30)。任何购置硬件的操作都是不科学的——最快也需要 48 个小时才能送到手里,而 48 个小时已经超过了一半竞赛时间了。

所以,一切都很有限,一切都很不同。你再也没有暂停一切的能力——无论是在调试时,还是在设计时。而没有暂停一切的能力对于程序员而言是一个很痛苦的限制。在这一刻,我终于理解了这个世界运行的速度。

硬件可靠性

如果说什么东西给我留下了最深刻的印象,那么就是一切东西都不可靠。

写了 8 年程序的我,已经把「当代码行为错误时,先怀疑自己,再怀疑工具,永远不要怀疑硬件问题」这句话写到了骨髓里。于是,这句话在这次比赛中也成功地被证明无效了。当硬件失效时,我根本无法定位问题。

硬件失效事实上是一个很微妙的问题——硬件失效并不是硬件损坏。有时或许是因为电压不稳、或许是因为姿势不对、或许是因为接触不良,传感器决定不向主控报告信息,或者不向主控报告正确的信息。这样会导致程序卡死或者发生异常,从而带跨一切。比较好玩的是,很多外设并不需要主控一直控制就能自主运行,比如计数器。这就意味着电机将不会在主控死机时停止,从而直接失控。

而后呢,就是无数程序员的梦魇了——不可复现的错误。如果说多线程会产生一些非常奇怪的不可复现错误是一种恐怖的话,那么硬件在你的单线程代码中产生不可复现的错误就直接是爱手艺级别的纯粹魇魔了。没错,在单线程代码中,可以产生不可复现的错误。而且不是由于变量未初始化导致的——单片机的启动代码会将未初始化的区域填 0 的,而且对于未初始化变量的使用是会受到编译器警告的。

虽然严格意义上而言,嵌入式程序设计并不是单线程的,它还有硬件中断这一茬,但是良好的设计方式会保证中断服务程序是纯的或者核心逻辑是纯的,从而互相不会影响。

但即使是这么简单的操作、这么理所当然的设计模式,也会随着硬件失效而失效。

软件可靠性

C 语言里有一个关键字是 volatile. 这个关键字如果你从未深入学习过 C 语言可能根本不知道其存在。它用于指示一个变量可能会在编译器所不能预知的情况下被修改。对于嵌入式开发而言,这个关键字可以说是很常用了。但是没人会告诉你这个事情,甚至即使是已经在嵌入式开发有所建树的也可能会忽略其存在,因为:

Keil uVision 这种工具,默认的编译是直接开 -O0 的——即强制编译器不做任何优化。

所以呢?所以就有那么多手动优化的奇技淫巧啊!代码库里充斥着难以理解的 hack, 而且有些 hack 其实并不能提高运行速度,只能算是一种混淆。

我怎么知道的不能提高运行速度?我掐的表。为什么掐表?因为你没法在嵌入式系统上跑性能分析器,你也无法在嵌入式系统上运行单元测试和部署自动化测试——这些东西都是资源大头。但正是单元测试、自动化测试和性能分析组成了现代软件开发可靠性的基石。

但我终究,一半是为了更好的性能少写点手动优化的内容,一半是为了能回到自己的舒适区,把代码库扔到了 CLion 准备配合 GCC 进行开发。紧接着就被画满了整个屏幕的警告和错误打到想买票回家。嵌入式编程的画风确实比较生猛。

关掉大半个警告,修掉文件名大小写导致的错误,终于编译通——没有,我又忽视了一个细节:Keil uVision 的默认 C 语言标准是 C89, 不是 C99 或者 C11. 所以就有很多很好玩的 C89 迁移到 C11 上的问题。

但是就是这样的代码,可以驱动四个电机以最高 2300 转/分钟的速度悬停!我不知道我应该采取怎样的态度来继续开发,或许应该是和见了克总一样的心态继续工作吧。或许,直白地说,这就是屎山,我的工作,就是游到中间,再弄点半稀不干的把它粘好,拍两下,差不多能用得了。

但我的工作要仅仅是写代码就好了。

现实世界应用

程序员写程序久了,就会有一股自己能够掌控世界的错觉。

我一开始对这句话是嗤之以鼻的。程序员构筑了现代社会的信息产业,大众的衣食住行早已在程序的掌控范围之内。我掌控了程序,我就能书写未来。这怎么能说是错觉呢?

现在我清楚地明白了这句话的意思:代码终究是代码,软件终究是软件。就像你可以花心思去破解别人电脑的密码,但是永远都比不上拿一把扳手照着那个人的胳膊打一下来得快。如果我们的软件没有产生物理后果,那么它终究只是变化的数字而已。

所谓现实世界应用,就是会产生物理后果的应用。无论他们是股票交易应用、外卖平台,还是工控系统、自动驾驶系统,它们都会对现实世界——你的手能摸到的这个世界,产生(有时是不可逆的)影响。这时候你必须考虑到你的产物会对这个世界带来什么影响,以及更重要的,这个世界如何影响你的应用程序。你需要考虑到各种不完美的情况并加以应对。

现实世界是不完美的、现实世界是不完备的、现实世界是混沌无序的。尽管有很多编程规范明确地提到了「世界」的非完美性质,但那些规范或者习惯只是这个世界的冰山一角。程序员,这个坐在电脑前敲键盘的人,终究是没有能掌控世界。

「我即使被关在果壳之中,仍自以为无限空间之王。」

从字面意义和深层意义来看,程序员们确实如此。

P, I, D

关于 PID, 我还会单独写一篇笔记来详细讨论。这里就是简单写一些感想。

魔鬼。这东西就是纯粹的魔鬼。三个让人不得安宁的数字。

这三个参数让我感觉头疼的另一个原因是:我不会飞四轴。没错,尽管我莽了四轴的题目,但是我根本就不会手动飞。这也是在整个过程中最让人感到痛苦的事情:你需要依赖队友才能完成一个在其他人看来非常基本的操作。而当队友也不怎么给力的时候,就彻底没办法了。

火上浇油的是,这三个参数和飞行器的行为密切相关。如果其值略有偏差,轻则抖动不稳,重则摇摆坠毁。这让人更加失去调节这三个数字的信心。

而更加让人感到痛苦的是,PID 的调节是一个相当具有重复性和依赖直觉和经验的操作。于是随之而来的挫败感在一次次尝试中被放大、积分、不收敛、震荡。

总结

实际上,本次电赛我几乎没有学到任何知识——这样说似乎有些刻薄,或者显得自己很智障或者自大。但是有时候真相就是这样残忍:电赛没有教会我任何事情,它让我知道了我在很多地方是做错了,但是没有给我指明正确的方向。所以,我的收获是经验和教训,而不是知识点总结。

在我写下这个总结的时候,还没有评测,但是我已经提前为自己判了死刑。那架飞机要是能完成一个任务,就已经是撞了大运了。

如果说有什么教训,那就是永远不要为了比赛开实验性支持,尤其是这种实验性支持是在舒适区之外的支持。尽管在之前,我为了几个比赛开过了实验性支持,但是它们的结果都不尽人意;这一次则是在预料之外,情理之中——完全的失败。冒险固然是好事,年轻时多冒险是有益于个人发展的,至少通过一次失败你可以认识到自己的不足,以及是否愿意去弥补这个不足。

总之,这一次事件不加修饰地告诉了我:你现在根本不会搞嵌入式开发,更不会搞机械设计。你以为翻了翻 Electric Motors and Drives, Cybernetics, 写几个公式、瞎打了几行字就能玩转无人机?做梦呢?

或许是在做梦。或许这个梦还不会醒。但总体而言,知道自己不知道,比知道自己知道更重要。知道自己知道,那么可以解答一个领域内的问题;而知道自己不知道,则代表你仍有探索的空间,这世界依然充满了好奇和发现。我或许无法获得什么成就,但是我将获得知识,而知识是好的——我知道比别人知道我知道要更为重要。尽管我可能已经在这里失败了,但失败将不会成为前进的终点。

好吧,毕竟还是要稍微正能量一下的。文章太丧的话,显得这个人很没意思。

  1. 初心说道:

    哎,今年电赛目前来说 打得非常*** 已经不想比了 也不知道以后路怎么走

  2. gzh说道:

    希望人没事🙏

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.