compile assembly source in eclipseSDCC

Posted by: 邱小新 at 上午10:38 in

在 eclipseSDCC 內要編譯 c source 是完全沒問題的,但是要編譯 assembly source 卻產生一個小小的 bug,很多人都有在問解決方法,但原作者似乎沒有要修改的意願,還好有一個熱心的使用者提供一個解決方法,可以完美的解決。

在最原始的設定下,直接編譯 asm 會出現下列錯誤。

Building file: ../source/test.asm Invoking: SDCC Assembler as51 -o"source/test.rel" "../source/test.asm" C:\Temp\make34404.sh: command not found: as51 [1] make: *** [source/test.rel] Error 1

為什麼找不到 as51?那是因為 SDCC 的 assembly compiler 應該 asx8051 才對,換成 asx8051 後,再次編譯 asm 則出現下列錯誤。

Building file: ../source/test.asm Invoking: SDCC Assembler asx8051 -o"source/test.rel" "../source/test.asm" removing ASxxxx Assembler V01.70 + NoICE + SDCC mods + Flat24 Feb-1999 (Intel 8051) Usage: [-dqxjgalopsf][ -I<dir> ] file1 [file2 file3 ...] d decimal listing q octal listing x hex listing (default) j add line number and debug information to file g undefined symbols made global a all user symbols made global l create list output file1[LST] o create object output file1[REL] s create symbol output file1[SYM] c generate sdcdb debug information p disable listing pagination f flag relocatable references by ` in listing file ff flag relocatable references by mode in listing file -I<dir> Add the named directory to the include file search path. This option may be used more than once. Directories are searched in the order given. make: *** [source/test.rel] Error 1

為啥會錯呢?那是因為 -o 還沒有支援路徑功能,在那編譯,就在那產生 rel,不能更改路徑。最後使用 domning 所提供的 SDCCAsmWrapper 才得以正確的編譯成功。

Building file: ../source/test.asm Invoking: SDCC Assembler SDCCAsmWrapper -o"source/test.rel" "../source/test.asm" Wrapping SDCCAsmWrapper.EXE to > asx8051.exe -lo ../source/test.asm Finished building: ../source/test.asm

安裝 SDCCAsmWrapper

  1. 下載 SDCCAsmWrapper
  2. 解壓縮到 C:\Program Files\SDCC\bin。
  3. 修改 SDCC Assembler Command 為 SDCCAsmWrapper 即可。
  4. SDCCAsmWrapper 會去呼叫 asx8051 做編譯,再 copy rel 到 output 目錄。

SDCC 函數參數傳遞使用暫存器

Posted by: 邱小新 at 上午9:55 in

SDCC 函數回傳值

  1. SDCC 使用 DPL, DPH, B, ACC 來做回傳值的傳遞。
  2. 回傳 1 byte : 使用 DPL。
  3. 回傳 2 bytes: 使用 DPL(LSB) 及 DPH(MSB)。
  4. 回傳 3 bytes: 使用 DPL, DPH, B。
  5. 回傳 4 bytes: 使用 DPL, DPH, B, ACC。
  6. generic pointers 回傳 3 bytes,指針位置存在 DPL(LSB) 及 DPH(MSB),指針型態存在 B,0x00–xdata/far, 0x40–idata/near, 0x60–pdata, 0x80–code。

SDCC 函數參數值

  1. 第一參數: 使用 DPL, DPH, B, ACC 來傳遞,如同回傳值一般。
  2. 其餘參數: reentrant 函數使用 stack,一般函數使用 data/xdata,視記憶體模式而定。
  3. bit 參數都是使用 bit-addressable 位置的空間,不論 reentrant 或一般函數。

SDCC Bankswitching (code banking)

Posted by: 邱小新 at 下午5:23 in ,
範例下載, 使用 winbond w77e532, uart output "010hello test".

要做 SDCC code banking 真是有夠麻煩,找不到有人分享成功案例就算了,sdccman.pdf 竟然也只提到一頁而已,完全沒法子去了解如此實做。最後靠著不斷的試驗,總算完成這一件不可能的任務。

  1. 修改 C:\Program Files\SDCC\lib\src\mcs51\crtbank.asm 來達成 bankswitching 的函數。
  2. 重新建立 mcs51.lib,把修改好的 crtbank.asm 置換掉舊的 mcs51.lib。
  3. Project Properties → C/C++ Build → Settings → Tool Settings → SDCC Linker → Command 輸入 sdcc -Wl-r "-Wl-b BANK1=0x18000",千萬不要輸入 sdcc "-Wl-r -Wl-b BANK1=0x18000",為此我花了半天找問題,真是 ooxx。
  4. 建立一個 c source file,開頭輸入 #pragma codeseg BANK1。
  5. 建立一個函數 void test1(void) banked { ... },要記得加上 banked。
  6. 轉換 ihx 成 bin,makebin.exe < test.ihx > test.bin
  7. 產生出來的 bin,裏面有 common/bank0/bank1,但是在 bank1 裏沒有 common 存在,需要手動加進去,而且也要手動分開這個 bin,才方便 winbond isp writer 燒錄。
  8. 大功告成,真是幸苦啊,花了一天才完成。

to be continue.... 後面有空再來補一些說明及範例吧。

SDCC Bankswitching (code banking) part 2

rebuild mcs51.lib

Posted by: 邱小新 at 下午5:04 in

今天為了試驗 SDCC code banking,一直在試 crtbank.asm,怎麼弄都不行,最後想到直接置掉 mcs51.lib 看看。結果,當我下 sdcclib -m mcs51.lib,竟然回我一個錯誤訊息。

sdcclib -m mcs51.lib ERROR: File 'mcs51.lib' was not created with 'sdcclib'

最後,直接下 sdcclib -a mcs51.lib crtbank.rel,把檔案暴力加進去,結果....裏面檔案都不見了,只剩下 crtbank.rel

sdcclib -a mcs51.lib crtbank.rel sdcclib -m mcs51.lib crtbank.rel

當然這樣是不能用的啦,其它的 startup code 都不見了,根本無法編譯。所以又手動把 C:\Program Files\SDCC\lib\src\mcs51 下的 asm 全部編進 mcs51.lib。傻眼的是雖然可以加到 SDCC,但是搞出來的 hex 檔燒進去竟然動不了,真是怪了。

只好又去拜酷狗大神,歷經千幸萬苦,最後找到原始編譯 mcs51.lib 才解決了問題。原來我一直使用 asx8051 -ol 是不對的,應該下 asx8051 -plosgff 才對。而且 sdcclib 也不用加 -a 參數。

del mcs51.lib del *.rel asx8051 -plosgff crtbank.asm asx8051 -plosgff crtcall.asm asx8051 -plosgff crtclear.asm asx8051 -plosgff crtpagesfr.asm asx8051 -plosgff crtstart.asm asx8051 -plosgff crtxclear.asm asx8051 -plosgff crtxinit.asm asx8051 -plosgff crtxstack.asm sdcclib mcs51.lib crtbank.rel sdcclib mcs51.lib crtcall.rel sdcclib mcs51.lib crtclear.rel sdcclib mcs51.lib crtpagesfr.rel sdcclib mcs51.lib crtstart.rel sdcclib mcs51.lib crtxclear.rel sdcclib mcs51.lib crtxinit.rel sdcclib mcs51.lib crtxstack.rel copy /Y "C:\Program Files\SDCC\lib\src\mcs51\mcs51.lib" "C:\Program Files\SDCC\lib\small"

W77E352 雜記

Posted by: 邱小新 at 下午4:35 in

從今天開始又要開始玩 8051 了,在公司裏被丟來丟去,如浮萍一般飄泊不定,真是可憐啊。
BTW 有人要挖角嗎?歡迎來電喔。

  1. W77E352 內含二個 serial port,port 0 可以使用 timer 1/2 做 baudreate generator,而 port 1 只能使用 timer1。所以只要設定好 T2CON,port0 就會使用 timer2;如果只有設定 timer1 則 port 0/1 就會共用 timer1。
  2. W77E352 有 1K 的 On-chip SRAM,使用 xdata(MOVX) 來存取,位置在 0x0000~0x03FF。使用前需把 PMR 的 DME0 開啟,就是 PMR |= 1。
  3. to be continue....

保密協定文章已設限

Posted by: 邱小新 at 下午6:37
有關 Mstar 單晶片 TSUMU58EJ、MST7915LA 及 Genesis 晶片的文章都已經移到另一個 blog

tiny rtos for 8051 part5

Posted by: 邱小新 at 下午4:24 in , ,
原始碼下載

到這裏應該算是一個總結了,後面有什麼功能,再慢慢來想吧。這次主要是加入了 rtos delay 函數,讓 delay 更為精準,也讓空出來的時間,給其它 task 去執行,並且加入了 rtos_idle 函數,利用 cpu idle 功能達到省電功能,當然如果你有用到 PWM 時,記得把這個功能換掉,免得 PWM 不動了。

主要加入的功能

  1. rtos_idle 函數主要是在所有 task 都在 wait 時要執行的函數。由於 rtos_idle 沒有用到任何的堆疊空間,所以在計算每一個 task 的堆疊空間時,把 idle task 的空間直接定義成 17 bytes,就造成 size = (256 - SP - 17) / MAX_TASKS;,而非 size = (256 - SP) / (MAX_TASKS + 1);。

  2. rtos_nexttask 函數主要是用來做切換任務的工作,並根據 rtos_tick 決定是否要執行。

  3. rtos_isr 函數中加入了計時功能(以 10ms 為一個單位),並把 nexttask 功能分離出來,並加上 RETI 把後面的 POP 擋掉。

  4. rtos_scheduling 函數主要是用在非中斷環境下執行 nexttask 功能。

  5. rtos_delay_10ms 函數主要是用來做時間延時用的,每 10ms 為一單位。

程式執行結果為 0.5 秒顯示數字,1 秒顯示字母,也就是看到 1a23b45c67d89e12f34g56h78i9 如此循環顯示的字串。

tiny rtos for 8051 part4

Posted by: 邱小新 at 下午4:40 in , ,
原始碼下載

這次修改的內容,主要是加入 code banking 功能。

  1. 這次主要是參考 Keil C51 的 RTX-51 code banking 設定,利用 ?B_CURRENTBANK 及呼叫 ?B_RESTORE_BANK 來切換。

  2. L51_BANK.A51 除了主要的設定外,需要把 ?B_RTX 也設成 1,才能讓 ?B_RESTORE_BANK 函數可以使用。

  3. 在初始化過程中,也要先切換第一個 task 的 code bank。

tiny rtos for 8051 part3

Posted by: 邱小新 at 下午3:49 in , ,
原始碼下載

在 tiny rtos for 8051 part2 裏,本來想把已經測試好的程式碼加入 code banking 的相容性,但經過幾天幾夜的測試,不管怎麼做都會出錯,真是莫名奇妙。後來就倒回去看看是否之前程式碼是否也有問題,結果真的是如此。part1 的程式碼執行無誤,part2 的程式碼執行有時會出錯。經過反覆檢查,發現把 interrupt 的程式碼全改成 assembly 就會出錯,即使照著 Keil C51 編出來的 assembly code 寫進去也是一樣有問題,只能猜想是否在 link 過程中,又被動了一些手腳,造成程式莫名奇妙的出錯吧。

這次主要修改的內容

  1. 原本 rtos_isr 函數維持加入部份 assembly code,不要全部改成 assembly code。

  2. ?C_IBP 儲存方式改成放入堆疊裏,以利精簡程式碼,減少 interrupt 執行時間。

  3. 原本的 void (* const task_func[MAX_TASKS])(void) 改成 unsigned int code task_func[MAX_TASKS],將函數指標改成一般變數放在 rom 裏,如此可以節省記憶體用量,也精簡程式碼。

  4. 為了避免 LCALL rtos_start 變成 LJMP rtos_start 問題,直接改變 SP 的初始化過程,藉由修改 STARTUP.A51 來增加 STACK 位址的讀取。

  5. 不可以將 STARTUP.A51 中的 IBPSTACK 設成 1,原來使用 reentrant 函數都需要將 IBPSTACK 設成 1,但是不知什麼原因,在用 IspWriter 燒錄完後的自動重啟執行都會出現怪怪的執行結果。

tiny rtos for 8051 part2

Posted by: 邱小新 at 下午2:08 in , ,

經過反覆測試,發現將 interrupt 全改寫成 assembly 會造成不明原因輸出錯誤,這個原始碼不要用喔,後面有解決方法。

原始碼下載

在這次的實驗裏要解決 reentrant 函數的問題,其實也很簡單,就是把 reentrant 的 stack 變數依照每個 task 儲存起來,執行該 task 時,再回復其值就好了。程式碼如下,都是用 c 語法做說明,實際程式碼很多都改成 assemble 語法,主要是因為 Keil C51 的 reentrant stack 是存在 ?C_IBP,而 C 語法不允許變數有 ? 符號,所以就改成用 asseble 來寫。

void rtos_isr(void) interrupt 1 { // save current stack rtos_stack[rtos_task_id] = SP; rtos_stack_IBP[rtos_task_id] = ?C_IBP; // load next stack if (++rtos_task_id >= MAX_TASKS) rtos_task_id = 0; SP = rtos_stack[rtos_task_id]; ?C_IBP = rtos_stack_IBP[rtos_task_id]; } void rtos_start(void) { xdata unsigned char i, size; xdata unsigned char idata *sp; // variable init rtos_task_id = 0; // stack init size = (256 - SP + 2) / MAX_TASKS; sp = (unsigned char idata *)SP; for (i=0; i<MAX_TASKS; i++) { *sp-- = ((unsigned int)(task_func[i])) / 256; *sp-- = ((unsigned int)(task_func[i])) % 256; rtos_stack[i] = sp + 15; sp += size; rtos_stack_IBP[i] = sp; sp += 2; } ?C_IBP = rtos_stack_IBP[0]; // timer0 init TMOD = (TMOD & 0xF0) | 0x01; TH0 = 0; TL0 = 0; TF0 = LOW; ET0 = HIGH; TR0 = HIGH; }

接下來二個 test tasks 就全改成 reentrant,如此就不會有 data overlaying 的問題,而且 Code Optimization Level 也可以調到 7:Extended Index Access Optimizing。執行結果就會看到數字跟英文字母相互出現。

void task1(void) reentrant { unsigned char i=0x30; while(1) { if (++i>=0x3a) i=0x31; putchar(i); delay_1ms(100); } } void task2(void) reentrant { unsigned char j=0x60; while(1) { if (++j>=0x6a) j=0x61; putchar(j); delay_1ms(100); } }

寫到這裏也許有人會覺得奇怪,為什麼 Code Optimization Level 沒有調到預設值 8:Reuse of Common Entry Code?主要是因為在 rtos_start 裏,我利用 LCALL 的特性把 stack 裏的返回位址改成 task1 的位址,而 level 8 會把 main 函數裏的 LCALL rtos_start 改成 LJMP rtos_start,造成程式無法執行。當然只要一個小修改就可以修正掉這個 bug 了。

tiny rtos for 8051 part1

Posted by: 邱小新 at 下午3:50 in , ,
原始碼下載

最近有空去研究了一下陳明計的 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。很簡單吧,就如下面程式碼所列,並不會很難。

#define MAX_TASKS 2 unsigned char rtos_task_id; unsigned char rtos_stack[MAX_TASKS]; void (* const task_func[MAX_TASKS])(void)={ task1, task2 }; void rtos_isr(void) interrupt 1 { // save current stack rtos_stack[rtos_task_id] = SP; // load next stack if (++rtos_task_id >= MAX_TASKS) rtos_task_id = 0; SP = rtos_stack[rtos_task_id]; } void rtos_start(void) { xdata unsigned char i, size; xdata unsigned char idata *sp; rtos_task_id = 0; size = (256 - SP + 2) / MAX_TASKS; sp = (unsigned char idata *)SP; for (i=0; i<MAX_TASKS; i++) { *sp-- = ((unsigned int)(task_func[i])) / 256; *sp-- = ((unsigned int)(task_func[i])) % 256; rtos_stack[i] = sp + 15; sp += size + 2; } TMOD = (TMOD & 0xF0) | 0x01; TH0 = 0; TL0 = 0; TF0 = LOW; ET0 = HIGH; TR0 = HIGH; }

寫了二個測試函數,主要就是從 rs232 印出 1~9 及 a~i,如下所示。

void task1(void) { xdata unsigned char i=0x30; while(1) { if (++i>=0x3a) i=0x31; putchar(i); delay_1ms(50); } } void task2(void) { xdata unsigned char j=0x60; while(1) { if (++j>=0x6a) j=0x61; putchar(j); delay_1ms(50); } }

但是結果卻不如預期,跑出來的東西都是亂碼,傻眼。最後,經過了二天的努力,總算把結果正確無誤的弄出來了。主要有下列幾個地方要修改。

  1. 把 C51 的 Code Optimization Level 降成 1:Dead code elimination。因為 Data overlaying 的關係,造成 task1 的 i 變數跟 task2 的 j 變數共用同樣的位址,所以讓印出來的值都在 1~9 跳動。

  2. 原本 data overlaying 問題想要利用 reentrant 指令來建立可重入函數去解決,但最後發現仍然是無效。因為 keil 把變數都放在一個 ?C_IBP 的位址,仍然會造成共用的問題,這個問題真是難解啊,以後如果要寫複雜的 multi-task 函數有一定的難度,必須克服可重入函數及 data overlaying 造成的問題。

  3. 在 rtos_isr 函數裏加入 push 及 pop 指令,把 R0~R7,ACC,B,PSW,DPH,DPL 都丟到 stack 裏去。因為 keil 都會利用這些暫存器做一些運算,在進入 interrupt 時,也會主動 push 一些在 interrupt 會用到的暫存器到 stack 裏,但其它沒用到的暫存器就沒有主動 push,造成切換到其它 task 再切回原來 task 時,那些暫存器裏的值都被變更了,而造成運算錯誤。

  4. 另外在加入 assembly code 時,需把 Generate Assembler SRC File 及 Assemble SRC File 都打勾才可以,缺一不可。

Keil C51 Data Overlaying

Posted by: 邱小新 at 下午2:41 in

一般的編譯器將函數中的區域變數動態配置在 stack,等函數結束空間就釋放出來。因為 8051 的內部記憶體很少,只有區區 128 或 256 bytes,而且 stack 也是共用這塊記憶體。為了節省 stack 空間,所以區域變數基本上是靜態配置在固定位址, 也就是變成全域變數。如此就又造成浪費記憶體的情況,為了解決這個問題,所以 8051 的編譯器基本上都採用所謂的 data overlaying 技術來克服區域變數浪費空間的問題。

所謂 data overlaying 是指沒有呼叫關係的函數,它們的區域變數區可以重疊在一起(共用一塊記憶體)。Keil C51 會分析程式中函數間呼叫的關係,產生一個呼叫樹。它就根據這個呼叫樹來決定那些函數的區域變數區可以 overlaying 在一起。一種情況是是編譯器發現某一個函數(不是 main)沒有被別的函數呼叫,這會造成編譯器的困惑。一個正常的程式,除了 main 之外,除非是垃圾程式碼(沒用處但沒有刪除),否則所有的函數應該是至少會被一個其它函數呼叫的。編譯器在安全至上的原則下,會認定它的分析無法正確的辨識這個函數呼叫關係,所以對這個函數的區域變數就會獨立配置,不會重疊配置。這樣有沒有問題?邏輯上當然不會有問題,但沒 overlaying 就是會浪費記憶體,而且也會一直產生 *** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS 的警告。

什麼情況下,函數的呼叫樹讓編譯器無法分析?真實應用是有一些情況會發生這樣的問題。最常見的就是使用函數指標來呼叫函數。因為呼叫是執行時期動態變動的,這就可以難倒編譯器了。在這種情況這些被呼叫的指標函數就會獨立配置它們的區域奱數。如果你要這些函數也能正確的使用 overlaying 的好處,那麼你就必需手動分析那些函數的呼叫樹,然後告訴編譯器就可以了。這樣你也就不會在編譯時產 UNCALLED SEGMENT 的警告了。

Keil C51 延時函數 delay_1ms

Posted by: 邱小新 at 下午3:01 in
利用 printf 計時,好像不太準耶,不知為啥。

12.0 MHz 延時 1ms 函數

void delay_1ms(unsigned int ms) { unsigned char i; while(ms--) { for(i = 0; i < 123; i++); } }

22.1184 MHz 延時 1ms 函數

void delay_1ms(unsigned int ms) { unsigned char i; while(ms--) { _nop_(); _nop_(); for(i=0; i<228; i++); } }

原文出處:http://www.dztang.com/article/dpj/jcycx/117.html
经过电子堂网站的实测,精确到us啊,足够了吧。

Keil C51 指標變數 (pointers to specific memory spaces)

Posted by: 邱小新 at 上午11:16 in
由於 mcs51 的記憶體分成五大部份(註一),所以搞的 SDCC 的指標也要分成好幾種。以下先簡介指標定義準則:

type
變數型態
located
指向位址
pointerphysical
變數位址
name
unsigned charxdata*datap

指標分類

  1. unsigned xdata char * data p;
    指向外部記憶體的指標變數,變數存放在內部記憶體。
  2. unsigned char data * xdata p;
    指向內部記憶體的指標變數,變數存放在外部記憶體。
  3. unsigned char code * xdata p;
    指向程式記憶體的指標變數,變數存放在外部記憶體。
  4. unsigned char code * code p;
    指向程式記憶體的指標變數,變數存放在程式記憶體,指標變數是唯讀,所以必須先設定初始值
  5. unsigned char * xdata p;
    指向任何記憶體的指標變數 (generic pointer),變數存放在外部記憶體。
  6. unsigned char * p;
    指向任何記憶體的指標變數 (generic pointer),變數存放由 memory model 決定。
  7. char (* data fp)(void);
    指向程式記憶體的函數指標變數,變數存放在內部記憶體。函數指標變數只能指向程式記憶體,無法修改喔
  8. 由於內部記憶體指標讀取都是使用間接定址 (indirect addressing),所以無法存取 SFR 空間的記憶體內容。

指標變數大小

data1
xdata2
code2
generic pointer3

  1. generic pointer 是設計用來可以做通用指標變數使用,可以任意變換指標指向位址,但是相對的需要佔用 3 個 bytes 的位置,而且在做讀取寫入時,需要另外執行一個函數來轉換位址,沒事就少用一點吧。

W79E632 code banking using Keil C51

Posted by: 邱小新 at 下午3:40 in ,
  1. copy C:\Keil\C51\LIB\L51_BANK.A51 到專案目錄下。

  2. 修改 L51_BANK.A51 中的設定。
    ?B_NBANKS EQU 4 改成 ?B_NBANKS EQU 2。

  3. 在 mcu_init 中加入 ROMCON = 0x0A;,藉此設定 P1.2 as A16 並開啟 code banking 功能。

  4. 將 L51_BANK.A51 加入 project source file 裏。

  5. 進入 Project --> Manage --> Components, Environment and Books。

  6. 在 Groups 裏新增 bank0 及 bank1,完成後會在 Project Workspace 會出現 bank0 及 bank1 二個目錄。

  7. 在 bank0 按右鍵,選擇 Options for Group 'bank0',在 Code Bank 處選擇 "Bank #0"。

  8. 在 bank1 按右鍵,選擇 Options for Group 'bank1',在 Code Bank 處選擇 "Bank #1"。

  9. 在 bank0 及 bank1 各新增一個 souce file。

  10. 完成編譯後,原本會產生一個 xxx.HEX,現在則產生 xxx.H00 及 xxx.H01 二個檔案。

範例程式碼下載

Keil C51 printf 使用要點

Posted by: 邱小新 at 下午2:43 in
  1. 需要先初始化 uart 功能,TI 需設成 1,printf 輸出才有資料。
  2. 不需要像 SDCC 需要另外寫 putchar 及 getchar 函數,已經內建寫好了。
  3. 使用時要 inlcude stdio.h 標頭檔案,才不會找不到 prototype。
  4. printf 要輸出 8-bits 變數時,需寫成 %bd,不可以寫成 %d,會造成輸出錯誤,原因不明。
  5. getchar 函數在讀取到一個字元後,會自動輸出原字元做回應。(好像理論上不需要如此做,是為了仿造 PC 上有回應字元嗎?)

參考範例,使用 W79E632A,@40.0MHz,19200 bps。

void uart_init(void) { RCAP2L = 0xbf; RCAP2H = 0xff; TH2 = RCAP2H; TL2 = RCAP2L; T2CON = 0x34; SCON = 0x52; RI = TI = 1; } void main(void) { EA = 0; uart_init(); EA = 1; while (1) { printf("enter one charcter:"); getchar(); printf("\n\r"); } }

Keil C51 Code Banking

Posted by: 邱小新 at 上午9:50 in

當有三個函數為 A2() 位於 common 區,A0() 位於 bank0,A1() 位於 bank1,而且三個函數同時寫在 AA.c 裏,只是利用 CODE(?PR?A2?AA) bank0(?PR?A0?RAM) bank1(?PR?A1?RAM) 來分開,並且在 A2() 函數裏會呼叫 A1() 及 A0() 函數時,必須把 AA.c 放在 common 區裏,不可以放在 bank0 or bank1 這樣會造成 compiler 誤判,沒有呼叫 bank_switch 來切換 A1()/A2(),而造成不可預期的錯誤。

雜記

  1. 在 bank0 的函數可以互相呼叫 bank1 函數,沒有限制喔。
  2. 將 keil C51 內建 printf 移到 bank 裏時,最好避免一起把 code constants 一起移到 bank 裏去,不然就會發生最下面紅字警告的問題。

Common Area

common code area 主要是給所有的 bank 使用的函數或是固定資料。下面是一些會放在 common area 的函數及資料。
  1. Reset and Interrupt Vectors 重置及中斷指標
    主要是給硬體中斷時直接存取使用,所以必須放在 common area。

  2. Interrupt Functions 中斷函數
    主要是給硬體中斷時直接存取使用,所以必須放在 common area。

  3. Bank Switch Code 切換區塊函數
    這樣一定要放 common area,不然怎麼做切換啊。

  4. Library Functions 程式庫函數
    因為無法預知那個區塊會用到,所以都放在 common area。

  5. Code Constants 程式區資料變數
    這個不知為啥都放在 common area,可以經由參數設定放置在 bank 裏。

Code Constants 移至 bank 裏

code constants 主要就是經由宣告成 code 的變數,如字串,表格等。另外包含在 printf 裏面的字串也會放在 code constants,所以常常發現沒有宣告 code 變數,卻出現 ?CO?MYCODE 在 prog_name.m51 裏。
  1. 假設專案名稱為 prog,原始碼檔案為 test.c,晶片為 winbond W79E632。
  2. 在 prog.m51 裏的 C O D E M E M O R Y 區塊裏放置 common area 的資料描述。
  3. 在 test.c 裏的 code constants 會被統一放置在 ?CO?TEST 裏。
  4. Project --> Options for Target 'W79E632' --> BL51 Misc。
  5. 在 Misc controls 裏填入 BANK0(?CO?TEST (0x8000)) 即可,0x8000 是指定放在 bank0 的位址,也可以不填,由 keil 自動調配。
  6. 如果有多個 code constants,則使用逗號來分別,如 BANK0(?CO?TSUMU58EJ_CONF , ?CO?MODE_CONF, ?CO?MSTAR)。

要非常注意 code constants 移到 bank0 裏時,只能給 bank0 的函數使用,如果 bank1 函數要使用同樣的資料,會造成找到錯誤的資料而發生不可預期的問題。還有也要檢查 bank0 函數使用時,有無呼叫到 bank1 的函數,該函數是否把 code constants 當成參數傳到 bank1 函數去使用,如此也會產生大問題。

SDCC 及 Keil C51 記憶體變數宣告方式

Posted by: 邱小新 at 下午4:51 in ,

SDCC

記憶體區塊指向位址變數型別變數名稱初始值
__xdata[__at 0xC000]unsigned chartest_data[=0x01]

指向位址及初始值可以不宣告。指向位址沒有宣告的話,則由編譯器自動分配位置給變數,但由於 sfr 的特殊性,所以一定要包含 __at。

  1. data / near // c code __data unsigned char test_data=0x01; // asm code mov _test_data,#0x01

  2. xdata / far // c code __xdata unsigned char test_xdata=0x01; // asm code mov dptr,#_test_xdata mov a,#0x01 movx @dptr,a

未完待續......

Keil C51

新的定義方式。

變數型別記憶體區塊變數名稱初始值
unsigned charxdatatest_data[=0x01]

舊的定義方式,不建議使用。

記憶體區塊變數型別變數名稱初始值
xdataunsigned chartest_data[=0x01]

指向絕對位址定義方式,不可直接給初始值。

變數型別記憶體區塊變數名稱指向位址
unsigned charxdatatest_data_at_ 0xC000

但是如果一定要在指向絕對位址時又同時給初始值呢?? 請看http://blog.sina.com.cn/s/blog_4c5cec3b0100dmtd.html

未完待續......

SDCC 函數指標 (4)

Posted by: 邱小新 at 上午10:56 in

函數指標陣列

  1. 陣列宣告

    typedef int (*pt2Function)(int, int) __reentrant; pt2Function funcArr1[10]; int (*funcArr2[10])(int, int) __reentrant;

    funcArr1 跟 funcArr2 宣告結果是一樣的,利用 typedef 比較容易理解。

  2. 範例程式

    int Plus(int a, int b) __reentrant { return a+b; } int Minus(int a, int b) __reentrant { return a-b; } void main() { int (*funcArr[10])(int, int) __reentrant; printf("\nExecuting 'Array_Of_Function_Pointers'\n"); funcArr[0] = &Plus; funcArr[1] = &Minus; printf("12 + 5 = %d\n", funcArr[0](12, 5)); printf("12 - 5 = %d\n", (*funcArr[1])(12, 5)); }

SDCC 函數指標 (3)

Posted by: 邱小新 at 上午10:03 in

函數指標當成回傳值

  1. 函數宣告

    typedef int(*pt2Func)(int, int) __reentrant; pt2Func GetPtr1(const char opCode); int (*GetPtr2(int, int) __reentrant)(const char opCode);

    GetPtr1 跟 GetPtr2 宣告結果是一樣的,利用 typedef 比較容易理解。其中的 __reentrant 也可以不用宣告,因為只是函數指標傳遞並不影響函數中的區域變數的使用。

  2. 程式範例

    int Plus(int a, int b) __reentrant { return a+b; } int Minus(int a, int b) __reentrant { return a-b; } int (*GetPtr1(int, int))(const char opCode) { if(opCode == '+') return &Plus; else return &Minus; } void main() { int (*pt2Function)(int, int) __reentrant = NULL; printf("Executing 'Return_A_Function_Pointer'\n"); pt2Function=GetPtr1('+'); printf("2+4=%d\n", (*pt2Function)(2, 4)); pt2Function=GetPtr1('-'); printf("2-4=%d\n", (*pt2Function)(2, 4)); }

SDCC 函數指標 (2)

Posted by: 邱小新 at 下午4:02 in

函數指標當成參數傳遞

  1. 參數宣告

    void PassPtr(int (*pt2Func)(int)); void PassPtr(int (*pt2Func)(int, char, char) __reentrant);

  2. 程式範例

    int DoIt(float a, char b, char c) __reentrant { printf("DoIt\n"); return a+b+c; } int DoMore(float a, char b, char c) __reentrant { printf("DoMore\n"); return a-b+c; } void PassPtr(int (*pt2Func)(float, char, char) __reentrant) { int result = (*pt2Func)(12, 'a', 'b'); printf("%d\n", result); } void main() { printf("Executing 'Pass_A_Function_Pointer'\n"); PassPtr(&DoIt); PassPtr(&DoMore); }

SDCC 函數指標 (1)

Posted by: 邱小新 at 下午3:26 in
  1. 函數指標宣告

    int (*pt2Function1)(int) = NULL; int (*pt2Function2)(int, char) __reentrant = NULL;

    當函數指標裏的參數超過一個時,就必須宣告成可再入函數型態(__reentrant),否則會出現 Functions called via pointers must be 'reentrant' to take this many arguments 的錯誤。

  2. 函數指標 typedef

    typedef int (*pt2func)(int, char) __reentrant; pt2func pt2Function1 = NULL; int (*pt2Function2)(int, char) __reentrant = NULL;

    pt2Function1 跟 pt2Function2 二者宣告的結果是一樣,而 pt2Function1 看起來更容易理解,也更像一般的變數。

  3. 函數指標設定給值

    int DoIt (int a) { printf("DoIt\n"); return a; } int DoMore(int a, char b) __reentrant { printf("DoMore\n"); return a-b; } pt2Function1 = DoIt; // 簡單表示法 pt2Function2 = &DoMore; // 正規表示法

    當指標函數宣告成 __reentrant 型態,其相對應的函數也要宣告成 __reentrant,否則會造成二者參數傳遞出問題,這個 bug 編輯器可是不會顯示警告的。

  4. 函數指標比較

    if(pt2Function1 > 0) { // check if initialized if(pt2Function1 == &DoIt) printf("Pointer points to DoIt\n"); } else printf("Pointer not initialized!!\n");

  5. 函數指標執行

    int result1 = pt2Function1 (12); // 簡單表示法 int result2 = (*pt2Function2)(12, 'a'); // 正規表示法

一些網站資料

Posted by: 邱小新 at 凌晨12:40 in
  1. 大鳥實驗室
  2. 雷兒電子電機工程協會
  3. 南台科大機電科-單晶片原理與應用
  4. 崑山科大電子系-單晶片8051教學網頁
  5. 工程師的家 (KEIL C Compiler)

SDCC 資料型態 (data type)

Posted by: 邱小新 at 下午3:15 in
typewidthdefaultsigned rangeunsigned range
bool1 bitunsignedx0 ~ 1
char1 bytesigned-128 ~ 1270 ~ 255
short2 bytessigned-32768 ~ 327670 ~ 65535
int2 bytessigned-32768 ~ 327670 ~ 65535
long4 bytessigned-2147483648 ~ 21474836470 ~ 4294967295
float4 bytessigned?1.175494351E-38 ~ 3.402823466E+38
pointer1~4 bytesgenericxx

PWM (Pulse Width Modulation) 原理與應用

Posted by: 邱小新 at 下午3:09 in

PWM (Pulse Width Modulation) 稱為脈波寬度調變,常用於直流馬達的控制、電源變換器之穩壓控制、甚至是直流轉換交流弦波的控制等,是控制直流馬達轉速最常見的方法。

duty cycle

  1. duty cycle D = DT / T
  2. 也就是指 Ymax 的部份在一個 period(T) 佔有多少比率。
  3. 台灣稱 duty cycle 為「責任週期」。
  4. 大陆称 duty cycle 为「占空比」。

duty cycle 不同說法

  1. duty cycle high time = TH / T。
  2. duty cycle low time = TL / T。
  3. duty cycle high/low = TH / TL。
  4. TH = 維持在 HIGH 的時間。
  5. TL = 維持在 LOW 的時間。
  6. T = TH+TL。

紅外線感應器

Posted by: 邱小新 at 下午4:11 in
MOTION SENSOR (PASSIVE INFRARED TYPE) MP MOTION SENSOR‘NaPiOn’

紅外線感應器的種類非常之多,通常可以區分為量子型與熱型兩大類。而量子型感應器主要動作原理是利用光電效應,例如光二極體,以及利用光電效應的 CdS、PbS 等元件,此類型最為一般所常見。

此外尚有利用焦電效應的焦電型紅外線感應器,其動作原理是熱型的代表作,而所謂焦電效應(pyroelectric effect)簡單地說就是利用溫度的變化而產生電荷之現象。因此,我們可以說若是沒有溫度變化,即無法生電荷輸出訊號。

焦電型紅外線感應器俗稱 PIR 是 Passive Infrared Sensor 的英文簡寫,它主要的感應方法為利用溫度之變化,來感應待測物體之移動。舉例來說,人在 PIR 的感應範圍之內移動,PIR 上的感應點會因為溫度的變化而轉化成電流訊號輸出去,這個訊號就可以使電燈開啟、警報器作響……,達成我們所要的需求。

但是上述情形的感應方式會有一些問題,那就是 PIR 的感應距離太近(約 1~2 公尺),而且感應點會不夠多,所以最簡單的解決方法就是為 PIR 戴眼鏡,我們可以利用凸透鏡可以將光線聚焦的原理,將較遠處的人體的紅外線能量聚焦至 PIR 元件的感應點上,如此有可大大增加了感應距離,另一方面我們再把凸透鏡集合在一起,形成一個透鏡陣列 (Lens Arrays),這樣就可以增加許多感應點。至於透鏡的材質,必須要讓人體所發出的紅外光能夠順利通過,為了避免誤動,我們選擇對可見光阻絕性好的高密度 PE 可以避掉許多光線之干擾,而減少誤動之情形。




在自然界,任何高於絕對溫度(-273℃)時物體都將產生紅外光譜,不同溫度的物體,其釋放的紅外能量的波長是不一樣的,因此紅外波長與溫度的高低是相關的。

人體輻射的紅外線中心波長為 9~10μm,而探測元件的波長靈敏度在 0.2~20μm 範圍內幾乎穩定不變。在熱釋感測器頂端開設了一個裝有濾光鏡片的視窗,這個濾光片可通過光的波長範圍為 7~10μm,正好適合於人體紅外輻射的探測,而對其他波長的紅外線由濾光片予以吸收,這樣便形成了一種專門用作探測人體輻射的紅外線感測器。

在被動紅外探測器中有兩個關鍵性的元件,一個是熱釋電紅外感測器,它能將波長為 8-12μm 之間的紅外信號變化轉變為電信號,並能對自然界中的白光信號具有抑制作用,因此在被動紅外探測器的探測範圍內,當無人體移動時,熱釋電紅外感應器感應到的只是背景溫度,當人體進人探測範圍,通過菲涅爾透鏡,熱釋電紅外感應器感應到的是人體溫度與背景溫度的差異信號,因此,紅外探測器的紅外探測的基本概念就是感應移動物體與背景物體的溫度的差異。

另外一個元件就是菲涅爾透鏡,菲涅爾透鏡有兩種形式,即折射式和反射式。菲涅爾透鏡作用有兩個:一是聚焦作用,即將熱釋的紅外信號折射(反射)在熱釋電紅外感測器上,第二個作用是將探測範圍內分為若干個明區和暗區,使進入探測範圍的移動物體能以溫度變化的形式在熱釋電紅外感測器上產生變化熱釋紅外信號,這樣熱釋電紅外感測器就能產生變化的電信號。

人體都有恒定的體溫,一般在 37 度,所以會發出特定波長 10 微米左右的紅外線,被動式紅外探頭就是靠探測人體發射的 10 微米左右的紅外線而進行工作的。人體發射的 10 微米左右的紅外線通過菲泥爾濾光片增強後聚集到紅外感應源上。紅外感應源通常採用熱釋電元件,這種元件在接收到人體紅外輻射溫度發生變化時就會失去電荷平衡,向外釋放電荷,後續電路經檢測處理後就能產生信號。




紅外線 (Infrared) 在防盜系統用途上,可區分為主動式紅外線 twin-bean infrared sensor (感應器本身會發射紅外線光束偵測物體移動),與被動式紅外線 PIR (感應器本身不會發射紅外線光束,而是靠物體之熱源移動觸發感應器)。

一般而言,主動式紅外線大多數皆為2個(壹組)一起使用,一為發射紅外線光束 (Transimter),另一為接收紅外線光束 (Reciver),適用於室內或室外點對點之直線距離使用 (例如:對照式圍牆用紅外線投射器)。

被動式紅外線則適用於室內封閉空間使用,例如:一般室內防盜常用之紅外線感應器。之所以會有此區別用途,乃因室外溫差氣候變化大,例如:馬路溫度、汽車溫度、下雨天、刮風天.....等等外在因素,皆有可能會導致被動式紅外線誤報。而主動式紅外線因本身會持續發射紅外線光束,故受干擾誤報機會不大,相對的,其所能偵測的範圍僅限於點對點之直線距離,又不如被動式紅外線 PIR 有著整個空間的大涵蓋警戒範圍。




  1. PIR sensor 啟動時,需要有一段時間做 Calibration,依據不同的 sensor 有不同的時間,大約 10~60 秒,這個時間所輸出的值不具參考值。
  2. 沒有任何物體被偵測到移動時,輸出為 LOW。
  3. 當有物體被偵測到移動時,輸出為 HIGH,然後又回到 LOW。
  4. 如果有物體不斷的移動,輸出就會變成 HIGH LOW 不停變換,而不是維持在 HIGH 喔
  5. 使用 parallax PIR Sensor (#555-28027) 則可以設定成輸出一直維持在 HIGH。



  1. 純硬體設計線路不用軟體來解,使用 KC7788B IC。
  2. PIR 輸出 PWM (digital) -> 經過積分器輸出 DC (analog) -> 經過 ADC 輸出 0~255 -> MCU,這樣才可以控制 PIR 的靈敏度,不然只依據 HIGH/LOW 來判斷,只要一隻小鳥飛過也會觸發。
  3. 如果要用類比的電路更複雜,而且不穩定,會飄。更複雜的還有溫度的補償,PIR 很容易受溫度影響。

ir remote control NEC protocol

Posted by: 邱小新 at 下午4:32 in

一般指令時脈圖

  1. 圖一:0 與 1 的時脈圖。

  1. 圖二:傳送一個完整指令的時脈圖。
  1. 根據圖一,每次都會先送出 560us 的紅外線,再停止一段時間,再送出 560us 紅外線........直到資料送完。
  2. 根據圖一,送出一個 bit 1 需時 2.25ms 的時間。
  3. 根據圖一,送出一個 bit 0 需時 1.12ms 的時間。
  4. 根據圖二,會先送出一個 start sync (13.5ms),再依序送出 32bit 的資料,總共耗時 67.42ms。
  5. 一般來說 address 我們稱為 custom code,遙控器上的每一個按鍵送出的 address 都一樣,而只有 command 不一樣,以做區別。

重複指令時脈圖

  1. 圖三:傳送一個重複指令的時脈圖。
  1. 圖四:重複指令放大的時脈圖。
  1. 根據圖四,傳送一個重複指令需時 11.25ms。
  2. 根據圖三,傳送一個完整重複指令需時 110ms,所以後面空白時間為 110-11.25-0.56=98.19ms。
  3. 但是實際拿了三支不同的遙控器來試,結果發現三支的空白時間均不同,分別為 96.18ms、96.78ms、97.26ms。所以切記不要拿空白時間來當 repeat 訊號。

紅外線接收圖

  1. 圖五:紅外線接受器的訊號輸出圖。
  1. 根據圖五可以知道,當收到紅外線時,會產生一個 falling edge-triggered,剛好可以配合 8051 的 external interrupt。
  2. 只要利用二個 falling edge-triggered,就可以算出一個 pulse 所需的時間,進而知道是那一種訊號。
  3. 另外再利用 timer 8-bit auto-reload mode 的功能,可以精確計時。

範例程式

原始碼下載 void start_timer(void) { TR0 = LOW; timer_count = 0; TL0 = TIMER_COUNTER_60us; TR0 = HIGH; } void stop_timer(void) { TR0 = LOW; } int get_timer0(void) { int count; stop_timer(); count = timer_count; start_timer(); return count; } void timer0_isr(void) interrupt (1) { if(timer_count < 1500) { timer_count++; } else { stop_timer(); ir_control = 0; } } void init_timer0(void) { TR0 = LOW; TF0 = LOW; TMOD = (TMOD & TIMER1_MASK) | TIMER0_MODE_8BIT_AUTO; TH0 = TIMER_COUNTER_60us; PT0 = HIGH; ET0 = HIGH; } 發現了一個大 bug,i=ir_pos>>3; 要改成 i=(ir_pos&0x1f)>>3; 才可以,不然當接收到太多錯誤訊號時,會造成 ir_pos 溢位,而修改到其它變數。過了好久才發現這個問題,還是不小心發現的,還要感謝同事的遙控器溢波干擾,讓我看到這個大 bug。 void ext0_isr(void) interrupt (0) { static char ir_pos; int pulse; char i; if (ir_control == 0) { ir_control = IR_START; start_timer(); return; } pulse = get_timer0(); if (IS_IR_START(pulse)) { ir_control |= IR_READ; ir_pos = 0; return; } if (IS_IR_REPEAT(pulse)) { if (ir_control == IR_START) { printf_tiny("\n\rrepeat(%d)", pulse); } return; } i = (ir_pos & 0x1f) >> 3; if (IS_BIT_0(pulse)) { ir_code[i] >>= 1; ir_pos++; } else if (IS_BIT_1(pulse)) { ir_code[i] >>= 1; ir_code[i] |= 0x80; ir_pos++; } else { printf_tiny("\n\rerror(%d)", pulse); ir_control = 0; stop_timer(); return; } if(ir_pos >= 32) { ir_control ^= IR_READ; printf_tiny("\n\rcode = %x,%x,%x,%x", ir_code[0], ir_code[1], ir_code[2], ir_code[3]); ir_control = 0; stop_timer(); } } void ir_init(void) { init_timer0(); ir_control = 0; IT0 = HIGH; EX0 = 1; P3_2= HIGH; }

8051 外部中斷 (external interrupt)

Posted by: 邱小新 at 下午1:05 in
  1. 每一個 machine cycle 採樣一次訊號。
  2. 當中斷腳 (INT0) 被觸發後,若已在中斷致能 (EX0=1) 情況下,則中斷要求旗標 (IE0) 會設立為 1,再產生中斷。當 CPU 呼叫中斷服務程式,即自動清除 IE0 並執行中斷程式。嚴格的說,是由 IE0 旗標要求中斷,而非 INT0 腳直接要求,故 8051 亦可直接以指令設定 IE0 而產生中斷 0。
  3. 當我使用 falling edge-triggered 時,把 P3_2(INT0) 設成 0 時,發現中斷不再產生;重設成 1 後,中斷就會產生,不知啥原因。

triggered mode (觸發模式)

modeITxIEx說明
falling edge-triggered1clear只有當訊號從 high 變成 low 時會產生觸發。
low level-triggered0hold當訊號一直為 low 時會一直產生觸發,直到訊號變成 high。

edge-triggered (邊綠觸發)

  1. rising edge-triggered (正緣觸發)
    當訊號從 low 變成 high 時會產生觸發。
  2. falling edge-triggered (負緣觸發)
    當訊號從 high 變成 low 時會產生觸發。

SDCC 高低階型別轉換問題

Posted by: 邱小新 at 下午5:10 in
void main(void) { unsigned char a=143, b=121; unsigned long c; c = a * 256 + b; printf("\n\r143 * 256 + 121 = %ld", c); }

當執行上述程式碼時,理論上輸出值應為 "143 * 256 + 121 = 36729",但是輸出的值卻為 "143 * 256 + 121 = -28807"。為什麼會如此呢?主要是因為 SDCC 在做型別轉換時出了點問題。如下所示:

  1. (int) (unsigned char) 143 * (int) 256 + (int) (unsigned char) 121=
  2. (int) 143 * (int) 256 + (int)121 =
  3. (int) 36729 = (int) 0x8F79 = (long) 0xFFFF8F79 = (long) -28807
  4. 因為 0x8F79 第一個 bit 為 1,所以被當成負數來處理。

void main(void) { unsigned char a=143, b=121; unsigned long c; c = a * 256L + b; printf("\n\r143 * 256 + 121 = %ld", c); }

只要改成上列程式就沒有問題了,原因如下:

  1. 運算式中若有使用混合的資料型別,編譯器在計算之前會先將變數轉換為相同的資料型別,其規則是將佔記憶空間較小的變數轉換成佔記憶空間較大的資料型別。
  2. SDCC 在處理四則運算處理時,都會先轉換成 int 型態來處理,最後再轉換成儲存型態。
  3. 所以當四則運算裏有 long 常數或變數時,全部數值都會轉換成 long 來處理。

SDCC 有號無號型別轉換問題

Posted by: 邱小新 at 下午4:51 in
void main(void) { unsigned char a=-12; char b=-3; printf("\n\r-12 / -3 = %d", a/b); }

當執行上述程式碼時,理論上輸出值應為 "-12 / -3 = 4",但是輸出的值卻為 "-12 / -3 = -81"。為什麼會如此呢?主要是因為 SDCC 在做型別轉換時出了點問題。如下所示:

  1. (int) (unsigned char) -12 / (int) (signed char) -3 =
  2. (int) (unsigned char) 0xf4 / (int) (signed char) 0xfd =
  3. (int) 0x00f4 / (int) 0xfffd =
  4. (int) 244 / (int) -3 =
  5. (int) -81 = (int) 0xffaf;

只要改成下列程式就沒有問題了。

void main(void) { unsigned char a=-12; char b=-3; printf("\n\r-12 / -3 = %d", (char)a/b); }


void main(void) { unsigned short n=100; int l = -1; printf("sizeof(int) %d\n\r", sizeof(int)); if (n > l) printf("%u > %d\n\r", n, l); else printf("%u <= %d\n\r", n, l); }

當執行上述程式碼時,理論上輸出值應為 "100 > -1",但是輸出的值卻為 "100 <= -1"。但是在 linux 用 gcc 編譯執行的結果卻是 "100 > -1"。為什麼會如此呢?主要是因為 SDCC int 為 2bytes 而 gcc int 為 4bytes。

在 SDCC 中 unsigned short 與 int 都是 2bytes,根據一般算術型別轉換,unsigend 級別比較低時,signed 的資料型別 bit 數跟 unsigned 一樣多,會把 signed 轉成 unsigned,也就是 -1 變成 65535。如此 100 當然小於 -1 (65535) 囉。




一般算術型別轉換規則

  1. 整數型別的級別排序是 char < short < int < long < long long。
  2. 兩個運算元的型別相同時,不做轉換。
  3. 兩個運算元的型別不同時,兩邊都一樣是 signed 或 unsigned 的話,就轉換成高級別的型別。
  4. 兩個運算元的型別不同時,一個是 signed,一個 unsigned 的話。
    unsigned 這邊的級別比較高或相等時,會把 signed 轉成 unsigned。
    unsigend 級別比較低時,signed 的資料型別 bit 數比另一邊多時,會把 unsigned 轉成 signed,反之會把 signed 轉成 unsigned。

SDCC 數字常數表示方法及資料型態符號

Posted by: 邱小新 at 下午4:14 in

整數常數表示方式

  1. 二進位:由 0、1 所構成,開頭必須為 0b 或 0B。
    例如:0b00110011、0B11000011 等。
    SDCC 2.9.0 開始支援。
  2. 八進位:由 0、1、... 7 所構成,但第一個數字必須為 0
    例如:-012、0231、032767 等。
  3. 十進位:由 0、1、... 9 所構成,但第一個數字不可為 0
    例如:-12、231、32767 等。
  4. 十六進位:由 0、1、... 9、A、B、C、D、E、F (或 a、b、c、d、e、f) 所構成,開頭必須為 0x 或 0X。
    例如:-0x12、0X231、0xFFFF 等。

浮點常數表示方式

  1. 十進位:如 12.4、3.1415926 等,SDCC 不支援負數喔。
  2. 科學符號:如 2.34E+02、0.34e-12 等。

整數資料型態表示方式

  1. int:範圍 -32.768, +32.767,整數常數預設值,不加任何指示元。
    例如:32767、0231、0xFFFF 等。
  2. long:範圍 -2.147.483.648, +2.147.483.647,常數尾須加上指示元 l 或 L。
    例如:32767l、0231L、0xFFFFL 等。
  3. float:範圍 1.175494351E-38, 3.402823466E+38,常數尾須加上指示元 f 或 F。
    例如:32767f、12345F 等。
    SDCC 不支援。

SDCC interrupt 編碼注意事項

Posted by: 邱小新 at 下午2:14 in
  1. 變數沒有宣告成 volatile (variable not declared volatile)
    在中斷副程式裏改變一個全域變數,且這個全域變數也會被其它函數使用,則這個全域變數必須宣告成 volatile。
  2. 非原子型態變數的存取 (non-atomic access)
    非原子型態變數即 MCU 需要一個以上的指令去存取變數。當存取非原子型態變數時發生中斷,且在中斷函數中也會去存取此變數,則會發生不可預期的問題,而且很難被複制重現。所以當函數中存取到在中斷函數中用到的非原子型態變數時,需關閉所有中斷。例如:
    1. 16/32 bit 變數在 8bit MCU 是屬於非原子型態變數。
    2. 8bit 變數 flags 使用 "flags |= 0x80;" 這個指令,當 flags 位於 xdata 是需要一個以上指令來完成。
    3. 8bit 變數 counter 使用 "counter += 8;" 這個指令,不管 counter 位於那個記憶體,都需要一個以上指令來完成。
  3. 堆疊溢位 (stack overflow)
    返回位址 (return address) 及在中斷函數會用到的 register 都會被中斷函數先放進堆疊,離開時再從堆疊裏取出恢復。但是當堆疊空間不足時,又發生中斷,就會造成不可預期的問題。這種問題常常發生在遞迴函數的運行。
  4. 非可再進入函數的使用 (use of non-reentrant functions)
    在中斷函數中呼叫其它函數是不建議的,最好避免如此做。因為當執行此函數時,又發生中斷時,中斷函數又去呼叫此函數,造成重復進入的問題。所以如果要在中斷函數中呼叫函數,必須是呼叫可再進入的函數。
  5. int/long/float 的四則運算使用
    當你在中斷函數中使用 int/long 變數的乘法、除法、餘數運算,及 float 變數的四則運算時,需要注意非可再進入函數的使用。因為這些運算都是另外依靠函式庫來提供運算,並非 MCU 所提供的,而預設都是非可再進入函數。如果需要在中斷函數內使用,需要加上 --stack-auto 重新編譯,而且函數庫需要加上 --int-long-reent 及 --float-reent 再重新編譯一次。

SDCC printf 函數介紹

Posted by: 邱小新 at 上午10:41 in
  1. printf 會使用到 putchar 函數,而 SDCC 沒有實作 putchar,需要使用者自己定義。
  2. 預設的 printf 是使用 printf_large.c 的函數,不支援 float 輸出。
  3. 如果要讓 printf 支援 float 輸出,需加上 -DUSE_FLOATS=1 才可以,另外最好使用 --model-large,因為會使用到很多 memory。
  4. printf_fast 函數可以設定 #define 去除 long 及 field widths 的支援,並縮小 code size 並加快速度。
  5. printf_fast_f float 只支援到 +/- 4294967040,小數點後 8 位。
  6. %e 及 %g 二種 float 都不支援。
  7. %d 顯示有符號十進位整數(signed),%u 顯示無符號十進位整數(unsigned)。

mcs51printfprintf
USE_FLOATS=1
printf_smallprintf_fastprintf_fast_fprintf_tiny
filenameprintf_large.cprintf_large.cprintfl.cprintf_fast.cprintf_fast_f.cprintf_tiny.c
"HelloWorld" size small/large1.7k/2.4k4.3k/5.6k1.2k/1.8k1.3k/1.3k1.9k/1.9k0.44k / 0.44k
code size
small / large
1.4k / 2.0k2.8k / 3.7k0.45k / 0.47k (+_ltoa)1.2k / 1.2k1.6k / 1.6k0.26k / 0.26k
byte arguments on stackyesyesnononono
formatscdiopsuxcdfiopsuxcdosxcdsuxcdfsuxcdsux
long (32 bit) supportyesyesyesyesyesno
float format--%f----%f--
field widthyesyesnoyesyesno
string speed
small/large
1.52 / 2.59 ms1.53 / 2.62 ms0.92 / 0.93 ms0.45 / 0.45 ms0.46 / 0.46 ms0.45 / 0.45 ms
int speed
small / large
3.01 / 3.61 ms3.01 / 3.61 ms3.51 / 18.13 ms0.22 / 0.22 ms0.23 / 0.23 ms0.25 / 0.25 ms
long speed
small / large
5.37 / 6.31 ms5.37 / 6.31 ms8.71 / 40.65 ms0.40 / 0.40 ms0.40 / 0.40 ms--
float speed
small / large
--7.49 / 22.47 ms----1.04 / 1.04 ms--

  1. Execution time of printf("%s%c%s%c%c%c", "Hello", ’ ’, "World", ’!’, ’\r’, ’\n’); standard 8051 @ 22.1184 MHz, empty putchar()
  2. Execution time of printf("%d", -12345); standard 8051 @ 22.1184 MHz, empty putchar()
  3. Execution time of printf("%ld", -123456789); standard 8051 @ 22.1184 MHz, empty putchar()
  4. Execution time of printf("%.3f", -12345.678); standard 8051 @ 22.1184 MHz, empty putchar()

利用 printf 來算出 delay 1ms 的函數

Posted by: 邱小新 at 下午4:38 in , ,
一般來說,要取得 delay_1ms 的函數有幾種方法。
  1. 利用 timer 中斷來取得。
  2. 利用 while loop 來取得,但是要算出 while loop 的數值,通常使用示波器來輔助求得。

使用第一項方式會浪費掉一個 timer,使用第二個方法又很麻煩(因為不會用示波器)。所以我就想利用 printf 加上 timer 來算出 while loop 的數值。程式如下:

#define CNT 100 void delay_1ms(int msec) { int i; while(msec--) { i = CNT; while(i--); } } void main(void) { long time, cnt; uart_init(); while (1) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(1); TR0 = 0; time = (TH0*256L + TL0) * 12L / 40L; cnt = 1000l * (long)CNT / time; printf("\n\rtime0 = %d,%d,%ld,%ld\n\r", TH0, TL0, time, cnt); getchar(); } }

計算過程

  1. 首先 delay_1ms 中的 i 值先給 100,燒進去執行。
  2. 取得 TH0, TL0,利用 timer 公式 (TH0*256+TL0)*12/Fosc,計算出 100 次要花多少 t 時間。
  3. 再利用公式 t/i0 = 1ms/i1,i1=1ms*i0/t,取得下一次要填入的 i1 值。
  4. 再次重覆計算,直到 i 值不變或 t>1ms 為止。

範例:W79L632A@40MHz

  1. TH0=2, TL0=75, t=176.1 us, i1=567.8591 取 568。
  2. TH0=12, TL0=168, t=972 us, i1=584.3621 取 584。
  3. TH0=13, TL0=3, t=999.3 us, i1=584.4090 取 585。
  4. TH0=13, TL0=8, t=1000.8 us, i 值設定成 585
  5. 同樣的硬體,同樣的 source code,使用 keil C51,計算出來的值卻是 711,由此可見 keil C51 跑得比較快??
  6. 最近又從別人的 code 發現,其實在 i=CNT 前加入 nop 指令可以用來增加準確度到 1us 喔,當然 nop 數量的多寡要經由測試才能得知,有興趣的人自己試看看吧。

測試誤差

void delay_1ms(int msec) { int i; while(msec--) { i = 585; while(i--); } } void main(void) { long time; int cnt=20; uart_init(); while (cnt--) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(cnt); TR0 = 0; time = (TH0*256L + TL0) * 12L / 40L; printf("\n\rtime0 = %d,%d,%ld,%d", TH0, TL0, time, cnt); } }

Keil-C 怪怪的問題

最近玩 mstar 的晶片,改用 Keil C 來測這個程式,結果發現一個怪異現象,就是每次 printf 出來的 TH0 都是 16bit 的數值,而 TL0 卻總是 0,time,cnt 算出的值也不對,真是莫明奇怪。後來發現把 TH0, TL0 放入 int 變數就正常了,如果放入 char 變數也是不對。由此證明 keil 在 char 變數的四則運算有點問題喔。

#define CNT 100 void delay_1ms(int msec) { int i; while(msec--) { i = CNT; while(i--); } } void main(void) { long time, cnt; int a, b; uart_init(); while (1) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(1); TR0 = 0; a=TH0; b=TL0; time = (a*256L + b) * 12L / 40L; cnt = 1000l * (long)CNT / time; printf("\n\rtime0 = %d,%d,%ld,%ld\n\r", a, b, time, cnt); getchar(); } }

一個簡單的 uart debug 輸出程式

Posted by: 邱小新 at 下午3:52 in , ,

下面這支程式的 baudrate 是 38400 使用 40MHz 的震盪器。主要功能就是你輸入什麼,就顯示什麼。依此下列程式再加上 SDCC 裏的 printf 就可以用來做 debug 輸出功能了,短小精幹好用。

PS: 由於沒有使用到 ES 中斷,所以可以很放心的在中斷函數中使用 printf 來輸出訊息。

#include <8052.h> void putchar(char value) { while (!TI); TI = 0; SBUF = value; } char getchar(void) { while(!RI); RI= 0; return SBUF; } void uart_init(void) { RCAP2H = 0xff; RCAP2L = 0xe1; TH2 = RCAP2H; TL2 = RCAP2L; T2CON = 0x34; SCON = 0x52; } void main(void) { uart_init(); while (1) { putchar(getchar()); } }

PS: 因為我不會用 8051 的模擬器,所以都用 printf 在做 debug,有人說這樣不方便,其實用久習慣就好了。不管黑貓白貓,可以抓到老鼠就是好貓。

I2C protocol 原理及應用

Posted by: 邱小新 at 下午4:14 in
原始碼下載

I²C (Inter-Integrated Circuit) 是內部整合電路的稱呼,是一種串列通訊匯流排,使用多主從架構,由飛利浦公司在 1980 年代為了讓主機板、嵌入式系統或手機用以連接低速週邊裝置而發展。I²C 的正確讀法為 "I-squared-C" ,而 "I-two-C" 則是另一種錯誤但被廣泛使用的讀法,在大陸地區則多以 "I方C" 稱之。截至 2006 年 11 月 1 日為止,使用 I²C 協定不需要為其專利付費,但製造商仍然需要付費以獲得 I²C Slave (從屬裝置位址)。

I²C的參考設計使用一個7位元長度的位址空間但保留了16個位址,所以在一組匯流排最多可和112個節點通訊。常見的I²C匯流排依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時脈頻率可被允許下降至零,這代表可以暫停通訊。而新一代的I²C匯流排可以和更多的節點(支援10位元長度的位址空間)以更快的速率通訊:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

I2C 的啟動條件及停止條件

  1. I2C start condition 有二種情況,如上圖所示,虛線表示 read 動作時的第二次 start condition,實線表示 r/w 時的第一次 start condition。
  2. I2C stop condition 只有一種情況,如上圖所示。
void i2c_start(void) { // for second start signal on i2c_read I2C_SDA = HIGH; I2C_SCL = HIGH; i2c_wait(); // send start signal I2C_SDA = LOW; i2c_wait2(); I2C_SCL = LOW; } void i2c_stop(void) { i2c_wait2(); I2C_SDA = LOW; i2c_wait2(); I2C_SCL = HIGH; i2c_wait2(); I2C_SDA = HIGH; }

I2C 的讀寫動作

  1. 當 SCL=HIGH 時,表示 SDA 穩定,可以做讀取動作。
  2. 當 SCL=LOW 時,表示 SDA 混亂,不可以讀取;因為此時可以設定 SDA 的值,也就是做寫入動作。
  3. master 每一次傳送八個 bit,最後 slave 會回傳一個 ack bit,表示接受是否完成。
  4. master 每一次接受八個 bit,最後 master 要傳送一個 ack bit,表示接受已經完成。
  5. 在傳送完第八個 bit 之後,再等待 slave 接受完成後,需將 SDA 設成 HIGH,此時 slave 會將 SDA 拉回 LOW,表示接受動作完成。如果 acknowledge=HIGH,也就是 slave 沒有拉成 LOW 則表示傳送失敗。
bit i2c_write(unsigned char value) { char i=9; bit ack; // upload data while(--i) { i2c_wait2(); I2C_SDA = (value & 0x80) ? HIGH : LOW; i2c_wait2(); // send data I2C_SCL = HIGH; i2c_wait(); value <<= 1; I2C_SCL = LOW; } // get acknowledgement i2c_wait2(); I2C_SDA = HIGH; i2c_wait2(); I2C_SCL = HIGH; i2c_wait2(); ack = I2C_SDA; i2c_wait2(); I2C_SCL = LOW; // return acknowledge return ack; } unsigned char i2c_read(bit acknowledge) { char i=9; unsigned char value=0; // read data while(--i) { value <<= 1; i2c_wait(); I2C_SCL = HIGH; i2c_wait2(); value |= I2C_SDA; i2c_wait2(); I2C_SCL = LOW; } // send acknowledge i2c_wait2(); I2C_SDA = acknowledge; i2c_wait2(); I2C_SCL = HIGH; i2c_wait(); I2C_SCL = LOW; // return data return value; }

標準 I2C 讀寫流程 by Philips

  1. 讀取完最後一個 byte 時,記得要回傳 no acknowledge(SDA=HIGH),表示已經沒有要繼續讀取資料。
  2. 讀取完最後一個 byte 時,回傳 acknowledge(SDA=LOW),再傳送 stop signal,則會造成後續的讀寫動作失敗。(這是在讀取 PCF8593 的經驗)
完整寫入流程

完整讀取流程

複合式讀寫流程

常用 I2C NVRAM 讀寫流程 by Philips PCF8953

  1. 一般 RTC 都有帶一些 NVRAM 或是讀寫 EEPROM,需要先指定讀寫的 register address 才可以。所以在寫時,先寫入 slave address 後,需再寫入 register address,讓 chip 知道你要寫入的起始位址,接下來才能寫入 data。
  2. 由於讀取前也要先寫入 register address,所以一般 NVRAM 讀取動作都是使用標準複合式流程,也就是先寫入 register address,再下一次 start conditon,再做讀取動作。
void i2c_write_byte(unsigned char slave_addr, unsigned char reg_addr, unsigned char value) { char i=10; EA = 0; while (--i) { i2c_start(); if(i2c_write(slave_addr)) continue; if(i2c_write(reg_addr)) continue; if(i2c_write(value)) continue; i2c_stop(); break; } EA = 1; } unsigned char i2c_read_byte(unsigned char slave_addr, unsigned char reg_addr) { char i=10; unsigned char value=0; EA = 0; while (--i) { // send register address i2c_start(); if(i2c_write(slave_addr&0xfe)) continue; if(i2c_write(reg_addr)) continue; // read data i2c_start(); if(i2c_write(slave_addr | 1)) continue; value = i2c_read(1); i2c_stop(); break; } EA = 1; return value; }

8052 console (command line) interface for SDCC

Posted by: 邱小新 at 下午2:04 in , , ,
這裏演示一下 8052 console interface 的做法,包含解析 ANSI escape code 的做法。
原始碼下載

全域變數

  1. char g_cmd_line[SZ_CMD_LINE+1];
    記錄指令輸入的暫存字串,SZ_CMD_LINE 不可大於 128。
  2. char g_position;
    記錄游標位置。
  3. char g_ansi_digit;
    記錄 ANSI escape code 的數字部位。
  4. bit g_ansi_mode;
    記錄是否進入 ANSI escape sequence 解析模式。
  5. char (*fp_parse_ansi)(char escape);
    記錄使用 ANSI 解析的函數。

console init

  1. 清除營幕所有東西,並回到左上角的位置,最後顯示提示字元。
void console_init(void) { g_cmd_line[0] = 0; g_ansi_mode = 0; g_position = 0; printf_small("\033[2J"); printf_small("\033[H"); printf_small("\r\n # "); }

console routine process

  1. 讀取輸入字元。
  2. 進入 ANSI escape code parse 或解析輸入字元。
  3. '\r': 輸入完成,解析輸入字串。
  4. 0x07: beep char,輸出錯誤嗚叫聲。
  5. '\t': 忽略定位字元。
  6. '\b', 0x7f: 刪除字元。
  7. '\033': 進入 ANSI escape sequence mode。
  8. default: 記錄字元並顯示在營幕上。
void console_process(void) { char ch=getchar(); if (g_ansi_mode) { ch = fp_parse_ansi(ch); } switch (ch) { case '\r': if (g_position > 0) { console_parse(); g_position = 0; } printf_small("\r\n # "); break; case 0x07: /* beep */ putchar(ch); break; case '\t': /* tab */ break; case '\b': /* backspace */ case 0x7f: /* delete */ delete_char(); break; case '\033':/* escape */ g_ansi_mode = 1; fp_parse_ansi = parse_ansi; break; default: if (isprint(ch)) { putchar(ch); insert_char(ch); } } }

console parse

  1. 取得 command id,並依 command id 執行相關程式。
void console_parse(void) { char cmid; char *next; g_cmd_line[g_position] = 0; next = parse_command(&cmid, g_cmd_line, g_main_command); switch (cmid) { case -1: // null string printf_small(c_return); break; case CMID_HELP: printf_small("\n\r\tHELP\t\t\thelp message"); printf_small("\n\r\tDUMP [addr] [len]\tdump code data"); break; case CMID_DUMP: dump_codedata(next); break; default: printf_small(c_error); break; } }

parse command

  1. 取得 command id,並回傳下一個參數字串指標。
char *parse_command(char *cmid, char *src, struct s_cmd *command) { char i; char *cmd, *next; // check null string cmd = clear_space(src); if (*cmd == 0) { *cmid = -1; return src; } // get command id next = next_string(cmd); for (i=0; command->cmd_id != CMID_MAX; i++) { if (strcmp(cmd, command->cmd_string) == 0) break; command++; } *cmid = command->cmd_id; return next; }

next string

  1. 取得參數字串並補上 \0 字元,並回傳下一個參數字串指標。
char *next_string(char *next) { while (*next) { if (*next == ' ') break; next++; } if (*next == ' ') { *next = 0; next++; } return next; }

clear space

  1. 清除空白字元,並回傳參數字串指標。
char *clear_space(char *src) { while (*src) { if (*src != ' ') break; src++; } return src; }

parse ansi code

  1. 解析 ANSI escape code 第一個字元。
char parse_ansi(char ch) { if (ch == 'O') { fp_parse_ansi = parse_func; } else if (ch == '[') { fp_parse_ansi = parse_bracket; } else { return 0x07; } return 0; }

parse bracket

  1. 解析 ANSI escape code 第二個字元。
char parse_bracket(char ch) { if (isdigit(ch)) { g_ansi_digit = ch - '0'; fp_parse_ansi = parse_digit; return 0; } // parse bracket g_ansi_mode = 0; switch (ch) { case 'A': // move up case 'B': // move down break; case 'C': // move right putchar(' '); insert_char(' '); break; case 'D': // move left delete_char(); break; default: return 0x07; } return 0; }

parse bracket

  1. 解析 ANSI escape code 第二個字元,這一個函數是針對 windows 的超級終端機所做的,標準 ANSI escape code 好像沒有。
char parse_func(char ch) { g_ansi_mode = 0; switch (ch) { case 'P': /* F1 */ case 'Q': /* F2 */ case 'R': /* F3 */ case 'S': /* F4 */ break; default: return 0x07; } return 0; }

parse bracket

  1. 解析 ANSI escape code 數字。
char parse_digit(char ch) { if (ch != 0x7e) { if (isdigit(ch)) { g_ansi_digit = g_ansi_digit * 10 + ch - '0'; return 0; } else { g_ansi_mode = 0; return 0x07; } } // parse digit g_ansi_mode = 0; switch (g_ansi_digit) { case 1: /* home */ g_position = 0; puts("\r # "); puts("\033[K"); break; case 3: /* delete */ delete_char(); break; case 11: /* F1 */ case 24: /* F12 */ break; default: return 0x07; } return 0; }

SDCC 指標變數 (pointers to specific memory spaces)

Posted by: 邱小新 at 上午9:49 in
由於 mcs51 的記憶體分成五大部份(註一),所以搞的 SDCC 的指標也要分成好幾種。以下先簡介指標定義準則:

located
指向位址
type
變數型態
pointerphysical
變數位址
name
__xdataunsigned char*__datap

指標分類

  1. __xdata unsigned char * __data p;
    指向外部記憶體的指標變數,變數存放在內部記憶體。
  2. __data unsigned char * __xdata p;
    指向內部記憶體的指標變數,變數存放在外部記憶體。
  3. __code unsigned char * __xdata p;
    指向程式記憶體的指標變數,變數存放在外部記憶體。
  4. __code unsigned char * __code p;
    指向程式記憶體的指標變數,變數存放在程式記憶體,指標變數是唯讀,所以必須先設定初始值
  5. unsigned char * __xdata p;
    指向任何記憶體的指標變數 (generic pointer),變數存放在外部記憶體。
  6. unsigned char * p;
    指向任何記憶體的指標變數 (generic pointer),變數存放由 memory model 決定。
  7. char (* __data fp)(void);
    指向程式記憶體的函數指標變數,變數存放在內部記憶體。函數指標變數只能指向程式記憶體,無法修改喔
  8. 由於內部記憶體指標讀取都是使用間接定址 (indirect addressing),所以無法存取 SFR 空間的記憶體內容。

指標變數大小

__data1
__xdata2
__code2
generic pointer3

  1. generic pointer 是設計用來可以做通用指標變數使用,可以任意變換指標指向位址,但是相對的需要佔用 3 個 bytes 的位置,而且在做讀取寫入時,需要另外執行一個 __gptrget 或 __gptrput 來轉換位址,沒事就少用一點吧。

SDCC link error

Posted by: 邱小新 at 下午4:55 in
一開始開發專案時,我們習慣把 source code 都放在 work root directory。漸漸的,檔案越來越多,我想要做分類,新建幾個目錄把檔案搬進去。這裏卻發現無法正常編譯,明明都沒有錯誤,卻發生 link error。這時只要做二個步驟就可以解決。

  1. 把 Release 目錄下的東西全部砍掉。
  2. 做一次 Clean Project 即可。

SDCC 記憶體模式 (memory model)

Posted by: 邱小新 at 下午4:49 in
modeldefaultsize
SMALL__data128使用直接定址內部記憶體當變數
Medium__pdata256使用間接定址外部記憶體當變數
Large__xdata64K使用間接定址外部記憶體當變數
8052 記憶體定址

SDCC command line options 命令列參數

Posted by: 邱小新 at 下午2:52 in

處理器參數 (Processor Selection Options)

  1. -mmcs51 (default)
    產生 Intel MCS51 系列的執行碼。
  2. -mgbz80
    產生 GameBoy Z80 處理器的執行碼。
  3. -mpic14
    產生 Microchip 14-bit 系列的執行碼。
  4. -mpic16
    產生 Microchip 16-bit 系列的執行碼。

前置處理器參數 (Preprocessor Options)

  1. -I<path>
  2. -D<macro[=value]>
  3. ...
  4. ...
  5. ...

連結器參數 (Linker Options)

  1. -L --lib-path <absolute path to additional libraries>
  2. --xram-loc <Value>
    外部記憶體的起始位置,預設值為 0,value 可以用十進位或 16 進位,如 --xram-loc 0x8000 or --xram-loc 32768。
  3. --code-loc <Value>
    程式記憶體的起始位置,預設值為 0,value 可以用十進位或 16 進位,如 --xram-loc 0x8000 or --xram-loc 32768。
  4. --out-fmt-ihx (default)
    產生 IntelHex 格式的燒錄檔案。
  5. --out-fmt-s19
    產生 Motorola S19 格式的燒錄檔案。
  6. --out-fmt-elf
    產生 ELF 格式的執行檔。
  7. ...

MCS51 參數 (MCS51 Options)

  1. --model-small (default)
  2. --model-medium
  3. --model-large
  4. --xstack
  5. --iram-size <Value>
    設定內部記憶體 (internal ram) 大小,預設值為 256。
  6. --xram-size <Value>
    設定外部記憶體 (external ram) 大小,預設值為 64K。
  7. --code-size <Value>
    設定程式記憶體 (code memory) 大小,預設值為 64K。
  8. --stack-size <Value>
    設定堆疊 (stack) 大小。
  9. --pack-iram (default)
  10. --no-pack-iram
  11. --acall-ajmp

最佳化參數 (Optimization Options)

  1. ...
  2. ...
  3. --no-xinit-opt
    不要拷貝 code 的初始資料到 xdata。當你沒有任何初始資料時,可以使用此選項來減少 code size。
  4. --nooverlay
    傳遞參數及局部變數不要重疊使用,這樣可能會加大記憶體耗損,但是可以保障程式不出問題。
  5. --opt-code-speed
    編譯出最快的執行檔,執行檔會變大。
  6. --opt-code-size
    編譯出最小的執行檔,執行檔會變慢。

其它參數 (Other Options)

  1. -c --compile-only
    只有編譯而已,不做連結,不產生燒錄檔案。
  2. -E
    只有執行前置處理器,並把結果顯示到標準輸出即營幕上。
  3. -o <path/file>
    輸出目錄路徑。
  4. --stack-auto
    所有的函數都被編譯成可重復進入 (reentrant) 的函數。
  5. --Werror
    把所有的警告當成錯誤。
  6. --print-search-dirs
    顯示編譯器的預設搜尋路徑。
  7. --vc
    使用 MSVC 樣式來顯示警告及錯誤,預設是使用 GCC 樣式。
  8. --std-sdcc89 (default)
    使用 C89 標準,並允許 SDCC 的擴充指令。
  9. --std-c89
    使用 C89 標準,不允許 SDCC 的擴充指令。
  10. --std-sdcc99
    使用 C99 標準,並允許 SDCC 的擴充指令。
  11. --std-c99
    使用 C99 標準,不允許 SDCC 的擴充指令。
  12. ...

8051/8052 memory addressing

Posted by: 邱小新 at 下午4:06 in ,

記憶體種類

typeSDCCinstructionsizeaddressing
(A) program__codeMOVC64Kindex direct
(B) external__xdataMOVX64Kindirect
(C) internal(80~FF)__idataMOV128indirect
(C) SFR(80~FF)__sfrMOV128direct
(C) internal(0~7F)__dataMOV128direct/indirect

記憶體定址

typereadwriteregister
directMOV Rn,direct
MOV direct,direct
MOV direct,Rn
MOV direct,@DPTR
MOV direct,#data
R0~R7,A
indirectMOV direct,@Ri
MOVX A,@DPTR
MOV @Ri,direct
MOVX @DPTR,A
MOV @Ri,#data
R0,R1,DPTR,A
index directMOV A,#30h
MOV DPTR,#300H
MOVC A,@A+DPTR
XA,DPTR
  1. Ri=R0,R1,Rn=R0~R7。
  2. 索引定址法 (index direct) 只能用在程式記憶體,而且不能寫入,只能讀取到 A。
  3. 外部記憶體只能使用間接定址法 (indirect),而且只能透過 Ri/DPTR/A 三者傳遞數值,如果要寫入數值,只能把值先存入 A,再由 A 寫入 DPTR/Ri。
  4. 內部記憶體不論間接或直接都可以直接把數值寫入,不需要透過 A,當然要經由 A 也是可以。

SDCC autobaud function

Posted by: 邱小新 at 下午3:56 in , ,

測試原由

SDCC 的 serial.h 內有一行 void autobaud(); 宣告,表示有自動鮑率偵測功能。所以就在 W79L632A 上實驗是否可行。它的原理是假定使用者第一次會輸入 return 鍵,也就是 ascii code 13。依照這個值利用 timer1 算時間差來做運算,進而取得 timer1 8-bit auto reload 的 TH 值。以下是測試的程式碼,大多是參考 SDCC 內的 serial.c 函數功能。

變數宣告

#include <8051.h> extern void autobaud();

輸出函數

void putchar(char c) { while(!TI); TI=0; SBUF=c; }

輸入函數

char getchar(void) { char c; while(!RI); RI=0; c=SBUF; return c; }

主要函數

void main(void) { ES=0; /* kill SIO IRQ */ TI=0; /* prepare sending */ RI=0; /* prepare reading */ autobaud(); /* automatically detect and set baud rate */ getchar(); /* discard the CR character from the autobaud routine */ // echo while(1) { putchar(getchar()); } }

測試結果

結果非常失望,完全沒有作用,功能無效。不知是不是因為我的 W79L632A 使用 40MHz 的震盪器。也不知 winbond LD 的 autobaund 功能怎麼寫的?只好放棄這個功能囉。

8052 UART

Posted by: 邱小新 at 上午9:26 in

baudrate 推導過程,以 timer2 為 baudrate 產生器

  1. baudrate 的單位為 bps 即每秒傳送多少 bits,反過來說傳送一個 bit 要花掉 1/baudrate 時間。
  2. 根據上圖來看,每次 timer 溢位一次,要先經過一個 16 的除法器,所以需 16 次溢位才接收一個 bit。
  3. 根據上圖來看,計數器每次加一需先經過一個 2 的除法器,所以每一次溢位時間為 (65536-RCAP2) * 2 / Fosc。
  4. 每接受一個 bit 要花掉時間為 16 * (65536-RCAP2) * 2 / Fosc。
  5. 1/baudrate = 16 * (65536-RCAP2) * 2 / Fosc = 32 * (65536-RCAP2) / Fosc。
  6. baudrate = Fosc / 32 / (65536-RCAP2)
  7. 以 40MHz 為例,baudrate 最大值為 1250000,最小值為 19。
  8. RCAP2 = 65536 - (Fosc / 32 / baudrate)
  9. 以 11.0592MHz 為例,baudrate=115200 時,RCAP2 = 65536-(11059200 / 32 / 115200)=65536-3=65533。
  10. 以 22.1184MHz 為例,baudrate=115200 時,RCAP2 = 65536-(22118400 / 32 / 115200)=65536-6=65530。

8052 Timer 2

Posted by: 邱小新 at 下午3:06 in
  1. Timer 2 有三種模式,透過 T2CON 來設定。

    RCLK/TCLKCP/RL2TR2mode
    0011. 16 位元自動載入
    0112. 16 位元補捉
    1x13. 鮑率產生
    xx0停止動作
  2. 當 timer2 發生溢位或 T2EX 偵測到負緣信號時,都會產生中斷訊號。所以在中斷副程式中需要手動判斷並清除 TF2 及 EXF2。
  3. 當 timer2 設定成鮑率產生模式時,oscillator 經過一個 2 除法器,而非一般的 12 除法器,計算時要注意。
  4. 標準的 8052 並沒有 T2MOD 這個 register,一般都是廠商的特殊功能才會設置 T2MOD register。
  5. 當進入中斷函式時,一定要先清除 TF2,否則中斷不會再發生。這一點在 datasheet 裏沒提到,在 timer0/1 因為會自動清除 TFx,所以沒有這個問題。
圖一16-Bit Auto-reload Mode 16 位元自動載入模式
CP/RL2=0 and RCLK=0 and TCLK=0
圖二16-Bit Capture Mode 16 位元補捉模式
CP/RL2=1 and RCLK=0 and TCLK=0
圖三Baudrate Generator Mode 鮑率產生模式
RCLK=1 or TCLK=1

8052 Timer 2 Control Register (T2CON)

Posted by: 邱小新 at 下午1:08 in
  1. TF2: Timer 2 Overflow Flag
    1. 當 timer2 發生溢位時,此位元由硬體自動設成 1。
    2. 當 RCLK=TCLK=1 時,此位元無作用。
    3. 此位元需由軟體手動清除成 0。
  2. EXF2: Timer 2 External Flag
    1. 當 EXEN2=0 無作用。
    2. 當 T2EX 偵測到負緣信號時,此位元由硬體自動設成 1。
    3. 此位元需由軟體手動清除成 0。
  3. RCLK: receive clock bit
    1. 當 uart 工作於 mode 1/3,此位元決定接收時脈來源。
    2. RCLK=0,以 timer1 的溢位脈波為時脈來源。
    3. RCLK=1,以 timer2 的溢位脈波為時脈來源。
  4. TCLK: transmit clock bit
    1. 當 uart 工作於 mode 1/3,此位元決定傳送時脈來源。
    2. RCLK=0,以 timer1 的溢位脈波為時脈來源。
    3. RCLK=1,以 timer2 的溢位脈波為時脈來源。
  5. EXEN2: Timer 2 External Enable bit
    1. EXEN2=0,T2EX 訊號會被忽略。
    2. EXEN2=1,T2EX 偵測到負緣信號時會引發補捉或重載功能並設置 EXF2=1。
  6. TR2: Timer 2 Run Control bit
    1. TR2=0,關閉 timer2。
    2. TR2=1,開啟 timer2。
  7. C/T2: Timer/Counter 2 Select bit
    1. C/T2=0,計時模式,由內部裝置 (內部振盪) 來計數。
    2. C/T2=1,計數模式,由外部裝置 (T2 pin) 來計數。
  8. CP/RL2: Timer 2 Capture/Reload Select bit
    1. RCLK=1 or TCLK=1 無作用。
    2. CP/RL2=0,16 位元自動載入模式,當 timer2 產生溢位或是 T2EX 偵測到負緣信號時,會把 RCAP2H 載入到 TH2,RCAP2L 載入到 TL2。
    3. CP/RL2=1,16 位元補捉模式,當 T2EX 偵測到負緣信號時,會把 TH2 載入到 RCAP2H,TL2 載入到 RCAP2L。

8051 pin define

Posted by: 邱小新 at 上午11:42 in

ALE/PROG 地址使能信号端

  1. 8051外接RAM/ROM时,ALE接地址器(8282)的STB脚,(74373)的EN脚,当CPU对外部存储器进行存取时,用以锁住地址的低位地址。
  2. 8051未外接RAM/ROM时,ALE脚会有1/6晶体振荡频率,可作为外部时钟。
  3. 在烧写EPROM时,ALE作为烧写时钟的输入端。

PSEN 程序储存使能端

  1. 内部程序存储器读取,不动作。
  2. 外部程序存储器读取(ROM),在每个机器周期会动作两次。
  3. 外部数据存储器读取(RAM),两个/PSEN脉冲被跳过不会输出。
  4. 外接ROM时,与ROM的/OE脚连接。

EA/VPP

  1. 接高电平时:CPU 读取内部程序存储器(ROM)。扩充外部 ROM,当读取内部程序存储器超过0FFFH(8051)、1FFFH(8052)时,自动读取外部ROM。
  2. 接低电平时:CPU 读取外部程序存储器(ROM)。
  3. 8751 烧写内部 EPROM 时,利用此脚 21V 的烧写电压。

RESET 复位引脚

  1. 为高电平时(约2个机器周期),可将CPU复位。

P0

  1. 外部扩充存储器时,作数据总线(D0~D7)。
  2. 外部扩充存储器时,作地址总线(A0~A7)。
  3. 不扩充时,作一般 I/O 使用,内部无上拉电阻,作为输出/输入使用时应加上拉电阻。

P1

  1. 只作 I/O 口使用,有内部上拉电阻。

P2

  1. 扩充外部存储器时,作地址总线(A8~A15)使用。
  2. 不扩充时,作一般 I/O 口使用,有内部上拉电阻。

P3

  1. 作一般 I/O 口使用,有内部上拉电阻。
  2. 有一些特殊功能。

8051 UART

Posted by: 邱小新 at 下午3:04 in
點圖看原始圖

baudrate 推導過程,以 timer1 為 baudrate 產生器

  1. baudrate 的單位為 bps 即每秒傳送多少 bits,反過來說傳送一個 bit 要花掉 1/baudrate 時間。
  2. 根據上圖來看,每次 timer 溢位一次,總共要經下列關卡才會接收一個 bit。
    1. 假如 SMOD=0 要經過一個二的除法器。
    2. 再進入 clock 前要先經過一個 16 的除法器。
    3. 當 SMOD=0 需 16 * 2 的計時器溢位才會接收一個 bit。
    4. 當 SMOD=1 需 16 的計時器溢位才會接收一個 bit。
    5. 推導出,需 16 * 2 / 2^SMOD 的計時器溢位才會接收一個 bit。
  3. 以 8-bit auto reload mode 來說,每一次溢位時間為 (256-TH1) * 12 / Fosc。
  4. 每接受一個 bit 要花掉時間為 (16 * 2 / 2^SMOD) * ((256-TH1) * 12 / Fosc)。
  5. 1/baudrate = (16 * 2 / 2^SMOD) * ((256-TH1) * 12 / Fosc)。
  6. 1/baudrate = (32 / 2^SMOD) * ((256-TH1) * 12 / Fosc)。
  7. baudrate = (2^SMOD * Fosc) / (384 * (256-TH1))
  8. 以 40MHz 為例,baudrate 最大值為 208333.33,最小值為 406.90。
  9. Th1 = 256 - (2^SMOD * Fosc / baudrate / 384)
  10. 以 11.0592MHz 為例,baudrate=19200 及 SMOD=1 時,TH1 = 256 - (2*11059200/19200/384) = 256-3 = 253。
  11. 以 11.0592MHz 為例,baudrate=9600 及 SMOD=0 時,TH1 = 256 - (11059200/9600/384) = 256-3 = 253。

8051 Serial Port Control Register (SCON)

Posted by: 邱小新 at 下午2:01 in
  1. SMO, SM1: serial port mode。

    SM0SM1modelength同步baudrate
    0008同步Fosc/12
    01110異步time1
    10211異步Fosc/32 or Fosc/64
    11311異步time1

  2. SM2: multiple processors communication。

    modeSM2action
    00x
    11收到有效的停止位元才會設定 RI。
    2,31當 RB8=1 才會將值送入 SBUF,並設定 RI。

  3. REN (Receive enable): REN=1 才會開啟 uart 接收功能。
  4. TB8: 在模式 2,3 時,在 bit 9 要傳送的數值,軟體要手動設定。
  5. RB8: 在模式 2,3 時,在 bit 9 所接收的數值。
  6. TI: Transmit interrupt flag。 uart 送完資料就會設定這個旗標,須由軟體手動清除此旗標。
  7. RI: Receive interrupt flag。 uart 收到資料就會設定這個旗標,須由軟體手動清除此旗標。

W78E65 on-chip 1KB AUX-RAM

Posted by: 邱小新 at 下午4:47 in
  1. 空間位址在 0x0000 ~ 0x03ff。
  2. 只能使用 MOVX 指令來讀寫,也就是設成 xdata 的變數。
  3. CHPCON 中 ENAUXRAM (bit 4) 可以設定是否使用 on-chip AUX-RAM。
    ENAUXRAM=0,0x0000~0xffff 都指向外部記憶體。
    ENAUXRAM=0,0x0000~0x03ff 指向內部記憶體。

/* enable on-chip 1KB MOVX SRAM */ CHPENR = 0x87; CHPENR = 0x59; CHPCON |= ENAUXRAM; CHPENR = 0x00;

W78E65 security register

Posted by: 邱小新 at 下午4:20 in
  1. 出廠 default 值為 0xFF,存放位址在 LDFlash 的 0xFFFF。
  2. 當某一個 bit 變成 0 後,就不可以再改成 1,要變成 1 的唯一方法就是使用 earse LDFlash 功能。
  3. Bit 0 - Lock bit (LOCK BIT)
    設成 0 時 (Active),flash 及 security 都無法讀取。
  4. Bit 1 - MOVC Inhibit (MOVC BIT)
    設成 0 時 (Enable),當 MOVC 指令在外部記憶体空間時,只能存取外部記憶体。當 MOVC 指令在內部記憶体空間時,則可以存取內部及外部記憶體。
  5. Bit 2 - Encryption
    設成 0 時 (Enable),port 0 的資料輸出會被加密。
  6. Bit 3~6 -- reserved 沒用到
  7. Bit 7 -- Select clock freqency (Osillator Control)
    設成 0 時 (1/2 gain),MCU 頻率小於 24 MHz。
    設成 1 時 (full gain),MCU 頻率大於 24 MHz。

W79L632A ISP Operation Modes register

Posted by: 邱小新 at 下午1:33 in
  1. BANK: BANK=0 選擇 Flash 0,BANK=1 選擇 Flash 1,由於 LD 只有一個 bank,所以當 WFWIN=0 時,BANK 不可寫入 1。
  2. WFWIN: WFWIN=0 選擇 LDFlash,WFWIN=1 選擇 APFlash。
  3. NOE: NOE=0 唯讀模式,NOE=1 寫入模式。
  4. NCE: NCE=0 flash 燒錄模式,NCE=1 flash 工作模式。
  5. CTRL[3:0]: 燒錄模式選擇。

ISP MODEBANKWFWINNOENCECTRLSFRCNtime
抹除 LDFlash011000100x6215ms
抹除 APFlash0001000100x2215ms
抹除 APFlash1101000100xA215ms
寫入 LDFlash011000010x6150us
寫入 APFlash0001000010x2150us
寫入 APFlash1101000010xA150us
讀取 LDFlash010000000x401.5us
讀取 APFlash0000000000x001.5us
讀取 APFlash1100000000x801.5us

  1. 抹除功能一次可抹除整個 flash,抹除完成後全部的值都變成 0xff。
  2. 讀寫功能一次只能讀寫一個 byte。
  3. 操作時間要視 XTAL 的速度來計算,上述的值是以 XTAL=24MHz 來計算。
  4. SFRAH/SFRAL 儲放欲讀寫的位址,SFRFD 則儲放讀寫的值。

W79E632A ISP control register

Posted by: 邱小新 at 上午11:37 in
  1. SWRST: software reset,令 W79E632A 回到初始狀態,並自動清除這個 bit。
  2. HWB: hardware boot,HWB=1 表示 ISP hardware reboot mode。
  3. LDAP: reand only,LDAP=1 表示在 LDFlash 運行,LDAP=0 表示在 APFlash 運行。
  4. LDSEL: LDSEL=1 表示從 LDFlash 讀取 code 執行。
  5. ENP: ENP=1 表示進入 the ISP mode。

從 APFlash 進入 LDFlash

TA = 0xAA; TA = 0x55; CHPCON = CHP_LSEL | CHP_ENP; SFRCN = 0; /* 不執行也可以, 原廠 sample code 有做 */ PCON |= IDL;

從 LDFlash 進入 APFlash

TA = 0xAA; TA = 0x55; CHPCON = SWRST | CHP_LSEL | CHP_ENP; SFRCN = 0; /* 不執行也可以, 原廠 sample code 有做 */ PCON |= IDL;

在 LDFlash 執行 ISP 指令前,需先指定 CHPCON 參數,只需指定一次即可。

TA = 0xAA; TA = 0x55; CHPCON = CHP_LSEL | CHP_ENP; SFRCN = 0; /* 不執行也可以, 原廠 sample code 有做 */ PCON |= IDL; /* 不執行也可以, 原廠 sample code 有做 */

在 APFlash 執行 ISP 指令前,需先指定 CHPCON 參數,只需指定一次即可。

TA = 0xAA; TA = 0x55; CHPCON = CHP_ENP; SFRCN = 0; /* 不執行也可以, 原廠 sample code 有做 */ PCON |= IDL; /* 不執行也可以, 原廠 sample code 有做 */

注意事項

  1. CHPCON 在寫入時需要 timed access protection
  2. CHPCON 在進入 idle mode 才會執行上述指令,所以記得要讓 W79L632A 離開 idle mode,原廠 sample code 使用 XTAL=24MHz 在 idle mode 花費 8us。
  3. 其實可以不要理會 datasheet 上在寫什麼,記住切換 flash 要如上述的下指令即可,不要想要更動指令,會產生錯誤的結果,因為我已經試過都無效了。

8051 Timer

Posted by: 邱小新 at 上午9:45 in
TMOD register 0x89 (timer/counter mode control register)

  1. GATE = 1 需要 INTx 接腳為高電位才計時,GATE = 0 則不需要。

    也就是當 TRx=1 and GATE=0 開始計時,或者當 TRx=1 and GATE=1 and INTx=1 開始計時,其它條件都不計時。

  2. C/T = 0 使用內部時脈,C/T = 1 使用外部時脈。

    Timer 的計時時脈來源有兩種,一種是 8051 單晶片的內部時脈也就是從 XTAL1 與 XTAL2 接腳所輸入的內部時脈,一種是從 T0 與 T1 接腳所輸入的外部時脈。所以一般來說設定成內部時脈時,稱為計時器,因為振盪器頻率固定可以當計時器用;而設定成外部時脈時,稱為計數器,因為不知使用者會接什麼裝置,但是都有計算次數功能。如果外部時脈也是接振盪器時,也可以算是計時器,只是沒人會如此做,因為直接使用內部時脈即可,可以省成本又可以多些腳位使用。

  3. M1, M0: Mode Select bits:

    M1M0MODE
    0013 位元計時器
    0116 位元計時器
    108 位元自動重新載入
    11兩個8位元計時器

  4. 在 8051 單晶片使用內部時脈計時,會在每個機械週期值由〝1〞變為〝0〞時,將 Timer 的值累加1。也就是每一個 count 的時間為一個機械週期的時間,時間算法請見 8051 clock

  5. 當計時器溢位時(0xff->0x00),會設置 TFx 為 1;如果 ETx 被設置成 1,則接著會進入中斷常式,並且把 TFx 設置為 0。

8 位元自動重新載入時間算法,以 40MHz 為例:


  1. 當 TLx 從 0xff 變成 0x00 時,會產一個中斷,並且把 THx 載入 TLx 再繼續計數。所以產生中斷的次數為 256-THx。

  2. 1 count = 12/40000000(s) = 12/40000(ms) = 12/40(us),所以產生中斷的時間為 (256-THx) * 12/40。

  3. 最大值為 (256-0)*12/40 = 76.8(us),最小值為 (256-255)*12/40 = 0.3(us)。

  4. 如果設定中斷時間為 60us 則 (256-THx) * 12/40 = 60,THx = 256 - 60*40/12 = 56。

watchdog interrupt function

Posted by: 邱小新 at 下午5:13 in

watchdog time-out 後會呼叫 watchdog interrupt function,再經過 512 clock 後會做 reset 8051 動作。如果在這 512 clock 之間重置 watchdog 就不會 reset 8051。經過測試結果,在很久很久的時間過後,仍有機會引起 reset 動作,也就是 reset watchdog 沒有來得急做,即使把 watchdog interrupt priority 提高也是一樣。最好不要用喔。

void watchdog_isr(void) interrupt 12 { // clear interrupt flag TA = 0xAA; TA = 0x55; WDIF = 0; // reset watchdog TA = 0xAA; TA = 0x55; RWT= 1; }

W79E632A 啟動時的旗標

Posted by: 邱小新 at 下午4:46 in
flagPower-on resetWatchdog resetExternal reset
POR1xx
WTRF010
EWT0xx
WDCON01000000b0x0x01x0b0x0x0xx0b

由上表得知一些判斷開機的因素,整理如下:

  1. 判斷是因為電源開關所引起的開機。
    只要判斷 POR 為 1 即可,開完機後需要立即把 POR 清為 0,以利後續判斷。
  2. 判斷是因為 watchdog 所引起的開機。
    只要判斷 WTRF 為 1 即可,開完機後需要立即把 WTRF 清為 0,以利後續判斷。
  3. 判斷是因為外部中斷所引起的開機。
    只要不是上述二個條件就是外部中斷引起的。
if (POR) { POR = 0; // power-on reset } else if(WTRF) { WTRF = 0; // watchdog reset } else { // external reset } PS: 一開機後請立即清除 EWT,以免 watchdog reset 再次發生。

W79E632 watchdog function

Posted by: 邱小新 at 下午3:27 in

啟動 watchdog 程序

  1. 先關閉 watchdog 功能,防止一些干擾的問題。
  2. 選擇 watchdog time-out 時間。
  3. 設定 watchdog 中斷開關。
  4. 開啟 watchdog 重置工作。
  5. 開啟 watchdog 功能。
void watchdog_start(void) { // clear watchdog flag TA = 0xAA; TA = 0x55; WDCON = 0; // select 2^26 time-out interval CKCON |= 0xC0; // disable watchdog interrupt EWDI = 0; // start watchdog reset task poller_start(POLLER_WATCHDOG, TIMER_WATCHDOG); // enable watchdog TA = 0xAA; TA = 0x55; WDCON = 3; }

關閉 watchdog 程序

void watchdog_stop(void) { TA = 0xAA; TA = 0x55; EWT= 0; }

重置 watchdog 程序

void watchdog_reset(void) { TA = 0xAA; TA = 0x55; RWT= 1; } PS: 一開機後請立即清除 EWT,以免 watchdog reset 再次發生。

8051 interrupt

Posted by: 邱小新 at 下午1:42 in ,
  1. 8051 的中斷服務功能,可使程式強制中斷去執行需要即時處理的副程式,進而提升執行效率。
  2. 在 8051 中總共提供五個中斷來源。INT0,INT1,Timer0,Timer1,UART。
  3. 中斷源在 8051 中都有相對應的旗標,當中斷條件產生時,中斷源就會使其相對應的旗標值設定為 1。8051 會在每一個機械週期檢查這些旗標的狀態,若系統允許相對的中斷源產生中斷,且該中斷相對應的旗標值亦為 1 時,則 8051 會在執行完目前正在執行的指令後,將程式在記憶體中的位址存入堆疊中,並產生中斷服務副程式的呼叫,跳到該中斷所對應之中斷向量位址去執行,8051 執行該中斷服務副程式,直到 RETI 指令後才結束中斷副程式,再從堆疊中取出先前存入的位址值繼續執行被中斷的程式。
  4. 當一個中斷要求發生時,若中斷是被致能的,則 8051 會執行該中斷服務副程式。然而在執行中斷副程式中若有較高優先權的中斷源要求中斷,則 8051 會先暫停目前正在執行的中斷服務副程式,而立即執行這個較高優先權的中斷服務副程式。如果相同優先權或優先權較低的中斷源要求中斷,則 8051 將不予理會。另外,若兩中斷同時發生,則高優先權中斷源優先執行;但若優先權相同時,則依 INT0、Timer0、INT1、Timer1、UART 之順序先後執行。

中斷產生的要件

  1. 沒有任何事情可以停止高優先權的中斷運行,即使是另一個擁有高優先權的中斷。
  2. 一個擁有高優先權的中斷發生,可以強制停止另一個低優先權的中斷運行,讓自己先行。
  3. 一個擁有低優先權的中斷發生,只能在沒有任何中斷運行的條件下,才可以執行。
  4. 如果二個中斷同時發生,高優先權的中斷會先執行;如果是同樣優先權的中斷,則依 INT0、Timer0、INT1、Timer1、UART 之順序先後執行。

中斷發生時所做的事

  1. Program Counter push 進堆疊裏,low-byte 最先。
  2. 同一優先權及低優先權的中斷被阻塞。
  3. 在時間及外部中斷發生時,相對應的中斷旗標會被自動清除。
  4. Program Counter 轉換成相對應的中斷向量位置。
  5. 執行中斷程式。

中斷結束時所做的事

  1. Program Counter 堆疊裏 pop 出來。
  2. 中斷旗標被恢復成中斷前的值。
  3. 所以在時間及外部中斷發生時,中斷程式中就不需要為了防止再次中斷發生,而自己去操作中斷旗標
  4. 繼續執行中斷前的程式。

中斷必須保護的暫存器

  1. ACC
  2. B
  3. DPTR (DPH/DPL)
  4. PSW
  5. R0~R7

中斷時 register bank 的使用

  1. R0~R7 有 4 個 register bank 可以使用。
  2. SDCC 發現你在中斷使用 bank0 時,都會對 R0~R7 做 push/pop 動作。
  3. SDCC 發現你在中斷使用 bank1~3 時,不會對 R0~R7 做 push/pop 動作。
  4. SDCC 的中斷函數跟 main 函數沒有放在一起編譯時,則不會有任何最佳化作用,也就是中斷必須保護的暫存器通通都會做 push/pop,表示 register bank 的使用無效。
  5. 因為相同優先權不會互相中斷執行,所以相同優先權的 interrupt 可以使用同一個 bank。
  6. 因為高優先權會中斷低優先權的執行,所以不同優先權的 interrupt 不可使用同一個 bank(bank0 除外)。
  7. 最好按照下列方式使用:
    1. bank0: main routine
    2. bank1: low-priority interrupt
    3. bank2: high-priority interrupt
  8. 如果要節省記憶體的使用,就全部用 bank0 吧。