陀螺小车传奇之00011000
最后橘猫说
?
最后蜘蛛说
黑胶带绕到第一个弯的时候,我还站在旁边笑。
车走得比我预想中稳。它沿着白纸中央那条线向前,车头偶尔轻轻晃一下,随即又摆正。我刚想拿起手机拍下来,轮子忽然朝左边猛推了一把。
车头转过去了。
它没有转回来。
黑线已经落到车后,两个轮子却还在一左一右地朝同一个方向使劲。车身贴着地面打转,先是一圈,接着第二圈。电机的声音从平稳的嗡鸣拉成尖细的一线,像有人把一根弦越拧越紧。
我伸手去拦。车壳碰到手背时还有点烫。
那一刻,我没觉得是代码的事。基础速度是 40,我把它改成 30。重新下载,小车慢了下来。它几乎走过了那个弯,轮子也没有刚才那么急。我蹲在地上,看着它一点点靠近弯道,指头已经从复位键旁边挪开了。
然后它又往左转了。
这次转得慢些,像一枚收着力气的陀螺。白纸面上被轮胎磨出一道浅浅的灰印。
我把左边的修正量从 20 改成 10,又把右边加了补偿。两只电机本来就不一样,这很合理。再下载,再放下去。它走得更远,甚至在第二个弯前自己挣回来一点。我蹲久了腿麻,站起来的时候膝盖咔嗒响了一声。
第三个弯,它又开始转。
后来我不记得自己一共下载了多少次。PWM 改过,基础速度改过,左右补偿改过。传感器离地高度抬高一点,压低一点;模块上的电位器拧到左边,又拧回右边。桌上的便签纸越贴越多:
30 好像稳
右轮 +3
再低会不动
可能是反光
每一条都像个挺有道理的解释。
小车也很配合这些解释。它总会在我刚改完参数的时候表现好一点:直线走得很直,进入弯道时也肯收车头。可只要它经过某个说不清的位置,左轮就慢下来,右轮推上去,车又开始打圈。电机声再尖起来的时候,我就蹲下去按复位,指腹在复位键上多停了一会儿才松开。
换了白纸,换了黑胶带,甚至怀疑那条线贴得不够黑。凌晨以后,地面上全是被撕下又贴回去的短线段。
后半夜,我做了最后一次修改。基础速度提回去一点,转向差拉大。前几次失败让我觉得它是“转得不够果断”——左修的时候左轮还在拖,右轮推得也不够狠,所以它才会在弯道边缘磨来磨去。
下载的时候我盯着进度条,进度条走完那一秒,我把车摆在线的起点,手指在复位键上停了一秒,才松开。
它冲了出去。
第一段直线很干净。第一个弯,它过了;第二个弯,它也过了。小车跑得比刚才所有版本都漂亮,车头几乎贴着黑线走。我从地上站起来,膝盖又响了一声。
黑线在前面收成一个很窄的弯。
OLED 先闪出:11110000。
车头像被谁攥住了一样,猛地向左甩过去。那一甩很漂亮,甚至可以说是正确的。可黑线没有停在原处。它从车头下继续掠过去,OLED 又跳成 01110000,再跳成 00110000、00011000、00001100。小车没有跟着这些变化收回车头。左轮和右轮仍维持着刚才那道最大的转向命令,把车钉在原地。轮胎在白纸上擦出尖锐的声音,车壳震得发颤。
它原地转了一整圈。
没有停。
第二圈、第三圈、第四圈。黑线、桌腿、我的鞋尖、黑线、桌腿、我的鞋尖,眼前的东西被它卷成一个飞快重复的圆。电机声已经听不出左右轮的分别,只有一团持续拔高的嗡鸣。
我扑过去按复位,没有按准。车壳擦着手背滑开,继续在原地转。最后我直接拔掉电源,它才忽然停下。纸面上留着一圈被轮胎反复磨出的灰痕。
我看着那圈灰痕,蹲得太久,小腿有点发酸。
我把车架回书本上,接上 OLED,拿了一小条黑纸。既然它总在某个位置出问题,那我就不让它跑了。我只让黑纸一个探头一个探头地经过。
屏幕上的数字跟着亮灭。
最左边单独压住,10000000,正常。
左边两路一起压住,11000000,正常。
整排压住,11111111,正常。
再把黑纸压到左侧四路,11110000。小车立刻给出那道最凶的左转命令,和最后一次冲进弯道时一模一样。
最右边单独压住,00000001,还是正常。
我在纸上写下四个勾。笔尖顿了一下,又写了一个勾。
黑纸往右挪一格。
OLED 显示:01110000。
小车没有接住它。轮子还保持着11110000留下的左转,像把那一声命令钉死在了原地。
第一个叉。
黑纸继续移到中间。
OLED 显示:00110000。
轮子还是没有换方向。
第二个叉。
再试右边中间两路。
00001100。
又是一个叉。
我开始把每一种状态都写下来。写到后来,纸上的勾和叉像两群人站在两边。我没有马上看出它们的区别,只觉得那几个叉很刺眼,眼睛有点干,揉了揉。
过了一会儿,我把纸转了九十度。
勾那一边是:
10000000
11000000
11110000
11111111
00000001
叉那一边是:
00110000
00011000
00001100
我的视线从第一列移到第二列。停了一下,又从第一列移到第二列。
那些叉,全是从零开始的。
代码窗口还开着。那张很早就写好的 switch 表安安静静地停在那里,case 00011000: 和 OLED 上的 00011000 长得一模一样。我曾经就是因为它们一模一样,才放心把整张表一次写完。
00011000 在代码里不是一排传感器状态。最前面的零把它变成了八进制数,值是 4608;而我用十进制乘法拼出的 uint32_t value,在那一刻是 11000。
OLED没有骗我。
勾旁边的10000000、11000000、11110000、11111111 前面没有零,所以侥幸走进了正确的 case。00000001 更坏,它无论按哪种方式读都是 1,替这段错误代码作了一次伪证。
最后一次失控的顺序也有了答案:11110000 真的触发了最高速左转;随后循迹状态已经变成 01110000、00110000、00011000、00001100,它们却一个也没有进入对应的 case。没有新命令覆盖旧命令,最高速左转就被一直保留下来。
我坐了很久,看着转圈算法、补偿参数、被撕掉的黑胶带......翻了一页便签纸,把十进制拼接删掉。八路状态不再假装成一串十进制数字,收进真正的掩码。
switch (mask)
{
case 0x18: /* 0001 1000:中间两路压线 */
/*
0b00011000 这种二进制字面量不是 C90 里的写法(C23 才收进标准):Keil 里新的 AC6(armclang) 认它,老的 AC5(armcc) 不认,实测在 AC5 下 C90、C99 两档都报 #53。这一路工程是按 AC5 建的,所以代码里写成十六进制,我当时用的是AC6,所以直接写 0b00011000 是可以的,也很直观。
*/
break;
}
再放回地面时,黑线经过车头中央。小车往左修了一下,又把自己带回了线上。
没有转成陀螺。
台灯底下,那张记满勾和叉的纸还摊着。00011000 被我圈在最中间,笔圈画得有点重,纸面凹进去一道痕。

车在原地一圈一圈地打转,屏幕上跳出来的那串 0 和 1,和代码里写下的判断长得一模一样,可它就是进不去。换过参数、撕过胶带、写过一桌便签之后,线索为什么落在两个数字的差别上?
浙公网安备 33010602011771号