Shenzhen I/O 过关流程

《Shenzhen I/O》是一个汇编模拟器,跟《人力资源机器》是同一个类型。只不过《Shenzhen I/O》非常硬核,如果你没有编程基础,建议不要买,如果一定要买,那大概率会变成一个纸牌游戏。

前两天被steam推荐到这个游戏,原价48块,但是刚刚好在特价促销,打骨折只要16块,我就剁手了。

游戏一上来就直接丢给你一本40多页的说明书,比较类似于芯片的datasheet,而且还推荐打印出来翻阅。感觉在这里就会劝退很多咸鱼了啊~

这篇文章就是记录我的解题过程了,大概率不是最优解,但是平均水平应该还是有的吧。偶尔有些不太好说明的地方,我会直接写伪代码。

Lv.01 - 模拟安全摄像头

1
2
* *活动状态*和*网络*是连接 LED 灯的简单输出。
* 按照验证面板中的提示控制*活动状态*和*网络状态*输出,使其显示一组重复的固定信号。

这个感觉没啥好说的,观察给定的信号输出图,就能写出来。。

这里大概解释一下slp指令。我的理解就是类似于“结束回合”。控制器在一个时间周期内应该是可以执行无限量的指令,所以一般来说都至少需要一个slp 1来进入下一个时间周期。当然在这一关,由于电平信号较长时间不变,所以可以直接slp跳过多个时间周期。

  • 成本:6
  • 用电量:60
  • 代码行数:12

Lv.02 - 控制信号放大器

1
2
3
* *控制输入*是连接工厂设备的简单输入。
* *控制输出*是连接工厂设备的简单输出。
* 将*控制输入*的信号值乘以 2 后,复制到*控制输出*。

这关要求做一个信号放大器,基本上就是检查有没有看过手册了,因为这里需要用到acc(通用累加器)

一开始想复杂了,以为要手动输入信号,后来发现是自动输入的,那就简单了。。

  • 成本:3
  • 用电量:300
  • 代码行数:5

Lv.03 - 诊断脉冲生成器

1
2
3
* *按钮*是连接按钮的简单输入。
* *脉冲*是连接电子设备的简单输出。
* 请按照验证面板中的指示,在按下*按钮*时生成脉冲,松开*按钮*时停止脉冲。

这关要求生成脉冲信号,要用到条件分支了。当然,游戏里是一个大大简化模型,跟直接写if else差不多了。

伪代码差不多是这样。

1
2
3
4
5
6
if (p0 == 100) {
if (acc == 0) acc = 100;
else acc = 0;
p1 = acc;
sleep(1);
}
  • 成本:3
  • 用电量:281
  • 代码行数:6

Lv.04 - 动画 ESPORTS 标志

1
2
3
* *点击0*和*点击1*是连接显示组件的简单输出,分别与点击鼠标的两幅动画对应。
* *喝0*、*喝1*和*喝2*都是连接显示组件的简单输出,分别与三幅喝饮料动画对应。
* 按照验证面板中的提示控制显示组件,使其显示一组重复的固定信号。

这关要求控制一个应援灯牌的闪烁,开始有点意思了。总体来说不难。最右边那个控制器不知道有没有办法优化进9行代码,如果可以那就只需要MC4000了。

  • 成本:11
  • 用电量:341
  • 代码行数:23

左边的控制器用了条件,我稍微写点伪代码好了。核心思想就是把acc当成flag用。顺便说一下,这个if else是支持多行的。

1
2
3
4
5
6
7
8
9
10
11
if (acc == 0) {
p0 = 100;
p1 = 0;
acc = 100;
}
else {
p0 = 0;
p1 = 100;
acc = 0;
}
sleep(1);

Lv.05 - 饮酒游戏积分器

1
2
3
4
5
* *得分*和*犯规*是连接按钮的简单输入。
* *显示*是连接 XBus 显示器的输出,与 LCD 屏上的数字相对应。
* 计数器必须可以从 0 开始计数,并时刻保证*显示*值与当前值一致。
* 每当按下*得分*按钮,应加 1 分。
* 每当按下*犯规*按钮应减 2 分,但分数不可低于 0。

这关开始要用到XBus来发送数据了,但是却出乎意料的简单。。。

顺带说一句,acc是可以为负数的,所以可以先进行加减操作,再检查是否小于0。

  • 成本:3
  • 用电量:323
  • 代码行数:8

Lv.06 - 调谐最优化引擎

1
2
3
4
5
* *音频输入*是连接音频源的简单输入。
* *音频输出*是连接音频接收器的简单输出。
* *最优化*是连接开关的简单输入。
* 应将*音频输入*信号复制到*音频输出*,并在*最优化*开关开启时应用调谐最优化算法。
* 在手册“补充信息”部分有一张“调谐最优化算法”广告,找到并阅读该部分内容。

这关要求处理音频,并且在开关开启的时候进行音质优化。说明书给了下面这个公式:

1
AUDIO_OUT = (AUDIO_IN - 50) x 4 + 50

这里需要做到“开关”的效果,但是无论MC4000还是MC6000都只有两个I/O引脚,所以这里直接用XBus引脚控制状态即可。需要注意的是,XBus要求读写同步,所以需要用slx指令等待接收XBus数据包。

开始我用了三个控制器,后来发现原来两个就足够了。而且这个公式也是一个优化点,可以用数学方法化简,变成下面这样,从而节省一行代码。

1
AUDIO_OUT = 4 x AUDIO_IN - 150
  • 成本:6
  • 用电量:550
  • 代码行数:11

Lv.07 - 被动红外感应器

1
2
3
4
5
6
7
* *时间*是连接 DT2415 时钟的简单输入,能提供当前时间。
* *传感器*是连接红外线传感器的简单输入。
* *报警器*是连接无声报警器的简单输出。
* 如果当前*时间*与报警时间“on time”相同,设备应_报警_。
* 如果当前*时间*与停止报警时间“off time”相同,设备应_停止报警_。
* 如果设备_报警_,且*传感器*读数值等于或大于 20,则启动*报警器*输出。
* 设备使用者可通过转盘设置报警和停止报警的时间;该值数值范围与 DT2415 时钟相匹配,可以从 XBus 输入读取。

这关要制作一个红外报警器,个人感觉挺难的,因为新增了不少模块,得一个个去摸索作用。中文版也有点小问题,关卡描述里的“报警器”,和电路板上的“闹钟”应该是同一个东西,我切英文版看了,都是alarm。。。因此这个电路板上的“闹钟”大概就是翻译错误了。

首先是DT2415累加式时钟,输出值范围是0-95,累积到最大值后,下一次就回到0。输出引脚在右下,输出简单I/O信号。

然后是时间点输入模块,这俩都输出XBus数据包。

那么要求我直接用伪代码描述一下。

1
2
3
4
// 注意:“flag_激活报警器”的初始值是0
if (DT2415输出 == 开启时间) flag_激活报警器 = 1;
if (DT2415输出 == 关闭时间) flag_激活报警器 = 0;
if (传感器数值 >= 20 && flag_激活报警器 == 1) 报警;

注意:这里有个坑,MCxxxx系列的控制器,是没有>=的。这里只能用>(tgt),那么相应的临界值就变成了19,而不是题目描述给的20。

  • 成本:8
  • 用电量:503
  • 代码行数:11

Lv.08 - 仿真蜂音器

1
2
3
4
5
6
7
* *无线rx*是连接无线接收器的非阻塞式 XBus 输入。
* *蜂音器*是连接电子机械蜂音器的简单输出。
* 从*无线装置*收到数据包时,读取该数据包,并执行下表中的相应指令:

| 0 | 关闭电源 | 关闭蜂音器
-------------------------------------------------------------
| 1 | 开启电源 | 开启蜂音器

这个题要写出来不难,但是我这肯定不是最优解。总感觉可以只用一个MC6000,但是却想不到如何保存多个状态。由于XBus在一个时间内只能读一次,但是有-999(什么都不做),0(关闭),1(开启)三个状态,所以肯定需要一个临时的变量来保存获取的值。然后又需要一个变量表示开关。最后还需要一个变量来控制蜂鸣器的状态。

TODO:看看之后能不能想出更好的解法

Lv.09 - 无线游戏控制器

1
2
3
4
5
6
7
8
9
10
* *x*和*y*是连接游戏杆的简单输入。
* *a*和*b*是连接按钮的简单输入。
* *无线rx*是连接无线接收器的非阻塞式 XBus 输入。
* *无线tx*是连接无线发送器的 XBus 输出。
* 当通过无线装置收到数据包时,返回以下由 3 个值组成的数据包。注意确保发送的是最新输入数据:

| | | 0, if *a* = 0 and *b* = 0
| *x* | *y* | 1, if *a* = 100 and *b* = 0
| | | 2, if *a* = 0 and *b* = 100
| | | 3, if *a* = 100 and *b* = 100

这关难度不小,光是理级题目要求就要费点功夫,我在这里简要概括下。

我们需要获取x,y,a,b,tx,最后输出到rx。其中tx是传输开始的信号,值为-1时传输,-999则不传输。x和y是直接使用原始值,a和b经过处理之后生成一个值(这里我们称之为acc),然后我们需要把x,y,acc按照顺序依次输出到rx。

其它地方都不难,重点是这个c的计算过程。题目给出下面的规则:

1
2
3
4
5
6
7
8
if (a == 0) {
if (b == 0) acc = 0;
else acc = 2;
}
else {
if (b == 0) acc = 1;
else acc = 3;
}

乍一看,这是要搞嵌套循环啊,但是MC系列的汇编语法不允许这样嵌套。与其说是嵌套(nesting),实际上是链式(chaining)。我们来看下面的例子。

1
2
3
4
5
6
7
8
9
10
  teq a 0
+ teq b 0
+ mov 0 acc
- mov 2 acc

运行结果:
a = 0, b = 0, acc = 0
a = 0, b = 1, acc = 2
a = 1, b = 0, acc = 2
a = 1, b = 1, acc = 2

发现问题了吧,这种写法,用伪代码写出来就是下面这样。

1
2
if (a == 0 && b == 0) acc = 0;
else acc = 2;

这种问题可以通过jmp来解决,但是强如MC6000也只能写14行汇编,所以这里我们转向数学方法。仔细观察,会发现下面的关系。

1
2
a == 1 -> acc += 1
b == 1 -> acc += 2

所以这么写就可以了

1
2
3
4
  teq a 1
+ add 1
teq b 1
+ add 2
  • 成本:13
  • 用电量:375
  • 代码行数:21

Lv.10 - 激光束游戏设备

1
2
3
4
5
6
7
8
9
10
* *击中*是一个简单输入,连接着背心上的激光探测器列阵。
* *复活*是一个简单输入,连接着背心上的快速连接装置。
* *活着*是一个简单输出,连接着背心上的 LED 灯列阵。
* *扣扳机*和*填弹*是简单输入,连接着仿真枪的开关。
* *射击*是简单输出,连接着仿真枪的激光和效果模块。
* 当玩家被*击中*时,他们的背心应被设置为不再*活着*。
* 当玩家*复活*时,他们的背心应被设置为*活着*。
* 当玩家给枪*填弹*时,无论是否*活着*,都应将弹药数设为填弹初始值。
* 如果玩家*活着*时做了*扣扳机*动作,并且还有弹药剩余,则应设置手枪*开火*、弹药数减少 1。
* 为让玩家能“按照实际情况使用”不同型号的仿真枪,使用者可以通过转盘设置填弹初始值。转盘数值可从 XBus 输入读取。

这关要求特别多,略显麻烦。不过我看我的三项排名都比较靠前,估计是最优解了。

由于简单I/O接口特别多,这关很明显需要用到DX300。这个模块不仅便宜(只要1块),而且还带有3个简单I/O引脚。不过DX300需要配合MC系列的控制器一起使用,因为DX300本身无法进行编程,只能用作数据接口。

这里简单说下DX300的用法。可以用任意XBus接口输入一个二进制的三位数来控制3个简单I/O引脚,也可以读取当前状态下的简单I/O引脚情况(获取二进制的三位数)。其中p0是个位,p1是十位,p2是百位。(例子:XBus=101 -> p0=1, p1=0, p2=1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 处理“击中”,“复活”,“活着”
teq x1 1 // 判断玩家是否被击中
+ mov 0 dat // 关闭“活着”标记
teq x1 10 // 判断玩家是否按下复活按钮
+ mov 100 dat // 开启“活着”标记
mov dat p0 // 把“活着”标记输出到p0引脚
// 处理激光枪
teq x0 100 // &&判断:玩家是否扣下扳机
+ tgt acc 0 // &&判断:玩家是否还有子弹
+ teq dat 100 // &&判断:玩家是否存活
+ mov 100 p1 // (All True)把“射击”信号输出到p1引脚
+ sub 1 // (All True)子弹数量-1
- mov 0 p1 // (Any False)不输出到p1引脚
teq x0 10 // 判断是否装弹
+ mov x2 acc // 填充子弹
slp 1
  • 成本:7
  • 用电量:483
  • 代码行数:14

Lv.11 - 变色电子烟笔

1
2
3
4
5
6
7
* *无线rx* 是连接无线接收器的非阻塞式 XBus 输入。
* *红色*、*绿色*和*蓝色*是连接超高亮度 RGB(三色)LED 灯的简单输出,顺序依次是从上到下。
* 从*无线装置*收到数据包时,按照以下规定生成 LED 脉冲:

| 红色亮度 | 绿色亮度 | 蓝色亮度 | 脉冲时长

* 如果正在生成脉冲时收到数据包,则终止脉冲。

这题需要控制一个RGB灯,给出答案不难,但是想要一次就写出最优解还是没那么容易的。

一开始我给出的解是这样的:

  • 成本:13
  • 用电量:828
  • 代码行数:20

这种方实比较模块化,思路也很清晰,但是可以看出有很多引脚并没有用上,尤其是相对珍贵的p引脚。所以,可以考虑直接从主控用p引脚连接RGB灯,就像下面这样。

  • 成本:8
  • 用电量:588
  • 代码行数:15

从电量使用的分布来看,似乎还有更省电的答案,但是不清楚是否要用到其它模块,或者是牺牲成本和代码行数。不管了,就先这样吧。

Lv.12 - 不明优化设备

1
2
3
* *x*和*y*是连接未知设备的简单输入,该设备可能是某种未知传感器。
* *电源*是连接未知设备的简单输入,该设备可能是某种型号的电机或大功率开关。
* 将(*x*, *y*)坐标与相应*电源*对应起来,使其值符合 Poseidon-779 几何规格书的规定。在手册里的“补充信息”部分可以找到该规格书。

这关好难啊,思路不对的话,死磕是解不出来的。

首先我们需要研究下说明书里头的这个图。

稍微用伪代码写一下,大概就是下面这样的:

1
2
3
4
5
6
7
8
9
if (x < 20) power = 30;
if (x >= 20 && x < 60) {
if (x >= 40 && y >= 40 && y < 80) power = 50;
else power = 0;
}
if (x >= 60) {
if (x < 80 && y >= 40 && y < 80) power = 80;
else power = 30;
}

要是平时,我就直接用上面的代码了,反正现在电脑性能高得很,优化什么的当然是不存在的。但是现在只能用汇编,而且一个控制器最多写14行汇编代码。所以,上面那段伪代码是没有办法实现的。

那么,有没有简化的方法呢?我后来就尝试将整个地图切割成下面这样的15份。

赋予地图一个坐标系之后,看起来比较可行,因为绝大部分区域都是0和30,这些可以批量去判断。关于两个特殊的格子(50和80),可以单独拿出来判断。最后一共用了五个控制器,然后还差一行写不下,太尴尬了。。。而且塞满了整个电路板,说明答案肯定不对了。

后来实在没办法了,上网翻到一个讨论贴,看了眼提示,然后恍然大悟。这里其实还是用数学方法来简化,很关键的一点就是:

The value inside the inner square is the outer value + 50

所以我当时咋就没看出来呢。。。有了这个信息,那就简单很多了。首先通过x的区间,决定是0或30。然后再通过x和y共同决定是否+50(也就是0或50),最后把两个数值加一起就行了。

  • 成本:10
  • 用电量:1037
  • 代码行数:19

感谢评论区Wof3YldPjOvDh9xe提出了另外两种更好的方案,在此一并列出。

另外,优化后的设计,比博主目前的版本稍好些,希望博主能更新文章,方便大家😋

  • 成本:8
  • 用电量:740
  • 代码行数:16

一个QQ群友,告诉我一个更牛逼的,人外有人,天外有天呀🤡

  • 成本:6
  • 用电量:715
  • 代码行数:14

秘密指令!

这个不是关卡,但是这段剧情提到了两个可能有用的新指令,而且不在说明书上。

gen P R/I R/I

说明:它能方便地在所有简单 I/O 引脚上生成脉冲!该信号会在开(100)和关(0)之间跳转,你想要持续多少时间单位都行,取决于最后两个操作数的数值。

1
2
3
4
5
6
7
用法:gen P X Y

相当于:
mov 100 P
slp X
mov 0 P
slp Y

@ 条件语句

说明:在指令前加 @ 符号能让该指令只执行一次

Lv.13 - 古钱币付款终端

1
2
3
4
5
* *投入口1*、*投入口5*和*投入口12*是连接到一台机器上的简单输入,该装置能检测并接收 1 銖、5 銖和 12 銖钱币。
* *铃*是连接螺线管的简单输出,该装置被触发会导致响铃。
* *找零口1*和*找零口5*是连接到一台机器上的简单输出,该装置能吐出 1 銖和 5 銖钱币从而为玩家找零。
* 投入足够数量的钱币后,付款台会响*铃*,并按照验证面板中的指示找零。
* 使用者可通过转盘设置商品费用,转盘数值可从 XBus 输入读取。

这关出乎意料的简单,一次就写出答案了。这里用了两块控制器,一块收钱响铃,另一块找钱。

不过在写的时候有个坑。控制器只支持下面这些指令。

1
2
3
4
5
teq a b -> if (a == b) exec +; else exec -;
tgt a b -> if (a > b) exec +; else exec -;
tlt a b -> if (a < b) exec +; else exec -;
// 最后这个只有在两者不相等才执行
tcp a b -> if (a > b) exec +; else if (a < b) exec -;

那么要实现>=的效果怎么办呢?其实很简单,走false分支就可以了,像下面这样。

1
2
3
4
  tlt acc x0
- mov 100 p1

// 用js写就是if (!(a < b)),等价于if (a >= b)
  • 成本:11
  • 用电量:389
  • 代码行数:25

Lv.14 - 个人三明治制作机

1
2
3
4
5
6
7
8
9
10
* *小键盘*是连接小键盘的 XBus 输入。
* *面包*、*肉*、*奶酪*和*芥末*是连接到一台机器上的简单输出,该装置能吐出以上原料。
* *旗*是一个简单输出,它连接着一台小电机,可以在三明治做好后升起或放下一面美国国旗。
* 当*小键盘*输入一组值,读取该值并按照以下表格和验证面板的提示做一只三明治。

| 1 | 三明治时间! | 做一只加*肉*、*奶酪*和*芥末*的三明治。
-------------------------------------------------------------
| 2 | 不要奶酪 | 做一只三明治,但不要加*奶酪*
-------------------------------------------------------------
| 3 | 多加芥末 | 给三明治多加一份*芥末*

这关写出来不难,但是要把所有代码塞进一个控制器,还是有点难度的。

首先,很容易想到,用一个DX300控制肉、奶酪、芥末,然后用MC6000的两个p引脚控制面包和旗子,基本上写出下面的代码不难。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  slx x1
mov x1 acc
gen p1 1 0
mov 100 x3
slp 1
teq acc 2
- mov 010 x3
- slp 1
mov 001 x3
teq acc 3
+ slp 2
- slp 1
mov 000 x3
gen p1 1 0
gen p0 3 1

但是问题来了,这里一共有15行,超出了1行,所以得想办法变成14行。接下来要说的,会推翻前面对“条件语句”的认知。

根据说明书上提供的条件表格,“+”和“-”指令在不同的条件下会“允许执行”或者“禁止执行”。也就是说,“+”和“-”指令不需要紧跟着条件语句,而是应当看作“是否被激活”的一个状态。

关于tcp指令,说明书是这么写的:

条件 对‘+’ 指令的作用 对‘-’ 指令的作用
A 大于 B 允许执行 禁止执行(屏蔽)
A 等于 B 禁止执行(屏蔽) 禁止执行(屏蔽)
A 小于 B 禁止执行(屏蔽) 允许执行

因此这里可以直接用tcp来标记状态(是否允许执行特定的代码),从而省去把状态保存到acc这一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  slx x1
tcp x1 2 // 1执行-,2不执行+-,3执行+
gen p1 1 0 // 面包:123
mov 100 x3 // 肉:123
slp 1
mov 010 x3 // 奶酪:13(注意:2也会执行这句,但是会跳过下面的slp,然后被芥末直接覆盖)
+ slp 1
- slp 1
mov 001 x3 // 芥末1:123
slp 1
+ slp 1 // 芥末2:3(直接延长一个时间周期即可)
mov 000 x3 // 关闭芥末
gen p1 1 0 // 面包:123
gen p0 3 1 // 旗子:123

这个应该是最优解了,而且使用一个控制器的,肯定也只有这个唯一解。这一关看成绩分布的时候,发现很多人都用了两个控制器,好像还有用三个的。。。

  • 成本:6
  • 用电量:84
  • 代码行数:14

Lv.15 - 卡宾枪瞄准照明器

1
2
3
4
5
6
* *雷达输出*是连接微型雷达单元的简单输出,该设备在发射雷达“ping”时会发送信号。
* *雷达输入*是连接微型雷达单元的简单输入,该设备在收到雷达“ping”时会发送信号。
* *激光*是连接可变强度红外线瞄准激光器的简单输出。
* *泛光20*和*泛光60*是连接 20°和 60° 光束红外线泛光照明器的简单输出。
* 测量*雷达输出*脉冲起点和随后的*雷达输入*脉冲起点,以确定“雷达范围”。
* 按照手册“补充信息”部分的卡宾枪瞄准照明器照射范围表规定,控制*激光*、*泛光20*和*泛光60*的信号。

这关只要善用jmp,写出来还是挺容易的。收到“雷达输出”的时候开始用acc计时,收到“雷达输入”的时候停止计时(并且别忘了acc + 1)。然后就判断acc的范围,一个控制器写不下就通过xBus接口转发。

我这个不是最优解,也懒得优化了,差不多就行。

  • 成本:11
  • 用电量:262
  • 代码行数:26

Lv.16 - 幽灵娃娃

1
2
3
4
5
* *随机*是连接随机数发生器的简单输入。
* *扬声器*是连接微型自放大扬声器的简单输出。
* 当随机生成值 1 时,播放“邪恶地笑”音效。
* 当随机生成值 2 时,播放“令人毛骨悚然的尖叫”音效。
* 在手册“补充信息”部分有 Carl 找到的这两种音效的音频数据。

这关如果用teq的话,又写不下,所以还是要利用强大的tcp。而且如果写不下的话,也会大大增加布线难度。这里就给出我两个解,可以体会下差距有多大。

首先是使用teq的版本,一共使用了三个控制器,而且布局比较乱。这里倒是用到了两个小技巧。

第一,线路可以从芯片下方穿过。这里就是从随机数生成器那里开始,穿过200P-14(只读储存器),最后到达左下角MC6000控制器的p0引脚。

第二,可以把多个线路联通,从而实现一对多的关系。这里就是两个MC6000控制器的x3引脚,同时连接到了右边MC4000控制器的x0引脚。

  • 成本:17
  • 用电量:490
  • 代码行数:20

接下来是tcp的版本。由于代码恰好能塞进一个MC6000控制器,所以面板非常简洁,而且控制器的六个引脚全部用上。

  • 成本:9
  • 用电量:238
  • 代码行数:14

最后上一张资源消耗对比图。

现在用高级语言写代码可以随心所欲,还得感谢硬件的发展啊。要是回到过去,那可得绞尽脑汁去进行优化的。

Lv.17 - 共生环境维护机器人

1
2
3
4
5
6
7
8
9
10
* *无线rx*是连接无线接收器的非阻塞式 XBus 输入。
* *电机*是连接双向电机的简单输入。
* *清洁工具*和*加液工具*是连接位置工具的简单输出。
* 共生环境维护机器人开始工作时位于站点 0。
* 让*电机*输入生成从 100 波动到 50 的脉冲能将电机移动到下一站。
* 让*电机*输入生成从 0 波动到 50 的脉冲能将电机移动到前一站。
* 从*无线装置*收到数据包时,移动到目标站点,并按照规定时长激活工具。

| 目标 | *清洁工具* | *加液工具*
| 站点 | 激活时间 | 激活时间

这关的难点在于,如何利用gen输出0和100之外的数值。开始想了半天,后来才发现,只要在slp之前覆盖简单输出的值,就可以偷梁换柱了。具体实现就是下面这样。

1
2
3
4
5
6
  tcp dat acc
+ gen p1 1 0
+ add 1
- gen p1 0 1
- sub 1
mov 50 p1 // 这里偷梁换柱,把最后结果改成50

至于分区处理,还是比较好想到的。电机单独控制,然后清洁工具和加液工具一起控制。两个控制器之间使用slx来通信,机制比较类似于callback。电机当前的位置用acc记录,目标位置从x0获取之后存放在dat,因为只有acc可以进行加减操作,dat只能存放数据。

  • 成本:10
  • 用电量:217
  • 代码行数:23

Lv.18 - 远程退出开关

1
2
3
4
5
6
7
8
9
10
11
* *无线rx*是连接无线接收器的非阻塞式 XBus 输入。
* *电源0*、*电源1*和*电源2*是连接大功率开关的简单输出,该装置控制着工业设备。
* 从*无线装置*收到数据包时,读取该数据包,并执行下表中的相应指令:

| -1 | 保持现状 | 无操作
-------------------------------------------------------------
| 0 | 关闭电源 | 读取另一个值并关闭输出
-------------------------------------------------------------
| 1 | 开启电源 | 读取另一个值并开启输出

* 持续 5 个时间单位未收到数据包时,关闭所有*电源*输出。

这关需要提供三个p引脚输出,很明显要用到DX300。DX300接收的是一个三位数,这里就要利用dst指令来给指定数位赋值。然而dst指令是只能作用于acc的,这关还额外要求我们设计一个倒计时功能。注意dat是无法进行运算操作的,只是一个储存器,因此很明显我们需要两个acc。

这里我采用的方案是,用一个MC4000当倒计时器,MC6000当作主控。答案似乎不是最优解,耗电量超过了平均值,但是懒得优化了。

  • 成本:9
  • 用电量:798
  • 代码行数:20

Lv.XX - 未完待续。。。

Lv.19有点难,所以本文好像要烂尾了

最后更新:2021/2/28