記錄一下系統開機或是被 reset 時的一些旗標狀態。
旗標狀態
| reset | WTRF | POR | EWT | WDCON | 判斷 |
| power-on reset | 0 | 1 | 0 | 01000000b | POR=1 |
| external reset | x | x | x | 0x0x0xx0b | EWT=1* POR=0# |
| watchdog reset | 1 | x | x | 0x0x01x0b | WTRF=1 |
註解
- x: 保留原值,不會變更。
- *: 系統如果有開啟 watchdog 功能,在發生 external reset 時,EWT 一定是 1。
- #: 一開機時 POR=1,此時把 POR 設成 0,下次發生 external reset 時,POR 一定是 0,以此來區分。適合沒有開啟 watchdog 功能的系統。
判斷順序
- 開啟 watchdog: WTRF=1(watchdog) -> EWT=1(external) -> POR=1(power-on)。
- 關閉 watchdog: POR=1(power-on) -> POR=0(external),watchdog 不用判斷。
reset 開機狀態
- Program Counter 強制跳回 0000h,也就是從頭開始執行。
- SFR 都會返回 reset value,datasheet 有一張表。
- stack point 會變成 07h。
- Vdd 小於 2V 時,RAM 資料會遺失;反之則會保存。
到這裏應該算是一個總結了,後面有什麼功能,再慢慢來想吧。這次主要是加入了 rtos delay 函數,讓 delay 更為精準,也讓空出來的時間,給其它 task 去執行,並且加入了 rtos_idle 函數,利用 cpu idle 功能達到省電功能,當然如果你有用到 PWM 時,記得把這個功能換掉,免得 PWM 不動了。
主要加入的功能
- rtos_idle 函數主要是在所有 task 都在 wait 時要執行的函數。由於 rtos_idle 沒有用到任何的堆疊空間,所以在計算每一個 task 的堆疊空間時,把 idle task 的空間直接定義成 17 bytes,就造成 size = (256 - SP - 17) / MAX_TASKS;,而非 size = (256 - SP) / (MAX_TASKS + 1);。
- rtos_nexttask 函數主要是用來做切換任務的工作,並根據 rtos_tick 決定是否要執行。
- rtos_isr 函數中加入了計時功能(以 10ms 為一個單位),並把 nexttask 功能分離出來,並加上 RETI 把後面的 POP 擋掉。
- rtos_scheduling 函數主要是用在非中斷環境下執行 nexttask 功能。
- rtos_delay_10ms 函數主要是用來做時間延時用的,每 10ms 為一單位。
程式執行結果為 0.5 秒顯示數字,1 秒顯示字母,也就是看到 1a23b45c67d89e12f34g56h78i9 如此循環顯示的字串。
這次修改的內容,主要是加入 code banking 功能。
- 這次主要是參考 Keil C51 的 RTX-51 code banking 設定,利用 ?B_CURRENTBANK 及呼叫 ?B_RESTORE_BANK 來切換。
- L51_BANK.A51 除了主要的設定外,需要把 ?B_RTX 也設成 1,才能讓 ?B_RESTORE_BANK 函數可以使用。
- 在初始化過程中,也要先切換第一個 task 的 code bank。
在 tiny rtos for 8051 part2 裏,本來想把已經測試好的程式碼加入 code banking 的相容性,但經過幾天幾夜的測試,不管怎麼做都會出錯,真是莫名奇妙。後來就倒回去看看是否之前程式碼是否也有問題,結果真的是如此。part1 的程式碼執行無誤,part2 的程式碼執行有時會出錯。經過反覆檢查,發現把 interrupt 的程式碼全改成 assembly 就會出錯,即使照著 Keil C51 編出來的 assembly code 寫進去也是一樣有問題,只能猜想是否在 link 過程中,又被動了一些手腳,造成程式莫名奇妙的出錯吧。
這次主要修改的內容
- 原本 rtos_isr 函數維持加入部份 assembly code,不要全部改成 assembly code。
- ?C_IBP 儲存方式改成放入堆疊裏,以利精簡程式碼,減少 interrupt 執行時間。
- 原本的 void (* const task_func[MAX_TASKS])(void) 改成 unsigned int code task_func[MAX_TASKS],將函數指標改成一般變數放在 rom 裏,如此可以節省記憶體用量,也精簡程式碼。
- 為了避免 LCALL rtos_start 變成 LJMP rtos_start 問題,直接改變 SP 的初始化過程,藉由修改 STARTUP.A51 來增加 STACK 位址的讀取。
- 不可以將 STARTUP.A51 中的 IBPSTACK 設成 1,原來使用 reentrant 函數都需要將 IBPSTACK 設成 1,但是不知什麼原因,在用 IspWriter 燒錄完後的自動重啟執行都會出現怪怪的執行結果。
經過反覆測試,發現將 interrupt 全改寫成 assembly 會造成不明原因輸出錯誤,這個原始碼不要用喔,後面有解決方法。
原始碼下載在這次的實驗裏要解決 reentrant 函數的問題,其實也很簡單,就是把 reentrant 的 stack 變數依照每個 task 儲存起來,執行該 task 時,再回復其值就好了。程式碼如下,都是用 c 語法做說明,實際程式碼很多都改成 assemble 語法,主要是因為 Keil C51 的 reentrant stack 是存在 ?C_IBP,而 C 語法不允許變數有 ? 符號,所以就改成用 asseble 來寫。
接下來二個 test tasks 就全改成 reentrant,如此就不會有 data overlaying 的問題,而且 Code Optimization Level 也可以調到 7:Extended Index Access Optimizing。執行結果就會看到數字跟英文字母相互出現。
寫到這裏也許有人會覺得奇怪,為什麼 Code Optimization Level 沒有調到預設值 8:Reuse of Common Entry Code?主要是因為在 rtos_start 裏,我利用 LCALL 的特性把 stack 裏的返回位址改成 task1 的位址,而 level 8 會把 main 函數裏的 LCALL rtos_start 改成 LJMP rtos_start,造成程式無法執行。當然只要一個小修改就可以修正掉這個 bug 了。
最近有空去研究了一下陳明計的 small rtos for 8051,看了老半天,還是看不懂他在寫什麼;尤其是他在 stack 的處理方式,實在是有看沒有懂。所以自己就自己來寫了一套 tiny rtos for 8051。沒有 semaphore,也沒有 task priority,什麼都沒有,只有簡單的時間分配,也就是所有工作時間都一致,current task 工作固定時間後,再換 next task 工作固定時間,如此循環工作。
工作原理也很簡單,如果有 n 個 tasks 就把 stack 分成 n 等份,讓每個 task 保有自己的 stack,不要互相干擾就可以。再利用 timer interrupt 來切換 task。很簡單吧,就如下面程式碼所列,並不會很難。
寫了二個測試函數,主要就是從 rs232 印出 1~9 及 a~i,如下所示。
但是結果卻不如預期,跑出來的東西都是亂碼,傻眼。最後,經過了二天的努力,總算把結果正確無誤的弄出來了。主要有下列幾個地方要修改。
- 把 C51 的 Code Optimization Level 降成 1:Dead code elimination。因為 Data overlaying 的關係,造成 task1 的 i 變數跟 task2 的 j 變數共用同樣的位址,所以讓印出來的值都在 1~9 跳動。
- 原本 data overlaying 問題想要利用 reentrant 指令來建立可重入函數去解決,但最後發現仍然是無效。因為 keil 把變數都放在一個 ?C_IBP 的位址,仍然會造成共用的問題,這個問題真是難解啊,以後如果要寫複雜的 multi-task 函數有一定的難度,必須克服可重入函數及 data overlaying 造成的問題。
- 在 rtos_isr 函數裏加入 push 及 pop 指令,把 R0~R7,ACC,B,PSW,DPH,DPL 都丟到 stack 裏去。因為 keil 都會利用這些暫存器做一些運算,在進入 interrupt 時,也會主動 push 一些在 interrupt 會用到的暫存器到 stack 裏,但其它沒用到的暫存器就沒有主動 push,造成切換到其它 task 再切回原來 task 時,那些暫存器裏的值都被變更了,而造成運算錯誤。
- 另外在加入 assembly code 時,需把 Generate Assembler SRC File 及 Assemble SRC File 都打勾才可以,缺一不可。
- copy C:\Keil\C51\LIB\L51_BANK.A51 到專案目錄下。
- 修改 L51_BANK.A51 中的設定。
?B_NBANKS EQU 4 改成 ?B_NBANKS EQU 2。 - 在 mcu_init 中加入 ROMCON = 0x0A;,藉此設定 P1.2 as A16 並開啟 code banking 功能。
- 將 L51_BANK.A51 加入 project source file 裏。
- 進入 Project --> Manage --> Components, Environment and Books。
- 在 Groups 裏新增 bank0 及 bank1,完成後會在 Project Workspace 會出現 bank0 及 bank1 二個目錄。
- 在 bank0 按右鍵,選擇 Options for Group 'bank0',在 Code Bank 處選擇 "Bank #0"。
- 在 bank1 按右鍵,選擇 Options for Group 'bank1',在 Code Bank 處選擇 "Bank #1"。
- 在 bank0 及 bank1 各新增一個 souce file。
- 完成編譯後,原本會產生一個 xxx.HEX,現在則產生 xxx.H00 及 xxx.H01 二個檔案。
測試原由
SDCC 的 serial.h 內有一行 void autobaud(); 宣告,表示有自動鮑率偵測功能。所以就在 W79L632A 上實驗是否可行。它的原理是假定使用者第一次會輸入 return 鍵,也就是 ascii code 13。依照這個值利用 timer1 算時間差來做運算,進而取得 timer1 8-bit auto reload 的 TH 值。以下是測試的程式碼,大多是參考 SDCC 內的 serial.c 函數功能。變數宣告
輸出函數
輸入函數
主要函數
測試結果
結果非常失望,完全沒有作用,功能無效。不知是不是因為我的 W79L632A 使用 40MHz 的震盪器。也不知 winbond LD 的 autobaund 功能怎麼寫的?只好放棄這個功能囉。- BANK: BANK=0 選擇 Flash 0,BANK=1 選擇 Flash 1,由於 LD 只有一個 bank,所以當 WFWIN=0 時,BANK 不可寫入 1。
- WFWIN: WFWIN=0 選擇 LDFlash,WFWIN=1 選擇 APFlash。
- NOE: NOE=0 唯讀模式,NOE=1 寫入模式。
- NCE: NCE=0 flash 燒錄模式,NCE=1 flash 工作模式。
- CTRL[3:0]: 燒錄模式選擇。
| ISP MODE | BANK | WFWIN | NOE | NCE | CTRL | SFRCN | time |
| 抹除 LDFlash | 0 | 1 | 1 | 0 | 0010 | 0x62 | 15ms |
| 抹除 APFlash0 | 0 | 0 | 1 | 0 | 0010 | 0x22 | 15ms |
| 抹除 APFlash1 | 1 | 0 | 1 | 0 | 0010 | 0xA2 | 15ms |
| 寫入 LDFlash | 0 | 1 | 1 | 0 | 0001 | 0x61 | 50us |
| 寫入 APFlash0 | 0 | 0 | 1 | 0 | 0001 | 0x21 | 50us |
| 寫入 APFlash1 | 1 | 0 | 1 | 0 | 0001 | 0xA1 | 50us |
| 讀取 LDFlash | 0 | 1 | 0 | 0 | 0000 | 0x40 | 1.5us |
| 讀取 APFlash0 | 0 | 0 | 0 | 0 | 0000 | 0x00 | 1.5us |
| 讀取 APFlash1 | 1 | 0 | 0 | 0 | 0000 | 0x80 | 1.5us |
- 抹除功能一次可抹除整個 flash,抹除完成後全部的值都變成 0xff。
- 讀寫功能一次只能讀寫一個 byte。
- 操作時間要視 XTAL 的速度來計算,上述的值是以 XTAL=24MHz 來計算。
- SFRAH/SFRAL 儲放欲讀寫的位址,SFRFD 則儲放讀寫的值。
- SWRST: software reset,令 W79E632A 回到初始狀態,並自動清除這個 bit。
- HWB: hardware boot,HWB=1 表示 ISP hardware reboot mode。
- LDAP: reand only,LDAP=1 表示在 LDFlash 運行,LDAP=0 表示在 APFlash 運行。
- LDSEL: LDSEL=1 表示從 LDFlash 讀取 code 執行。
- ENP: ENP=1 表示進入 the ISP mode。
從 APFlash 進入 LDFlash
從 LDFlash 進入 APFlash
在 LDFlash 執行 ISP 指令前,需先指定 CHPCON 參數,只需指定一次即可。
在 APFlash 執行 ISP 指令前,需先指定 CHPCON 參數,只需指定一次即可。
注意事項
- CHPCON 在寫入時需要 timed access protection。
- CHPCON 在進入 idle mode 才會執行上述指令,所以記得要讓 W79L632A 離開 idle mode,原廠 sample code 使用 XTAL=24MHz 在 idle mode 花費 8us。
- 其實可以不要理會 datasheet 上在寫什麼,記住切換 flash 要如上述的下指令即可,不要想要更動指令,會產生錯誤的結果,因為我已經試過都無效了。
watchdog time-out 後會呼叫 watchdog interrupt function,再經過 512 clock 後會做 reset 8051 動作。如果在這 512 clock 之間重置 watchdog 就不會 reset 8051。經過測試結果,在很久很久的時間過後,仍有機會引起 reset 動作,也就是 reset watchdog 沒有來得急做,即使把 watchdog interrupt priority 提高也是一樣。最好不要用喔。
| flag | Power-on reset | Watchdog reset | External reset |
| POR | 1 | x | x |
| WTRF | 0 | 1 | 0 |
| EWT | 0 | x | x |
| WDCON | 01000000b | 0x0x01x0b | 0x0x0xx0b |
由上表得知一些判斷開機的因素,整理如下:
-
判斷是因為電源開關所引起的開機。
只要判斷 POR 為 1 即可,開完機後需要立即把 POR 清為 0,以利後續判斷。 -
判斷是因為 watchdog 所引起的開機。
只要判斷 WTRF 為 1 即可,開完機後需要立即把 WTRF 清為 0,以利後續判斷。 -
判斷是因為外部中斷所引起的開機。
只要不是上述二個條件就是外部中斷引起的。
啟動 watchdog 程序
- 先關閉 watchdog 功能,防止一些干擾的問題。
- 選擇 watchdog time-out 時間。
- 設定 watchdog 中斷開關。
- 開啟 watchdog 重置工作。
- 開啟 watchdog 功能。
關閉 watchdog 程序
重置 watchdog 程序
- W79L632A 有幾個 register 需要接受寫入保護,免得招到中斷破壞,導致問題出現。
- 這些需要寫入保護的 register,一定要使用寫入保護方式才可以做寫入動作,其餘無效。
- 做法為寫入 TA=0xAA; 再寫入 TA=0x55; 後,有三個 machine cycles 時間受到寫入保護,不被干擾。
受到寫入保護的 register
- WDCON
- POR
- EWT
- WDIF
- RWT
- CHPCON
watchdog 中斷觸發時間計算方式:(以 40MHz 為例)
1/40000000 * 2^17 = 131072/40000000 = 0.0032768(s) = 3.2768(ms)
| WD1 | WD0 | interval | clocks | @40MHz |
| 0 | 0 | 2^17 | 131072 | 3.2768 ms |
| 0 | 1 | 2^20 | 1048576 | 26.2144 ms |
| 1 | 0 | 2^23 | 8388608 | 209.7152 ms |
| 1 | 1 | 2^26 | 67108864 | 1677.7216 ms |
- 進入 idle mode 後,CPU clock 會停止,進而逹到省電效果。
- 只要中斷產生,就可以回復程式執行,會從進入 idel mode 的命令之後開始執行。
- 在 main loop 不可有即時性工作,以免進入 idel mode 後無法執行而造成錯誤。
- 切記,當使用到 PWM 指令時,不可進入 idle mode,會失效的。
- 空間位址在 0x0000 ~ 0x03ff。
- 只能使用 MOVX 指令來讀寫,也就是設成 xdata 的變數。
-
PMR 中 DME0 (bit 0) 可以設定是否使用 on-chip。
DME0=0,0x0000~0xffff 都指向外部記憶體。
DME1=0,0x0000~0x03ff 指向內部記憶體。
- 出廠 default 值為 0xFF,存放位址在 LDFlash 的 0xFFFF(根據 W78E65 datasheet 猜測值)。
- 當某一個 bit 變成 0 後,就不可以再改成 1,要變成 1 的唯一方法就是使用 earse LDFlash 功能。
- Bit 0 - Lock bit (LOCK BIT)
設成 0 時 (Active),flash 及 security 都無法讀取。 - Bit 1 - MOVC Inhibit (MOVC BIT)
設成 0 時 (Enable),當 MOVC 指令在外部記憶体空間時,只能存取外部記憶体。當 MOVC 指令在內部記憶体空間時,則可以存取內部及外部記憶體。 - Bit 2,3 -- reserved 沒用到
- Bit 4 - H/W Reboot with P2.6 and 2.7 (Set P2 reboot)
設成 0 時 (Enable),在開機時(RST = HIGH),P2.6 = LOW and P2.7 = LOW 時,MCU 會進入 LDFlash。 - Bit 5 - H/W Reboot with P4.3 (Set P4 reboot)
設成 0 時 (Enable),在開機時(RST = HIGH),P4.3 = LOW 時,MCU 會進入 LDFlash。 - Bit 6 -- reserved 沒用到
- Bit 7 -- Select clock freqency (Osillator Control)
設成 0 時 (FULL),MCU 頻率小於 24 MHz。
設成 1 時 (1/2),MCU 頻率大於 24 MHz。
