【深圳 IO 攻略】第 7 關(guān):被動(dòng)紅外感應(yīng)器

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

本關(guān)要求在時(shí)間到達(dá)【開(kāi)啟時(shí)間】時(shí)開(kāi)啟報(bào)警器,在到達(dá)【關(guān)閉時(shí)間】時(shí)關(guān)閉報(bào)警器。同時(shí),在報(bào)警器開(kāi)啟的狀態(tài)下,當(dāng)傳感器的值 ≥20 時(shí),啟動(dòng)報(bào)警輸出(將【鬧鐘】端口置為 100),其余時(shí)刻關(guān)閉報(bào)警輸出(將【鬧鐘】端口置為 0)。這里的“鬧鐘”應(yīng)該屬于翻譯錯(cuò)誤,英文原文是 alarm,這里根據(jù)上下文語(yǔ)境應(yīng)當(dāng)翻譯為“報(bào)警”。
這道題很明顯是一個(gè)【與】邏輯,僅當(dāng)“報(bào)警器開(kāi)啟”及“傳感器值 ≥20”兩個(gè)條件同時(shí)成立時(shí),【報(bào)警】端口才為 100。我們首先想到的是:用兩塊芯片分別計(jì)算并輸出兩個(gè)條件的邏輯值,并使用【與門(mén)】處理后將最終的值送入【報(bào)警】端口。
元件面板中,【LC70G08】這個(gè)元件為【與門(mén)】。僅當(dāng)其輸入端的兩個(gè)值都為 100 時(shí),輸出端上方的值才為 100,其余情況輸出端上方的值都為 0。輸出端下方的值則為【與非門(mén)】,與輸出端上方的值互反。
我們向開(kāi)發(fā)板中拖入兩塊【MC4000】芯片和一塊【LC70G08】(與門(mén)),并按下圖所示放置好各元件,書(shū)寫(xiě)好代碼:

如上圖所示,【開(kāi)啟時(shí)間】和【關(guān)閉時(shí)間】分別接在上方 MC4000 的 x0 和 x1 口上,而實(shí)時(shí)的 RTC 時(shí)間接在了 p0 口上。所以上方的芯片里,我們需要隨時(shí)判定 p0 的值是否等于 x0 或 x1。一旦等于兩者之一,就需要改變 p1 口的值(用于打開(kāi)/關(guān)閉報(bào)警器)。
下方的 MC4000 芯片里的代碼,相信看到這一節(jié)的你已經(jīng)很熟悉了,一個(gè)最基本的判斷,當(dāng)【傳感器】的值 <20?時(shí)向 p1 輸出 0,否則輸出 100。
將以上兩塊芯片輸出的邏輯值做與運(yùn)算,最終結(jié)果輸出給【報(bào)警】端口。
點(diǎn)擊左下角的【模擬】,稍等片刻,便會(huì)彈出結(jié)算界面:

優(yōu)化三項(xiàng)指標(biāo)
上一個(gè)方案的結(jié)算界面里,我們可以發(fā)現(xiàn)三項(xiàng)指標(biāo)都沒(méi)有達(dá)到最佳。如果你仔細(xì)觀察,可以發(fā)現(xiàn)我們的 MC4000 芯片沒(méi)有得到充分利用,兩塊芯片都沒(méi)有用到 acc 寄存器。那我們可不可以用 acc 寄存器代替一塊單獨(dú)的芯片來(lái)存儲(chǔ)報(bào)警器的開(kāi)/關(guān)值,然后再使用測(cè)試指令來(lái)實(shí)現(xiàn)與門(mén)呢?答案是可以的!
這時(shí)候,我們要介紹一塊功能更強(qiáng)大的芯片:MC6000。它是 MC4000 的老大哥,各項(xiàng)功能特性均完爆小弟 MC4000,不過(guò)成本上貴 2 塊錢(qián),占用的體積稍微大一些:

MC6000 比 MC4000 多了兩個(gè)用于傳輸?shù)?x?口、額外的五行代碼空間、一個(gè)額外的寄存器 dat。這里要說(shuō)明一下,雖然 MC6000 有一個(gè)額外的寄存器 dat,但是所有的數(shù)學(xué)運(yùn)算仍然只能在 acc 中進(jìn)行。類(lèi)似 add dat 1 這樣的指令是不存在的。dat 寄存器除了不能進(jìn)行數(shù)學(xué)運(yùn)算外,其余方面和 acc 寄存器無(wú)異。
這道題共有兩個(gè)簡(jiǎn)單輸入(時(shí)間、傳感器)、兩個(gè) x 輸入(開(kāi)啟時(shí)間、關(guān)閉時(shí)間)和一個(gè)簡(jiǎn)單輸出(報(bào)警),所以需要連接五個(gè)接口??紤]到【報(bào)警】這個(gè)輸出只有 0 和 100 兩種值,所以可以經(jīng)由?DX-300 中轉(zhuǎn),由 p 口輸出改為 x 口輸出。這樣我們一共需要連接 2 個(gè) p 口和 3 個(gè) x 口,一塊?MC6000?芯片足矣。如果我們改用 MC6000 + DX-300,可以省掉一塊錢(qián)的成本。后面我們會(huì)發(fā)現(xiàn),采用此套方案后,不僅僅是成本,三項(xiàng)指標(biāo)都會(huì)比上一個(gè)方案更優(yōu)!
電路圖及代碼如下圖所示:

我們用 acc 寄存器來(lái)表示報(bào)警器的開(kāi)/關(guān)狀態(tài)。當(dāng) p1(時(shí)鐘)的值到達(dá) x0(開(kāi)啟時(shí)間)或 x1(關(guān)閉時(shí)間)時(shí),相應(yīng)地更改 acc 寄存器的值。然后我們檢測(cè) p0(傳感器)的值是否 <20(tlt p0 20)。若 <20,則無(wú)視報(bào)警器的開(kāi)關(guān)狀態(tài),強(qiáng)制輸出 0(+ mov 0 x2);否則輸出的值跟報(bào)警器的開(kāi)關(guān)狀態(tài)一致(-?mov acc x2)。由于 DX-300 的 p2 口和報(bào)警輸出相連接,所以我們給 DX-300 傳的三位數(shù)里,百位為 0 時(shí)報(bào)警輸出關(guān)閉,百位為 1 時(shí)報(bào)警輸出開(kāi)啟。和直接通過(guò) p 口傳 0/100 的效果完全一致。
此時(shí)我們?cè)龠\(yùn)行程序,觀察結(jié)算界面:

可以發(fā)現(xiàn)三項(xiàng)指標(biāo)相比前一個(gè)方案都有所提升!
極致優(yōu)化電量
我們觀察時(shí)序圖,可以發(fā)現(xiàn)以下兩條規(guī)律:
實(shí)時(shí)時(shí)鐘在下一秒鐘的值總是當(dāng)前時(shí)鐘值 +1。特殊地,如果當(dāng)前的時(shí)鐘值是 95,那么下一秒的時(shí)鐘值會(huì)歸零重新計(jì)時(shí)?;蛘呖梢赃@么說(shuō),下一秒鐘的時(shí)鐘值 = (當(dāng)前時(shí)鐘值 +1) mod 96。
時(shí)鐘跨度是 0~96,而一個(gè)樣例只有 60 秒周期,所以報(bào)警器最多只會(huì)開(kāi)啟、關(guān)閉一次。同時(shí)我們可以發(fā)現(xiàn),題目中給的每個(gè)樣例的 60 秒周期里,報(bào)警器都是先開(kāi)啟后關(guān)閉。
根據(jù)以上兩條規(guī)律,我們可以在報(bào)警器開(kāi)啟前提前計(jì)算好需要等待的秒數(shù),在此期間令芯片一直休眠,不去檢測(cè)傳感器的狀態(tài)。休眠結(jié)束后再進(jìn)入正常的檢測(cè)環(huán)節(jié)。同樣地,一旦到達(dá)關(guān)閉時(shí)間,就令芯片執(zhí)行 slp 999,讓芯片“永久睡眠”下去,和開(kāi)啟前一樣,不再檢測(cè)傳感器的狀態(tài)。如此做,即可省下大量電量。代碼如下:

前 5 行代碼用來(lái)計(jì)算當(dāng)前時(shí)間距離【開(kāi)啟時(shí)間】有多遠(yuǎn)。我們用開(kāi)啟時(shí)間減去當(dāng)前時(shí)間(@ mov x0 acc, @ sub p1),并檢測(cè)差值是否為負(fù)數(shù)(@?tlt acc 0)。如果差值為負(fù)數(shù),說(shuō)明開(kāi)啟時(shí)間在下一個(gè) 0~95 的循環(huán)周期里(例如:當(dāng)前時(shí)間 95,開(kāi)啟時(shí)間 0,那么需要等待 1 秒鐘),那么就要把 acc 加上 96 得到正確的等待時(shí)間(+ add 96)。計(jì)算好了等待時(shí)間后,睡眠這么長(zhǎng)時(shí)間(@ slp acc)后,報(bào)警器自動(dòng)開(kāi)啟。
第 6~9 行的代碼完成的任務(wù)是【已知報(bào)警器打開(kāi)的狀態(tài)下】,檢測(cè)傳感器的值是否 <20,以此決定是否需要報(bào)警。
第 10~12 行的代碼用于實(shí)時(shí)監(jiān)測(cè)當(dāng)前時(shí)間是否已到達(dá)【關(guān)閉時(shí)間】。尚未到達(dá)時(shí),由于激活的是 - 前綴的指令,所以會(huì)跳回第 6 行(第一條非 + 非 @ 前綴指令)執(zhí)行。一旦到達(dá),則向報(bào)警口輸出 0(關(guān)閉報(bào)警),然后 slp 999 永久睡眠。
點(diǎn)擊左下角的【模擬】,稍等片刻,進(jìn)入結(jié)算界面:

可以看到此時(shí)電量消耗驟降到了 230,因?yàn)樵趫?bào)警器關(guān)閉的狀態(tài)下,芯片便不再探測(cè)傳感器的值,也不再改變報(bào)警輸出的值,因此電量被大幅節(jié)省。
2022 年 9 月 6 日更新:更極致的省電方案——預(yù)計(jì)算睡眠時(shí)間 + 使用與門(mén)
上一版方案里,我們預(yù)先計(jì)算好了當(dāng)前時(shí)間距離開(kāi)啟時(shí)間有多遠(yuǎn),以便在到達(dá)開(kāi)始時(shí)間前保持睡眠狀態(tài)。可開(kāi)始工作后,我們卻使用了反復(fù)判斷是否到達(dá)關(guān)閉時(shí)間的做法,效率上有所浪費(fèi)。
此時(shí)我們?cè)倩乜闯醢媸褂昧伺c門(mén)的那個(gè)方案,那個(gè)方案里,兩塊芯片分工合作,一個(gè)控制總開(kāi)關(guān),一個(gè)控制傳感器子開(kāi)關(guān),兩個(gè)開(kāi)關(guān)量用與門(mén)串聯(lián)作為報(bào)警器的輸出。這里,我們只需要把控制總開(kāi)關(guān)的芯片由【每秒判斷是否打開(kāi)或關(guān)閉總開(kāi)關(guān)】改為【預(yù)先計(jì)算好睡眠時(shí)間,定時(shí)打開(kāi)或關(guān)閉總開(kāi)關(guān)】,即可形成一個(gè)完美的省電方案。電路圖和代碼如下:

這次,為了布線上的方便,我們將控制總開(kāi)關(guān)的芯片挪到了下方,同時(shí)因?yàn)榇a行數(shù)到達(dá)了 10 行,芯片也升級(jí)為了?MC6000。
第 1~4 行計(jì)算的是 (start - now + 96) mod 96 的值,即當(dāng)前時(shí)間距離開(kāi)始時(shí)間有多遠(yuǎn)。計(jì)算完成后,執(zhí)行第 5 行的 slp 指令,令芯片睡眠這么長(zhǎng)時(shí)間。
第 6~9 行計(jì)算的是 (end - start + 96) mod 96 的值,即結(jié)束時(shí)間距離開(kāi)始時(shí)間有多遠(yuǎn)。計(jì)算完成后,執(zhí)行第 10 行的 gen 指令,令 p1 口輸出這么長(zhǎng)時(shí)間的 100 信號(hào),然后當(dāng)?shù)竭_(dá)關(guān)閉時(shí)間后,清除 p1 口信號(hào)并永久睡眠。
點(diǎn)擊左下角的【模擬】,稍等片刻,便會(huì)彈出結(jié)算界面:

電量由 230 進(jìn)一步降低到了 192。