【深圳 IO 攻略】第 13 關(guān):古錢幣付款終端

本文首發(fā)于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原創(chuàng)不易,轉(zhuǎn)載請注明出處。
關(guān)卡展示

自動售貨機大家都見過吧,這關(guān)要實現(xiàn)的就是類似于自動售貨機一樣的東西?,F(xiàn)在這臺售貨機里的商品價格已經(jīng)定死了(寫在【價格】常數(shù)芯片里),顧客投入 1/5/12 元的硬幣,一旦任何時候累計價格大于等于商品價格時,就響 4 秒鐘的鈴,同時找零口開始給用戶找錢,優(yōu)先找 5 元硬幣,找零剩余不足 5 元時開始找 1 元硬幣。
由于找零的脈沖信號要維持 4 秒鐘,且在此期間就要開始給顧客找零。所以為了方便,我們將收錢及響鈴的程序?qū)懺谝粔K芯片上,而找零的程序?qū)懺诹硪粔K芯片上。左邊有三個投入口,我們肯定要把它們接到一個 DX-300 上,這樣我們僅僅判斷一個三位數(shù)就能得知哪個口投入了錢幣。電路圖如下所示:

首先我們寫收錢部分,把相關(guān)的代碼寫在左側(cè)芯片里:

從本關(guān)開始,我們要認(rèn)識一條新的指令:跳轉(zhuǎn)指令 jmp。它的用法是:jmp 行標(biāo),意思是無視原先的執(zhí)行順序,強制跳轉(zhuǎn)到對應(yīng)行標(biāo)處執(zhí)行。這條指令通常都是和 + - 號前綴同時使用的,表示“按條件跳轉(zhuǎn)”。
這里我們之所以使用了 jmp 跳轉(zhuǎn)指令,是因為和硬幣投入口連接的 DX-300 其實是一個四態(tài)輸入(沒錯,四種狀態(tài),連單純的應(yīng)付三種狀態(tài)的 tcp 都無能為力):000 表示沒有錢幣投入;001 表示投入了 1 元硬幣;010 表示投入了 5 元硬幣;100 表示投入了 12 元硬幣。如果少了最前面的(teq x1 0, + jmp 8)兩條指令的話,那么后面的 tcp x0 10 在遇到?000 和 001 兩種狀態(tài)時都會激活同樣的 - 號指令(投入 1 塊錢)。這顯然不是我們想要的。所以,當(dāng)狀態(tài)值為 000 時,我們需要使用跳轉(zhuǎn)指令強制跳轉(zhuǎn)到第 8 行的 slp 1,跳過 tcp 的三態(tài)判斷。
下面我們來到了 tcp 三態(tài)判斷。首先我們假設(shè)當(dāng)前的狀態(tài)值是中間狀態(tài) 010(即用戶投入的是 5 塊錢),先令 acc +5 再說(add 5)。然后若?tcp 判定出 DX-300 的值位于兩端狀態(tài),我們再做額外的調(diào)整(tcp x1?10)。當(dāng)狀態(tài)值是 001 時,說明客戶投入的是 1 塊錢,acc 要在之前的基礎(chǔ)上 -4(- sub 4);當(dāng)狀態(tài)值是 100 時,說明客戶投入的是 12 塊錢,acc 要在之前的基礎(chǔ)上 +7(+ add 7)。執(zhí)行完上述操作后,我們得到了當(dāng)前顧客已經(jīng)累計投入的金額數(shù)量。
由于本游戲?qū)τ诓坏鹊呐卸ㄖ挥小緡?yán)格大于】和【嚴(yán)格小于】兩種,所以對于【大于等于】這樣的判定,我們只能用【不小于】來判定,即使用 tlt 測試指令,然后 - 號部分寫“第一個數(shù)【不小于】第二個數(shù)時”的操作。我們先判斷當(dāng)前顧客已投入的金額數(shù)量是否【小于】價格(tlt acc x0)。若當(dāng)前顧客已投入的金額【仍小于】價格,就休眠一秒,繼續(xù)等待當(dāng)前顧客投入更多的硬幣(+ slp 1);若【不小于】價格,則將 acc 減去價格得到需要找零的數(shù)量(- sub x0),然后將找零值發(fā)給右邊的芯片,委托右邊的芯片執(zhí)行找零任務(wù)(- mov acc x3),并響鈴 4 秒(- gen p1 4 1)。做完這些事后,清除 acc 的值準(zhǔn)備迎接下一個顧客(- mov 0 acc)。
左邊的“收錢及響鈴”程序已經(jīng)完成,現(xiàn)在要開始完成右邊的“找零”程序。在這里,我們需要學(xué)習(xí) jmp 指令的另一個重要功能:實現(xiàn)循環(huán)。
假設(shè)你現(xiàn)在要往 p1 口發(fā)送一段共計 20 次脈沖的方波信號,你會怎么做?即使是代碼空間最大的 MC6000,你也寫不下 20 條 gen 指令?;蛘?,脈沖次數(shù)不確定,由另一個輸入口提供呢?你根本不知道要寫幾條 gen。
程序有三大結(jié)構(gòu):順序結(jié)構(gòu)、選擇結(jié)構(gòu)、循環(huán)結(jié)構(gòu)。我們現(xiàn)在已經(jīng)熟練掌握了前兩大結(jié)構(gòu),現(xiàn)在我們就要學(xué)習(xí)如何利用跳轉(zhuǎn)指令實現(xiàn)循環(huán)結(jié)構(gòu),生成共計 20 次脈沖的方波信號給 p1。以下是代碼和解釋:
如果把這樣的匯編代碼改寫成 C 語言的樣式,那大概長這樣:
學(xué)過 C 語言編程的同學(xué)大概一眼就能看出來,while 語句塊的部分就是一個非常典型的 20 次循環(huán)。
回到題目。當(dāng)右邊的 MC6000 芯片收到由左邊芯片傳來的“待找零數(shù)量”數(shù)據(jù)后,該如何找零呢?了解了程序的循環(huán)結(jié)構(gòu)后,我們很容易設(shè)計出這樣一套帶有循環(huán)結(jié)構(gòu)的算法:
將待找零數(shù)量放入 acc 寄存器。
判斷 acc 寄存器的值是否大于 4。若滿足條件,則令 acc -5,然后生成一個“找零 5 元”的脈沖信號,并反復(fù)執(zhí)行此步,直到 acc 寄存器的值不大于 4 為止。
判斷 acc 寄存器的值是否大于 0。若滿足條件,則令 acc -1,然后生成一個“找零 1 元”的脈沖信號,并反復(fù)執(zhí)行此步,直到 acc 寄存器的值歸零為止。
執(zhí)行到此處后,本次找零完畢,等待下一次找零信號的出現(xiàn)。
根據(jù)以上算法,我們在右邊的芯片中寫出這樣的代碼:

首先,因為找零信號不會在固定的時間出現(xiàn),而是僅當(dāng)用戶投入了足額的硬幣后才會從左邊的芯片傳過來,所以我們必須要用 slx 指令來等待信號出現(xiàn)后才能讀取找零值。然后,第 2 行對應(yīng)算法的第 1 步;第 3~6 行構(gòu)成了“找零 5 元”循環(huán),對應(yīng)算法的第 2 步;第 7~10 行構(gòu)成了“找零 1 元”循環(huán),對應(yīng)算法的第 3 步。以上任務(wù)執(zhí)行完畢后,回到第 1 行 slx,對應(yīng)算法的第四步“等待下一次找零信號”。
點擊左下角的【模擬】,稍等片刻,便會彈出結(jié)算界面:

優(yōu)化電量
將左邊那塊芯片中倒數(shù)第二行的 gen p1 4 1 改成 gen p1 4 3 即可。這是我嘗試出來的允許的最長睡眠時間。電量可以由 318 減少到 294。
碎碎念
右邊那塊 MC6000 芯片接了兩個 p 口和一個 x 口,寄存器也只用到了 acc。如果能省掉一行代碼,那么就可以毫不費力地?fù)Q成?MC4000。但是我苦思冥想,結(jié)果卻無論如何都省不掉哪怕一行代碼。于是這塊 MC6000 芯片換不成 MC4000 了,僅僅只是多一行代碼而已啊,真的好不甘心。不知道讀者們有沒有辦法能省出來一行代碼,有的話請留言告訴我。