SDCC, HC08QT4, software dealy 1ms


https://wp.me/ph3BR-2ir

 

這個練習題, 基礎是 https://xiaolaba.wordpress.com/2018/06/05/sdcc-hc08/

實際操作, 用 HC08QT4, 內部的 RC 震盪器, 不需要外部 XTAL.

測試用電路圖,

 

 

其中關鍵的部分, 如何透過軟件操作達成1ms的計時基礎. 基本上, 點亮一個LED, 等一下, 關閉, 再等一下, 再次點亮LED, 如此循環就會看到 LED 有規律的亮滅. 那個 [等一下], 就是計時的關鍵, 其實也很簡單, 讓CPU做點沒意義沒營養的事情, 浪費時間就可以了, 從外面看起來就是在 [等一下].

CPU 只要有了電力, 就會不停歇在動作, 有些動作可以被人眼看見, 有些則看不出來. 先來看看人的眼睛可以觀察到的部分, 以下的寫碼blinkLED()及解說, toggle LED PIN, 意思就是把 LED 開開關關, 如此就會看到LED閃爍了; delay(500), 就是每次開或者關以後,  等一下, 時間半秒; while(1) {}, 就是在大引號之間的內容, 永遠不間斷的重複執行動作,

void blinkLED() {
  // blink the LED
  while(1) {
    PTA ^= myLED; // toggle LED PIN, on / off 
    delay(500);  // delay about 500ms = 0.5 second
  }
}

 

.

.

.

囫圇吞棗一般, 大概了解了LED閃爍的寫碼, 仔細看那個 [等一下] 的來源和意義, 源碼是這樣理解的, delay1ms(), 就是等個1ms, 或者等個千分之一秒. 開始計次從500開始, 每次等待1ms結束, 就扣減1. 500次計算完畢, 就耗時500ms, 也就是半秒, 結束這個 [等一下] 的動作.

其中 while( ms- – ) {} 實際就是 while( 500 – -) {}, 意思如上, 從500開始, 隨後的兩個減號, 意思是每次扣減1. while( 500 – – ){}, 意思是重複大引號之間的內容直到500變成0,

void delay(unsigned int ms) {
  while(ms--) {
    delay1ms();
  }
}

.

.

.

順藤摸瓜, 既然 [等一下] 的基本單位是1ms, 那麼 delay1ms() 又是怎麼來的!? 為了理解這個, 首先要簡單闡述CPU的宏觀動作原理.

簡單來說, CPU要運轉, 必須有個 CLOCK (時鐘訊號) 去推它一把, 就像一般時鐘的秒針, 滴答滴答的推動CPU. 這裡假設每一個滴答, CPU 就執行一個動作 (實際上不同的CPU有的動作要一個以上的滴答, 暫且不表). 所以, CPU 動作多少次, 就大概等於它接受多少次滴答, 只要知道一個滴答的時間長度, 再乘上滴答的總數, 就得到總共耗時.

這裡的 CPU 是 HC908QT4, 它的時鐘訊號並非每秒滴答一次, 而是每秒滴答三百二十萬次, 簡稱是 3.2MHZ, 它的每個滴答等於三百二十萬分之一秒, 簡化寫成 1 / 3.2 us, 單位 uS 微秒.

如果推給CPU有1000個滴答, 它會執行大約1000個動作. 反過來看, 如果確實知道它執行了1000個動作, 那需要用到的是1000個滴答的時長. delay1ms() 就是這樣衍生來的. 當然, 這個方法完全要CPU在做沒營養的動作, 唯一目的就是消耗預期長度的時間, 稱為 [software delay] 或者 [軟件延時], 也有人稱為 [軟件計時].

為了要精確設計CPU運行在期望時間長度, 安排CPU的每個動作就必須嚴謹, 這裡必須用 ASSEMBLY LANGUAGE, 依照不同CPU的類型, 寫碼會不用, 但是原理是一樣的. 抄襲和修改, 這個CPU有兩個動作的指令, 既沒有影響他人, 也達到計數計時的目的, 先來看這段,


; // one loop 16 cpu cycles
loopa:
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nop ; [1] // no operation
dbnza loopa ; [3] // Decrement Accumulator, Branch if Not Equal ($00)

nsa ; [3] // swap nibble of Acc, 這個動作, 簡單來說, 有個箱子名為Acc, 左右分成兩格, 每格稱為一個nibble, 每格分別放了四顆豆子. CPU 會把左右格子裡面的東西對調, 完成這個動作要3的滴答的時長. 所以, 連續做4次, 結果箱子內的設定沒變過, 沒有影響其他人, 卻耗用了3×4=12個滴答的時長.

nop ; [1] // no operation, 這個動作, 更簡單, 直接說明, 不做任何事, 只耗時一個滴答的時長, 幾乎任何CPU都有這個指令和動作.

dbnza loopa ; [3] // Decrement Accumulator, Branch if Not Equal ($00), 這個動作比較有含意, 它的意思是, 把 Acc 減去1, 如果還沒有到0, 就跳到 loopa 的位置, 繼續做白工.

有了這些動作的基本解釋, 可以看出來, 上面那段寫碼, 就算甚麼都不知道, 假設 Acc 原來是1, 那它跑一次用了16個滴答的時長; 假設 Acc 原來是2, 它跑兩圈, 用2×16=32個滴答的時長, 如此類推.

.

.

.

前面說過, 這裡的 CPU 是 HC908QT4, 它的時鐘訊號是 3.2MHZ, 它的每個滴答等於 1 / 3.2 us, 單位 uS 微秒. 要它消耗耗用1ms = 1000us, 到底要多少個滴答? 小學的數學, 1000us / (1/3.2us) = 1000×3.2 = 3200, CPU跑3200個滴答, 時間就是1ms. 那要它跑前面那段 16個滴答的 loopa, 1ms 可以跑幾圈呢 ? 答案也不難, 3200 / 16 = 200 圈, 好了, 記得前面提到若 Acc =1, 會跑1圈用16個滴答, 所以 Acc =200 會跑200圈用16×200=3200個滴答, 剛好就是1ms. 把上面那段原碼, 多加一個動作, 設定 Acc = 200, 得到以下的升級版本,


lda #200-1 ; [2] // number of loops = 200, set loops need 2 CPU cycles

; // one loop 16 cpu cycles
loopa:
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nop ; [1] // no operation
dbnza loopa ; [3] // Decrement Accumulator, Branch if Not Equal ($00)

其實加上這句, lda #200-1 ; [2] // number of loops =200, set loops need 2 CPU cycles, 設定 Acc = 200-1 = 199. 不是說好是200嗎? 為何比變成200-1 = 199 ?

.

.

.

這樣說吧, 上班下班都要時間, 工作8小時, 其實為了工作不只付出8小時, 老闆沒那麼仔細付你車程的薪水, 不過要精算的話, 你自己必須把上班下班耗在路上的小時也算上, 才可以得到最精確的時薪. 意思大概就這樣, 要CPU來這上班然後跑這個3200個滴答時, 它上工要一些時間, 做完回家也要一些時間, 所以作為大好人, 俺們必須讓CPU精準的連上下班時間都包含, 只要求精準3200的滴答, 不多不少, 就只要它跑200-1個圈, 剩下的一個圈沒跑的有16個滴答, 就給它作上下班用, 還有設定Acc=200的那一步. 不過, 它的上下班速度很快, 來去只要5+4個滴答, 設定Acc=200用2個滴答, 總共才11個, 比16個還短少5個. 那用膝蓋都可以猜得到, 請CPU再單獨作5個滴答, 完完整整它會跑3200的滴答, 很準確的完成了1ms的delay, 看起來沒作甚麼, 只是很精準的等待了1ms. 因此有了這段完整的源碼,

 


void delay1ms() {
// internal RC 12.8MHz, CPU speed = 12.8/4 MHZ = 3.2MHz
// 1 cpu cycle = (1/3.2) us

// calculation for number of CPU cycles for 1ms
// 1ms = 1000 us
// 1000us / (1/3.2)us = 1000x3.2 = 3200 CPU cycles
// 3200 cycles / [16] = 200 loops
// at 3.2MHz Bus Clock -> 1ms

// each call/return for this subroutine, extra 5+2+4 CPU cycles;
// add 3 dummy instructions, 3+1+1, sum as [16], equal 1 loop

// so total loops required = 200-1

// working syntax with SDCC 3.6.0 or the inline assembler
//
// __asm
// ...
// ...
// __endasm
//
// see SDCC manual, page 49
__asm
;; jsr [5] // caller need 5 CPU cycle to get here
lda #200-1 ; [2] // number of loops = 200, set loops need 2 CPU cycles

; // one loop 16 cpu cycles
loopa:
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nsa ; [3] // swap nibble of Acc
nsa ; [3] // restore Acc
nop ; [1] // no operation
dbnza loopa ; [3] // Decrement Accumulator, Branch if Not Equal ($00)

nsa ; [3] // dummy
nop ; [1] // dummy
nop ; [1] // dummy
;; rts ; [4] // return to caller
__endasm;

}

有部分細節, 看得懂或看不懂也無所謂, 意思到了, 大概了解就很夠了.

交功課, 完整的源碼和建立方式, firmware 燒錄用hc08模擬器

https://github.com/xiaolaba/HC08_SDCC_BLINK_C/tree/master/HC08QT4_c_blink_sdcc360_test

 

 

.

.

.

h_ttps://youtu.be/sOvjz38HGmw

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.