【深圳 IO 攻略】第 17 关:共生环境维护机器人

本文首发于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原创不易,转载请注明出处。
关卡展示

本关的 C2S-RF901 设备会不定期地发送一些长度为 3 的数据包。第一个数字表示需要令电机移动到哪一格,第二个数字表示需要清洁的秒数,第三个数字表示需要加液的秒数。
电机初始位置在第 0 格,电平信号为 50。如果要让电机向左移动一格,需要给电机发送 0x1s + 50x1s 的脉冲信号;如果要让电机向右移动一格,需要给电机发送 100x1s + 50x1s 的脉冲信号。
本关的电机移动较为复杂,因为多了 50 这种信号,所以没办法简单地用 gen 指令来生成脉冲信号了。如果把所有的工作都写在同一块芯片里,即使是 MC6000 也很难写得下。所以我们这里来尝试分工,一块芯片专门用来处理电机的移动任务,另一块芯片专门用来给前面那块芯片发送电机应当到达的位置,等到达后再亲自处理清洁和加液任务。
电路图和代码如下:

上方芯片做的工作是等待下方芯片传输目标位置上来,并给电机发送脉冲信号控制电机移动到目标位置。上方芯片的 acc 寄存器用于记录实时的电机位置,初始值为 0;而 dat 寄存器用于接收从下方芯片发来的目标位置。
首先我们将电机的电平信号初始化为 50(mov 50 p1),然后开始等待下面的芯片发送“位置”数据(slx x2)。等待到信号后,我们将电机的目标位置放入 dat 寄存器(mov x2 dat),并检查电机的当前位置是否已经在目标位置上(teq dat acc)。如果已经在目标位置上,则发送一个信号给下面的芯片,告知下面的芯片该清洁和加液了(+ mov 0 x2),同时开始休眠,等待下一个信号(+ jmp 2,跳到了第 2 行 slx x2)。如果不在目标位置上,则判断目标位置在当前位置的左边还是右边(tcp dat acc)。如果目标位置在电机的左边,则令电机的实时位置 -1(- sub 1),同时给电机发送一秒钟的 0 信号(- gen p1 0 1)。如果目标位置在电机的右边,则令电机的实时位置 +1(+ add 1),同时给电机发送一秒钟的 100 信号(+ gen p1 1 0)。然后,无论电机往哪个方向走,第二秒钟都是给电机发送 50 信号(mov 50 p1, slp 1)。做完以上事情后,跳回到第 4 行(jmp 4)继续判断当前位置是否等于目标位置。只要尚未到达目标位置,就重复做以上事情,直到到达目标位置为止。
下方的芯片做的事情就比较简单了。首先从 C2S-RF901 读入队列中的首数字(mov x0 acc),检查首数字是否为非负数(tcp acc -1)。若为非负数,说明收到了一个长度为 3 的数据包。首数字表示的是当前电机应该到达的位置,将该位置通过 x1 口发给上面的芯片(+ mov acc x1),然后等待上面的芯片给自己发送完成信号(+ slx x1)。收到完成信号后,我们读入队列中的第二个表示清洁时长的数字,并给【清洁工具】端口发送这么长时间的 100 信号(+ gen p1 x0 x1。注意这里不能写成 + gen p1 x0 0,因为我们必须要把上方芯片发来的数字 0 给接收掉,否则会导致运行时阻塞。所以这里必须读入 x1 口发来的常数 0,而不能直接写常数 0)。接着,我们读入队列中的第三个表示加液时长的数字,并给【加液工具】端口发送这么长时间的 100 信号(+ gen p0 x0 1)。若一开始收到的首数字为 -999 时,只需要休眠一秒(- slp 1),进入下一个机器周期就行了。
点击左下角的【模拟】,稍等片刻,便会弹出结算界面:

关于循环的附加知识
循环结构有两种:一种是先执行循环流程,后判断是否继续循环;另一种是像本题一样的,先判断是否进入循环流程,再执行。区别在于,前者的循环流程至少会执行一次,但是少一次跳转;后者多一次跳转,但是存在一开始就不执行循环流程的可能性。两种循环的流程图分别如下:


曾经的谜题里,我们用的都是前者。但是在本题里,我们使用了后者。注意一下我在解析代码时说的这段话:
等待到信号后,我们将电机的目标位置放入 dat 寄存器(mov x2 dat),并检查电机的当前位置是否已经在目标位置上(teq dat acc)。如果已经在目标位置上,则发送一个信号给下面的芯片,告知下面的芯片该清洁和加液了(+ mov 0 x2),同时开始休眠,等待下一个信号(+ jmp 2,跳到了第 2 行 slx x2)。如果不在目标位置上,则判断目标位置在当前位置的左边还是右边(tcp dat acc)。
得到目标位置后,因为存在【电机已经在目标位置上,不需要移动】的可能性,所以存在一次循环流程都不用执行的可能性。所以我们必须先判断 acc 是否和 dat 相等(teq dat acc),相等时直接回传 0 并跳到第 2 行,跳到循环体外面(+ mov 0 x2, + jmp 2)。第 7~14 行是循环体里要执行的部分,然后第 14 行是一条曾经从未见过的【无条件跳转】指令,即不带 + - 号的 jmp 指令(jmp 4)。我们观察后一张流程图,的确就是在循环执行完毕后,【无条件】跳转到循环入口处去重新判定条件。本题的 MC6000 代码里,使用了典型的【先判断,后执行】的循环结构。
【先判断,后执行】需要多一行跳转指令,而深圳 I/O 里,一块芯片最多 14 行代码,代码行数是极其珍贵的资源。所以后一种循环结构,只能是【存在一次都不执行循环流程的可能性】,迫不得已时才会去使用。