顯示具有 keil 標籤的文章。 顯示所有文章

當指標遇上結構

Posted by: 邱小新 at 上午10:50 in ,

請注意下列二行程式碼有啥不同?

1. (__xdata unsigned char *)(icmp_data_rx+sizeof(struct icmp_hdr)) 2. ((__xdata unsigned char *)icmp_data_rx)+sizeof(struct icmp_hdr) 3. (__xdata unsigned char *)icmp_data_rx+sizeof(struct icmp_hdr)

沒錯,就是括弧位置不同,但是請看下列編譯出來的組合碼。

../source/icmp.c:54: (__xdata unsigned char *)(icmp_data_rx+sizeof(struct icmp_hdr)), mov dptr,#_icmp_data_rx movx a,@dptr mov r4,a inc dptr movx a,@dptr mov r5,a mov a,#0x40 ; 不同處 add a,r4 mov _memcpy_PARM_2,a clr a addc a,r5 ../source/icmp.c:54: ((__xdata unsigned char *)icmp_data_rx)+sizeof(struct icmp_hdr), mov dptr,#_icmp_data_rx movx a,@dptr mov r4,a inc dptr movx a,@dptr mov r5,a mov a,#0x08 ; 不同處 add a,r4 mov _memcpy_PARM_2,a clr a addc a,r5

注意到那不同處的地方了吧,二者算出來的值卻相差十萬八千里,其實在指標使用時,這是一個很容易犯錯的地方。當你原本只是要取一個結構結尾的指標,很自然的會寫出 1 的程式碼,但那是錯誤的。因為指標加法是根據指標的變數型態去相加,比如一個 short 指標加一,是指向下一個 short 位置,也就是指數加二。而在此案例上,第一種寫法是指向第8個結構的位置,也就 8x8=64=0x40 的位置,實在是大錯特錯。正確寫法為第2,3個寫法。

IP210S external flash (code banking)

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

最近又接了一份工作,是搞 IP210S 的案子,上頭想在 LCD 裏內建網路,可以讓使用者透過網路去控制 LCD 功能,還有能從網路更新軔體;好像是要仿造 NEC 的軟體功能。

拿到 IP210S 的 source code,看了一下,主要的函數全被封裝在 library 裏了,真不知為啥要簽 NDA,都被包起來了,有意義嗎?

今天主要是來記錄 code banking 的用法,IP210S 內含一顆 8052 的 mcu,ROM 是採用外部 flash,SST 39VF040 512KB。原本 IP210S 只有 16 條的 address line,也就是只能使用 64KB,剛好也是 8052 最大的定址位址。

那 512KB 要怎麼使用?當然就是切成八塊 64KB,使用 Keil-C 的 code banking 功能,硬體也很簡單,拉三條 GPIO 到 39VF040 的 A16~A18 即可。而 L51_BANK.A51 改成使用 mode 4,自己寫 bank switch code 即可。bank 0 就三個 GPIO 設成 0,bank 1 就 A16 那個設成 1,以此類推,夠簡單吧。

之前只使用過 winbond W79E632 的 code banking,而且還是 internal ROM,使用 mode 1,一直搞不清楚 mode 4 會用在那里。現在有了範例,就更清楚為啥要分那麼多種模式了。學 8051 還真是沒啥捷徑,真是要多看多學,才能累積經驗值。

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。

未完待續......