欢迎光临散文网 会员登陆 & 注册

【深圳 IO 攻略】PGA33X6 的高级用法:状态翻转

2022-10-03 20:28 作者:ココアお姉ちゃん  | 我要投稿

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

前一篇 PGA33X6 的攻略里(传送门:【深圳 IO 攻略】番外篇:介绍一下 PGA33X6 这个冷门元件),我们利用 PGA33X6 自带的 data 锁存器做出了一个用两个按钮控制的,可以自由开启/关闭的电灯。电路图如下所示:

按下第一个按钮时,将 data 置为 1 并锁存;按下第二个按钮时,将 data 置为 0 并锁存。第一路输出和灯泡相连,输出的状态值和 data 一致,所以仅当 data 为 1 时灯泡才会被点亮。

你可能会觉得用两个按钮控制灯泡有点多余,能不能用一个按钮来控制呢?按钮按下时,如果灯泡处于点亮状态,那么就熄灭,反之亦然。正好我们的 data 值既可以做输出量也可以做输入量。这时候你尝试着把电路板改成下面的样子:

我们观察 PGA 中的点亮的小方块,将其中的逻辑转换成 C 语言的形式,如下:

乍一看,很合理嘛!但是当你点击下方的【模拟】运行程序时,意想不到的事情发生了:

按下按钮的时候,灯泡竟然在开启和关闭的状态间反复横跳,直到松开按钮时,灯泡会随机停留在开启/关闭中的一种状态上。

我们尝试复一下盘:

初始状态下,data 为 0,按钮也没有按下。

当按钮按下的时候,PGA 是满足第一列条件的(A 路输出为 true,且 data 为 0),此时执行第一列的逻辑,将 data 置 1。

但是,事情还没有结束!data 被置 1 后,PGA 就不再满足第一列的条件,转而变成满足第二列的条件了(A 路输出为 true,且 data 为 1)。那么,PGA 在执行了上述指令,将 data 置 1 后,第二列的条件又重新被满足了,根据第二列的要求,data 又需要重新置为 0。

data 被置 0 后,第一列的逻辑又重新被满足了……于是,只要按钮是按下的,PGA 就永远无法进入稳定状态,这时候的 data 是一个薛定谔的 data,它既是 0 又是 1。

这是我们在使用 PGA 时要特别注意的一点:更新 data 值时,不能以 data 自身的值作为判定依据,否则 PGA 会无法进入稳定状态。也就是说:同一列里,第 5、6 行的方块(data 输入)不能和第 9、10 行的方块(data 输出)同时点亮

那么问题来了:按下按钮时,我确实要根据灯泡此前的状态值来更新灯泡现在的开关状态啊,先前是熄灭的我就点亮,先前是点亮的我就熄灭。现在我已经不能用这个 data 作为判定依据了,还有别的办法吗?

答案是:有!因为第三路这个和 data 无关的输出量我们还没有用上,我们完全可以把这一路输出量作为另一个 data 反馈给 PGA。我们现在尝试着搭出这样的电路图:

这里,我们把灯泡的开关量移到了 3 路输出上,并将这一路输出同时作为反馈量接到 PGA 的 3 号输入上。这时候我们再重新观察 PGA 中的点亮的小方块,将逻辑转换成 C 语言的形式,如下:

在本方案里,我们将内置的 data 量和真正的灯泡开关做了分离。按钮按下时,根据灯泡的开关调整 data 值,但不改变真正的灯泡开关;当按钮抬起时,再根据 data 值改变灯泡开关,同时令 data 值保持不变。如此,即可避免【以 data 自身为依据修改 data 值,导致 data 反复横跳】这样的现象。

以上方案我们还需要做一些调整。我们的 3 路输出不能直接用导线跟 3 路输入相连,否则会触发如上图所示的自连错误。这里我们有两种办法:

①3 路输出和 3 路输入间加一个或门:

或门有两个输入口,如果只接了一个,那么另一个没有接入输入量的口会被视为恒假。由于 A or FALSE = A,只接了一个输入量的或门,输出的值为该输入量自己。

②3 路输出和 3 路输入间加一个非门:

接非门的好处是非门的占地面积小,但与此同时反馈的输入量也会被取反。所以接非门以后,原先点亮第 7 行小方块的地方要改为点亮第 8 行小方块,反之也一样,原先点亮第 8 行小方块的地方要改为点亮第 7 行小方块。如上图所示,接非门以后,第 7、8 行的小方块全部换了位置。

此时我们再运行这个程序:

成功了!我们成功做出了用一个按钮控制的电灯泡!

下面我会使用这个 PGA 技巧完成一些官方谜题,设计出一些最省行数的解决方案。

例 1:龙腾第 4 关《动画 ESPORTS 标志》

本关我们可以使用 PGA 将代码行数减少到 2 行。

左上方的芯片用于生成点击 0 的时序,点击 0 按照 100→0→100→0→……的顺序反复横跳,所以该芯片的代码是 gen p0 1 1。点击 1 的时序跟点击 0 互反,只需要将点击 0 的信号经过非门输出给点击 1 就 OK 了。

左下方的芯片用于生成喝 0 的时序,观察时序图,我们可以发现喝 0 先是连续 6 秒的 100 信号,再是连续 4 秒的 0 信号,以 10 秒为周期循环。所以该芯片的代码是 gen p1 6 4。

然后是我们的重头戏——PGA。这个 PGA 的三路输出量里,只有 3 号输出连接了导线,且接到了喝 2 上。很明显,这个 PGA 就是用来输出喝 2 量的。

我们将点击 0 和喝 0 的时序复制了一份到 PGA 的第 1、2 路输入量上,意图很明显:喝 2 的时序和点击 0 及喝 0 相关,我们需要根据点击 0 和喝 0 的时序来生成喝 2 的时序。现在我们忽略无关的点击 1 和喝 1,重点观察点击 0、喝 0 和喝 2 的对应关系:

注意我圈出来的这部分。如果我们把点击 0 当成按钮,喝 2 当成灯泡的话,我们惊奇地发现:当喝 0 为 0 时,点击 0 和喝 2 的逻辑完美形成了【单按钮灯泡】:当点击 0(按钮)从按下变为抬起时,翻转喝 2(灯泡)的开关状态。而当喝 0 为 100 时,我们只要无视点击 0 的点击效果,令喝 2 保持关闭状态就可以了。

现在我们再来观察这个用于生成喝 2 时序的 PGA:

我们将所有的逻辑改写成 C 语言的形式,如下:

以上的三条逻辑都必须要在喝 0 为 0 时生效,喝 0 为 100 时则不满足以上任何一条逻辑,所以喝 0 为 100 时,任凭点击 0 如何变化,喝 2 也是雷打不动地处于关闭状态。

最后的喝 1,跟之前的方案一样,为喝 0 和喝 2 的【或非】值。仅当喝 0 和喝 2 均为 0 时,喝 1 才为 100。

点击左下角的【模拟】,稍等片刻,便会弹出结算界面:

本方案的两行代码里,一行用于生成点击 0 的时序,一行用于生成喝 0 的时序。点击 1 的时序由点击 0 取反得到,无需代码行数;喝 2 的时序由 PGA 根据点击 0 和喝 0 推导而来,无需代码行数;最后的喝 1 再由点击 0 和点击 2 取或非值得到,仍然无需任何代码行数。本题里我们需要手动生成的时序只有点击 0 和喝 0 这两路,其余路的时序都可以由已知条件推导而来。这就是为什么这道题只用两行代码就可以搞定的原因。

例 2:阿瓦隆城第 1 关《冷库机器人》

本关使用 PGA 的话可以把代码行数减少到 23 行,而且芯片的话只要一块 MC4000 和一块 MC6000 就足够了。纸面难度变得和龙腾系列的关卡差不多了。

现在再看到这样的时序图你就应该有条件反射了:伸出和抓握的逻辑形成了完美的【单按钮灯泡】:当【伸出】信号由开启变为关闭时,反转【抓握】信号。

本次的芯片代码和上一版攻略相比改动较大,因此我从头再解读一遍。

上一版方案里,我们安排了一个总监芯片、一个数据库管理员芯片和两个工人芯片。由于总监和数据库管理员的工作量都较少,本方案里总监和数据库管理员的工作合并到了一块 MC6000 里。

根据游戏设定,所有官方谜题提供的 C2S-RF901 都不会在第一秒里提供数据。所以第一秒可以放心让 MC6000 休眠(slp 1)。从第二秒起,检查 C2S-RF901 有没有提供新的数据包。C2S-RF901 的首数字有三种可能性:-999、1、2。因此首先肯定是一个三态判定(tcp x3 1)。当首数字是 -999 时,什么事都不用做,直接跳回第一行睡觉(- jmp 1)。读到另外两个数字时再开始干活。下面我们依次分析。

读到 1 开头的数据包时,说明要向冷库内【存】新的食物;读到 2 开头的数据包时,说明要从冷库内【取】对应编号的食物。我们需要这样操作 RAM:读到 1 时,从 RAM 中找第一个数字为 0 的格子(空格),将对应的格子改写为数据包的第二个数字(向空格内存食物);读到 2 时,从 RAM 中找到和数据包的第二个数字相等的格子,并将对应的格子重置为 0(从冷库中取出食物)。

这块 MC6000 的 dat 寄存器就是用来记录要在 RAM 里找什么数字的。我们现在观察第 4~6 行的代码:开始搜索之前,我们首先将 RAM 的地址归零(mov 0 x1)。接下来我们根据数据包的首数字来确定接下来要执行的任务:如果首数字是 1,那么我们要在 RAM 中找值为 0 的格子,此时将 dat 置为 0(mov 0 dat);如果首数字是 2,那么我们要在 RAM 中找值和数据包第二个数字相等的格子,此时我们需要读取数据包的第二个数字放入 dat(+ mov x3 dat)。

第 7~9 行是一个循环,首先将当前的地址值放入 acc 暂存(mov x1 acc),然后判断当前格子的值是否和 dat 一致(teq x0 dat)。若不一致,跳回第 7 行继续找,直到找到和 dat 一致的格子为止(- jmp 7)。

第 10~14 行用于改写 RAM 中对应格子的值,并通知后面的 MC4000 和 PGA 执行任务。首先将 RAM 的地址重新定位到第 acc 格,准备改写对应格子内的值(mov acc x1)。此时判断 dat 的值是 0 还是非 0(teq dat 0)。若 dat 是 0,说明当前要执行【存】任务,我们从 C2S-RF901 中读取第二个数字放到 RAM 的这一格里(+ mov x3 x0);若 dat 是非 0,说明当前要执行【取】任务,我们将该格内容改写为 0(- mov 0 x0)。改写完毕后,将当前任务类型发给右边的 MC4000 芯片(mov dat x2)。接下来我们来看 MC4000 芯片:

MC4000 的 x0 口连接着 RAM 的地址口;x1 口连接着 MC6000 的 x2 口,用于芯片间的通讯;p1 口连接着【电机】输出;p0 口连接着 PGA 的 1 路输入,用于提供模拟的按钮信号。

为了节省行数,我调整了 MC4000 的代码顺序。下面我贴一个优化前的代码:

首先二话不说将电机信号初始化为 50(@ mov 50 p1),然后等待 MC6000 的唤醒信号(slx x1)。MC6000 会把自己的 dat 值发过来,这里我们只关心这个值是 0 还是非 0(teq x1 0)。

这里我们首先回顾一下电机的工作模式:

④电机初始在第 0 格,新食物也总会在第 0 格出现。当你收到 1 开头的数据包时,需要将新食物放入冷库。首先令电机【伸出】1 秒,然后让电机【抓握】住食物,并【向右移动】,找到冷库中的第一个空位后,令电机【伸出】1 秒,把爪子【放开】,将食物送入冷库。最后,令电机【向左移动】回到原点待命。


⑤当你收到 2 开头的数据包时,令电机【向右移动】,找到对应的食物后,令电机【伸出】1 秒,然后让电机【抓握】住食物,并【向左移动】到原点,将食物运送到出口。到达原点后,令电机【伸出】1 秒,把爪子【放开】,释放食物。 作者:ココアお姉ちゃん https://www.bilibili.com/read/cv17128944 出处:bilibili

当 MC6000 发来的值是 0 时,说明当前要执行的是【存】任务,这时候首先要给右边的 PGA 发送一个模拟按钮按下→抬起的信号(+ gen p0 1 1),令电机【抓握】住位于 0 号位置的食物,然后再令电机右移。当 MC6000 发来的值不是 0 时,说明当前要执行的是【取】任务,此时不需要令电机执行抓握指令,直接令电机右移即可。

由于 RAM 中的地址是以 0 起始的,而冷库中的位置编号是以 1 起始的,所以冷库地址 = RAM 地址 +1。而恰好,我们在读/写 RAM 后,地址会自增 1。因此我们可以直接将自增后的 RAM 地址作为“冷库目标地址”。这时候,我们需要让电机保持这么多秒的 100 信号,令电机右移到相应的位置(gen p1 x0 0)。

右移到对应的位置后,首先将电机信号重置为 50(mov 50 p1)。如果电机当前抓握着食物,说明当前在执行的是【存】任务,到达位置后,需要给 PGA 发送一个模拟按键信号(gen p0 1 1),令电机把食物【放下】。反之,如果电机当前没有抓握着食物,说明当前在执行的是【取】任务,到达位置后同样需要给 PGA 发送一个模拟按键信号,令电机【抓握】住对应位置的食物。也就是说,到达目标位置后,无论当前在执行的是什么任务,都要发送模拟按键的信号,令电机改变抓握状态。

最后我们给电机发送同样时长的 0 信号,令电机回到原点(gen p1 0 x0)。回到原点后,首先将电机信号重置为 50(mov 50 p1)。然后判定,如果当前执行的是【取】任务,那么在回到原点后,要给 PGA 发送一个模拟按键信号,令电机把食物【放下】(- gen p0 1 1)。

总结:执行【存】任务时,要在开始移动前以及到达目标位置后各发送一次模拟按键信号;执行【取】任务时,要在到达目标位置后,以及回到原点后各发送一次模拟按键信号

由于上电时 + - 号都不会激活,所以我们可以将最后两行代码挪到开头,并去掉冗余的 @ mov 50 p1 指令,即可将 10 行代码减少到 9 行,正好能够放到一块 MC4000 里。

最后是 PGA:

PGA 的 2 路输入用于接收 MC4000 发来的模拟按键信号,3 路输入用于接收反馈的【抓握】信号(注意经过了非门)。我们将 PGA 的逻辑改写成 C 语言,如下:

每当 MC4000 发送一个模拟按键的信号时,若按键处于按下状态,【伸出】信号会被激活;若按键处于抬起状态,【抓握】信号会被反转。

点击左下角的【模拟】,稍等片刻,便会弹出结算界面:

看到了吗,用 PGA 做这道“模拟电灯泡”题,只需要 23 行代码就足够了哦。

【深圳 IO 攻略】PGA33X6 的高级用法:状态翻转的评论 (共 条)

分享到微博请遵守国家法律