前言,

前一篇部落格文章,談到了 Arduino 的看門狗 Watchdog。因為發現很少然談及這塊,所以程式碼採用 Arduino IDE 的 C/C++ 來撰寫。來熟悉一下 Atmel 原廠的 avr 函式庫的呼叫。

但這裡的部落格,是要盡可能來推廣 FORTH 語言的。所以同樣的主題跟程式,我們也來一篇 Flash FORTH 版本的吧。來熟悉一下 Flash FORTH 下, Arduino 的看門狗如何操作。

關於看門狗,搞不清楚這是什麼的,請看我的前一篇部落格文,有來龍去脈,應該蠻有助於暸解這是怎麼回事,這裡就不贅述了。這一篇,是上一篇的延伸,補充 Flash FORTH 的看門狗 Watchdog 的使用方式。

 

Watchdog 的組成

這裡簡單貼一下前一篇部落格文的結果,細節來龍去脈請參考前一篇部落格文。

看門狗就是一個接在 CPU Reset 腳位的計時器,當它開始計時時 CPU 裡面的軟體要不斷的送訊號出來重置這個計時器。否則計時器時間到了,計時器會反過來 Reset CPU,讓 CPU重頭來過。

CPU 送訊號出來,有點像是心跳 (Heartbeat) 當心跳沒了,計時器計時時間到了會跑過來按 CPU 的 Reset 紐,來拯救這個已經沒有心跳訊號的 CPU。

所以組成的元件就是很簡單的一個硬體計時器而已。

 

ATmega328 (Flash FORTH) 的 Watchdog

一樣,請參考前一篇的介紹,這裡不多贅述囉。這裡直接切入 Flash FORTH 在 ATmega328 上面, Watchdog 的使用。

Flash FORTH 在 Arduino 上面的 Watchdog 使用方式很簡單,就三個字 wd+ wd- cwd 。來一個一個介紹一下

 

wd+ ( timer-enum --)

這個指令用來啟動 Watchdog 的使用, 傳入 timer-enum 參數用來指名 Watchdog 計時器的計時時間。

Atmega328 的 Watchdog 計時器的計時器時間,由暫存器 WDTCSR 所控制

WDTCSR.png

其中 WDP3, WDP2, WDP1, WDP0 這四個位元控制了 Watchdog 的計時時間,其時間如下所示

WDT Prescale Select.png

WDT Prescale Select2.png

不知道怎麼搞的, Ateml 設計 Avr晶片的時候,將 WDP3 跨了 2 位元。也許是這樣吧,所以 Flash FORTH 不支援 WDP3。所以 wd+ 這個指令,只支援 0 - 7 的計時器計時時間。亦即,只支援 16ms,32ms,... 最多到 2.0 s

所以,把這些 enum 定義為常數吧,這樣使用上會方便些。 FORTH 程式碼如下

 

%000000 constant  16ms  
%000001 constant  32ms 
%000010 constant  64ms 
%000011 constant 125ms 
%000100 constant 250ms 
%000101 constant 500ms 
%000110 constant    1s 
%000111 constant    2s 

 

要開啟 1 秒計時時間的 Watchdog,指令就變成 1s wd+

 

 

wd- ( --)

用來關掉 Watchdog 計時器電路的運作,關掉後就不會隨便跑去 reset 微處理器囉。

 

cwd ( --)   clear watchdog timer

用來重置歸零 Watchdog 計時器的計時時間,如前面所述的,你的程式在 Watchdog 計時器計時時間未到達前,需要利用這個 cwd 指令不斷重置歸零計時器,否則計時時間一到,會被視為軟體已經當機囉,Watchdog 電路會自動地硬體重置整個微處理器的運作。

 

內建上拉電阻

這裡我們的程式因為要使用內建上拉電阻的功能,所以來介紹一下。

平常我們開關電路有兩種接法,一種是上拉電阻法,另一種是下拉電阻法。圖示如下,

上拉電阻法,輸入 DIO 為 High,按下開關接地後為 Low。

sw2.png

下拉電阻法,輸入 DIO為 Low,按下開關後接上電源為 High

sw1.png

所以正常來說,為了要接上開關,我們外部通常要準備一個電阻來跟開關來搭配。看是要上拉電阻法,還是下拉電阻法。

但 ATmega Avr 系列的單晶片,真的是很貼心。它的所有 DIO 都有二極體的保護,對於短路跟過電流有一定的承受能力。(所以跟那個弱弱的 GPIO 動不動就燒掉,然後貴森森的 Raspberry Pi 來比,一個是天,另一個是地啊! Arduino 便宜又耐用啊!)

更讓人難以置信的是,每個 DIO 都已經內建上拉電阻的電路囉。所以要做開關的話,只要簡單地設定啟動內建上拉電阻的電路,就一切搞定,真的是非常方便啊!

ATmega DIO 的電路圖

Arduino DIO.png

可以看到 Pin 入口後,左邊有個上拉電阻,由一個 MOS 開關來控制要不要啟動接上電源 Vcc

然後這個 MOS 開關,由 PUD (Pullup Disable),控制方向的 DDR 正反器 (WDx, RDx),及 Pin 狀態的 PORT 正反器 (WRx, WPx) 來共同決定。

結論如下表

Arduino DIO Config.png

首先是在 MCUCR 控制整個MCU狀態的暫存器中的 PUD (Pullup Disable) 位元。這個位元有點像全域變數般,設成 1 的話可以將整個 MCU 中所有 DIO 的上拉電阻關掉,無視其他設定。

接下來就簡單了,當 DDxn (DDR) 為 0 表示此 DIO 方向為輸入的時候,此時 PORTxn 寫入 1 的時候,控制上拉電阻的那個 MOS 開關會打開,接上電源 Vcc。 當 PORTxn 寫入 0 的時候,控制上拉電阻的那個 MOS 開關會關閉,維持在高阻抗。

所以要使用內建上拉電阻,很簡單的。 DIO 方向設為輸入,再將 DIO 寫入 1 就可以囉, FORTH 程式碼如下 (以 Pin2 舉例)

Pin2 <INPUT  設定 Pin2 為輸入

Pin2 ->High   對 Pin2 寫入 1  (啟動 Pin2 內建上拉電阻 )

為了讓語意更甘甜些,定義了一個沒有功能的立即字 PULLUP 

: PULLUP ; immediate

所以

Pin2 <INPUT                 設定 Pin2 為輸入

Pin2 PULLUP ->High     啟動 Pin2 內建上拉電阻 

 

FORTH 的 Turnkey

FORTH裡面,當你完成你的程式後,你的主程式是個指令。真實世界當然不可能叫使用者進終端機模式去鍵入執行這個指令,而是要包裝成最終的應用程式。然後把整個 FORTH 系統沒用到的累贅全部移除,最佳化整個系統的資源。

像這樣,萃取程式碼,最佳化編碼跟系統的使用並建構最終應用程式的過程,在 FORTH 裡面被稱作叫 Turnkey。這個過程是高度跟 FORTH 的系統及電腦硬體和作業系統相關的。不同的系統整個 Turnkey 過程很不一樣,所以沒有一個標準的程序。

在 Flash FORTH 裡面, 開發者也提供了 Turnkey 的功能, 整個過程非常簡單的。就

' (word of your code) is turnkey

就把你最終程式碼的指令的執行位置,透過 is 寫入 turnkey 的延遲定義的變數就可以囉。

Flash FORTH 開機後, 會先看一下 turnkey 這個延遲定義變數,如果非零就會立刻跳去它所指向的執行位址來執行。這樣就完成一開機就自動執行我們所要的特定動作的這個目的啦。

在跳去執行前, Flash FORTH 會短暫的偵測一下, UART 的 Port 是否有人按了 ESC 的輸入,有的話就會停止執行,讓使用者有機會重新獲取系統的控制權。

 

FORTH 程式碼解說

前面是 DIO 的控制定義,看不懂的話請看我這一篇部落格文章,這裡就不贅述了!

所以重點在 main 這個主測試程式

: main

   ." Resting..." cr
   
   LED-Pin     >OUTPUT
   
   button-Pin  <INPUT
   button-Pin  PULLUP ->High
   
   1s wd+

   begin
     toggle @
     if    LED-Pin ->Low   toggle Off
     else  LED-Pin ->High  toggle On   then
     
     delay
     
     button-Pin Pin@ 0=
     if  cwd  ." Kick..." cr then
     
   again
;

一開始,從終端機列印個 Resting...,讓使用者辨識一下,系統有被重啟。

然後設定一下 LED-Pin (這裡是 Pin13, Arduino Uno上版子內建一顆 LED 是接在 Pin13 的。所以可以直接控制它閃爍,非常方便),將 LED-Pin 的 DIO 的方向設定為輸出。 LED-Pin >OUTPUT

再來是 button-Pin 的 DIO (這裡是 Pin2) ,設定為輸入  button-Pin <INPUT 。且將內建上拉電阻電路打開 button-Pin PULLUP ->High

再來是設定開啟看門狗 watchdog 監控電路,計時器時間 1秒。  1s wd+

然後開始無窮迴圈 Loop 囉。 ( begin-again)

toggle 是個邏輯變數, 等於0 的時候 LED 不亮, 不等於0的時候 LED亮。 FORTH 一般來說,採用等於0的時候邏輯值為 false,此時 IF 敘述不執行。不等於0的時候邏輯值為 true,此時 IF敘述執行。

toggle 為真的時候,表示 LED之前是亮著的,所以 LED-Pin ->Low 把 LED 關掉, toggle Off 把 toggle 邏輯變數設成 false。

toggle 為假的時候,表示 LED之前是暗著的,所以 LED-Pin ->High 把 LED 點亮, toggle On 把 toggle 邏輯變數設成 true。 就這樣,讓 LED 不斷閃爍。

然後是 delay 個約 0.5 秒左右,原來是採用 500 ms 的這個指令。不過測試後發現, Flash Forth 的開發者不知道是怎麼實作這個指令的,執行後 watchdog 計時器會停掉,完全無法運作。

只好自己用個雙重 for-loop 來延遲一下執行時間,當然這樣delay的時間就不會很準啦,自己隨便亂抓的。

: delay 5 for $ffff for next next ;

Delay 完後,讀一下 button-Pin 的 DIO,假如等於0的話,表示 button-Pin 的那條線是接地的 (Low),沒有被拔掉。 下一下 cwd 看門狗計時器 reset 的指令,踢一下看門狗告訴它我們還活著,不要亂 reset 微處理器。

然後迴圈繼續。

 

硬體上, Pin2 是接了一條線到接地。所以程式運作像這樣,

讓 LED 閃爍一下,等個約 0.5秒,看一下 Pin2是不是還是 Low。如果還是 Low,踢一下看門狗告訴它我們還活著。

如果 Pin2 是 High,表示 Pin2那條線被拔掉了。因為停止踢看門狗了,所以一秒後,看門狗 reset 微處理器,程式重頭來過。

 

' main is turnkey 前面有介紹了,這樣設定後每次開機都會自動執行 main 這個指令

 

測試實況

如影片所示,約 0.5秒左右,看門狗會被踢一次。

只要 Pin2 的接地線一拔掉,1 秒後看門狗發現了,整個微處理器會被重啟,Flash FORTH 系統會被重新載入,然後自動重新執行 main,最後一切恢復原狀。

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Frank Lin(@ohiyooo)分享的貼文

 

 

原始程式碼列表

 

\
\  Arduino Uno Flash FORTH Watchdog test
\      Frank Lin 2021.7.17
\    

\ uno dio

marker --uno_dio--

\
\ bit masks
\


%00000001 constant bit0
%00000010 constant bit1
%00000100 constant bit2
%00001000 constant bit3
%00010000 constant bit4
%00100000 constant bit5
%01000000 constant bit6
%10000000 constant bit7


\
\ Atmega328 Digital I/O Registers
\

  

#37 constant PortB
#40 constant PortC
#43 constant PortD

 

: Pin_Def ( mask Port "Name" --)
   create swap c, c,
   does>  dup  c@        ( mask)
          swap char+ c@  ( Port)
;


\
\  Arduino Uno with ATmega328 Pin definations
\


flash

bit0 PortD  Pin_Def Pin0
bit1 PortD  Pin_Def Pin1
bit2 PortD  Pin_Def Pin2
bit3 PortD  Pin_Def Pin3
bit4 PortD  Pin_Def Pin4
bit5 PortD  Pin_Def Pin5
bit6 PortD  Pin_Def Pin6
bit7 PortD  Pin_Def Pin7

bit0 PortB  Pin_Def Pin8
bit1 PortB  Pin_Def Pin9
bit2 PortB  Pin_Def Pin10
bit3 PortB  Pin_Def Pin11
bit4 PortB  Pin_Def Pin12
bit5 PortB  Pin_Def Pin13

bit0 PortC  Pin_Def Pin14
bit1 PortC  Pin_Def Pin15
bit2 PortC  Pin_Def Pin16
bit3 PortC  Pin_Def Pin17
bit4 PortC  Pin_Def Pin18
bit5 PortC  Pin_Def Pin19

 

\
\   Digital I/O Access Codes
\


: >OUTPUT ( mask port --)     \ set DIO as output
   1-     ( DDR register)
   mset   ( set to High = OUTPUT)
;

: <INPUT  ( mask port --)     \ set DIO as input
   1-     ( DDR register)
   mclr   ( set to Low = INPUT)
;

: PULLUP ( --)                \ dummy for sweeter syntax
; immediate

\  Example:
\  PinXX PULLUP ->High        \ enable  pullup resistance
\  PinXX PULLUP ->Low         \ disable pullup resistance
\


: ->High  ( mask port --)     \ put digital I/O to High
   mset
;

: ->Low   ( mask port --)     \ put digital I/O to Low
   mclr
;

: Pin@ ( mask port -- status) \ read the state of DIO
   2-     ( Pin register)
   mtst   ( read the state)
;

 

\ facility words

: On   true  swap ! ;
: Off  false swap ! ;


\ enum for watchdog timer-time

%000000 constant  16ms  
%000001 constant  32ms 
%000010 constant  64ms 
%000011 constant 125ms 
%000100 constant 250ms 
%000101 constant 500ms 
%000110 constant    1s 
%000111 constant    2s 


flash 

Pin13 Pin_Def LED-Pin
Pin2  Pin_Def button-Pin


variable toggle


: delay 5 for $ffff for next next ;


: main

   ." Resting..." cr
   
   LED-Pin     >OUTPUT
   
   button-Pin  <INPUT
   button-Pin  PULLUP ->High
   
   1s wd+

   begin
     toggle @
     if    LED-Pin ->Low   toggle Off
     else  LED-Pin ->High  toggle On   then
     
     delay
     
     button-Pin Pin@ 0=
     if  cwd  ." Kick..." cr then
     
   again
;

 

' main is turnkey

 

 

arrow
arrow

    ohiyooo2 發表在 痞客邦 留言(0) 人氣()