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

SDCC reentrant library

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

在 mutli-task 或是 interrupt 中想要使用長整數乘除法是不可以的。因為長整數乘除法會用到額外的全域變數 _mulint_PARM_2,如果同時有二個 task 同時用到長整數乘除法時,就會造成錯亂。

這時候就需要加入 --stack-auto 參數,讓所有編譯都搞成 reentrant 函數,這樣就可以避免使用全域變數的問題。

雖然使用 --stack-auto 可以呼叫 reentrant libray,但是也把使用者的函數全都編譯成 reentrant function 了。這樣雖然沒有什麼大問題,但是在 tiny rtos 因為 stack 空間不大,使用太多 reentrant function 有可能會讓 stack 爆掉。所以只在 SDCC Linker 地方加入 --stack-auto 即可,不要在 SDCC Compiler 加。

奇怪的是卻出現了,在 SDCC Linker 時會有錯誤訊息 ?ASlink-Warning-Undefined Global '__mulint_PARM_2' referenced by module 'ip210s_net';原來在 SDCC Linker 加上 --stack-auto 是不夠的,此時需在 SDCC Compiler 加入 --int-long-reent 才能正確無誤使用長整數乘除法。

tiny rtos for 8051 using SDCC part 4

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

原始碼下載

前言

由於 IP210S 使用的 flash 有 512KB,所以一定會用到 code banking 的功能,所以這次把 code banking 功能加入 tiny rots 裏。主要新增的部份就是在切換 task 時,會把目前 bank 值 push 到堆疊裏,然後把下一個 task 的 bank 值 pop 出來,並還原 bank 設定。

  • IP210S 一開機時會把 A16~A18 設成 0,所以相對應的 port 值在一開始初始化時,不要任意變動。因為只要一變動,address 就會被切換到新的 bank 區。
  • 比如我喜歡在初始化時把所有 port 值設成 0xff,所以在初始化過後,bank 會被切換到 bank7。因此在最後一塊必須要有 common code,不然會當機的。
  • 由於 demo board 上的 A16~A18 剛好位在二個不同的 port 上,當我從 bank0 切換到 bank7 時,會先設定 A18=1,此時 bank0 會馬上切到 bank4,如果 bank4 剛好沒有 common code,那就馬上當機了,同理設定 A16/A17 值也是如此,所以最好每一個 bank 都要有 common code,不然就是好好注意 A16~A18 設定時的 bank 變化。
  • 在 rtos_start 裏記得要多一行指令 *((__idata unsigned char *)rtos_stack[i]) = i;,目的是要把每一個 task 的 bank 值先設定好,免得跑錯 bank 區域了。
  • 每一個 task 的起始函數不需要加 __banked,因為那些函數是沒有結束的一刻,所以加不加都無所謂。因為加入 __banked 是要讓編譯器把 call 及 ret 換成 __sdcc_banked_call 及 __sdcc_banked_ret,既然都沒有結束,也不會呼叫到 ret。

SDCC Bankswitching (code banking) part 2

Posted by: 邱小新 at 下午3:08 in ,
範例下載,使用 ip210s,uart output "3210+dcba"。

前言

最近在玩 IP210S 的晶片,該顆 8051 使用外掛的 flash(SST 39VF040),有 512KB 之大,總共有 19 條 address line。之前寫的 SDCC Bankswitching 太簡略了,所以又提筆補充了一下。

  1. ip210s 對於 512K flash 的 address line 是放在 P1.7(18),P3.4(17),P3.5(16)。所以只要對該三個 GPIO 做設定,就可以讓 ip210s 定址到 512KB。
  2. 每個 bank 的起始位址設定指令為 "-Wl-b BANK1=0x18000",BANK1 為 #pragma codeseg 的名稱,0x18000 為該 bank 的起始位址。以 IP210S 為例,512/64=8,所以必須切成 8 banks。"-Wl-b BANK0=0x8000","-Wl-b BANK1=0x18000","-Wl-b BANK2=0x28000","-Wl-b BANK3=0x38000","-Wl-b BANK4=0x48000","-Wl-b BANK5=0x58000","-Wl-b BANK6=0x68000","-Wl-b BANK7=0x78000"。
  3. #pragma codeseg 的使用只對在它宣告下面的程式碼有用,以上則無效,因為內定值是不同的,所以才需用 #pragma codeseg 來變更。
  4. 在寫程式時,通常伴隨著一些存放在 code 的變數,如 code char str[] = "hello test\n\r";,一般而言,它們存放的區域也是放在 common 區,如果也把它們放在 bank 裏,則需宣告 #pragma constseg,如此 SDCC 才會把 code 變數也放到 bank 裏。
  5. SDCC 編譯出來的 hex 檔,包含了 common 及所有的 bank 程式,經由 hex2bin 的轉換後,只有 bank0 包含 common 程式碼,其它 bank 都沒有,只能利用 ultraedit 自己剪貼搞定。
  6. 使用 SDCC Bankswitching 需要在 main.c 宣告一個變數 unsigned char PSBANK=0;,並要先初始化為 0,這個 PSBANK 主要是存放目前的 bank 值。

__sdcc_banked_call

  1. 一般函數呼叫都是用 lcall _test1,但是在呼叫 banked 函數時,則是會先呼叫 __sdcc_banked_call,並把 banked 函數位址放到 r0~r2 當參數傳遞。
  2. 當要呼叫位於 bank? 區段的函數時,會把讓函數的位址放到 r0~r2,由於在 bank? 區段的函數位址包含了最高的 16~18 條位址線,所以會把 r2 當做 bank 的選擇,實際上 r2 只是代表 address line 的 16~23 位址線而已。由此可知,SDCC 的 Bankswitching 可以達到 224,也就 16M bytes。
  3. 一開始需先把目前的 bank 值(PSBANK)推入堆疊,以便離開時還原 bank 值。
  4. 由於函數可能利用 acc 來傳遞參數,所以必須先把 acc 值保存,xch a,r0 就是把 acc 保存在 r0。
  5. 接著把呼叫函數的位址推入堆疊,以便等一下 ret 時跳到該函數位址。
  6. 接著就是判斷 r2 值來切換位址線,我只會最笨的方法,貽笑大方了。
  7. 然後還原 acc 值,並用 ret 返回呼叫函數。
;../source/test2.c:19: test1(); mov r0,#_test1 mov r1,#(_test1 >> 8) mov r2,#(_test1 >> 16) lcall __sdcc_banked_call __sdcc_banked_call:: push _PSBANK ;save return bank xch a,r0 ;save Acc in r0, do not assume any register bank push acc ;push LSB address mov a,r1 push acc ;push MSB address ; set new bank mov a,r2 ;get new bank mov _PSBANK,acc cjne a,#0x07,call_bank6 setb _P1_7 setb _P3_4 setb _P3_5 sjmp call_end call_bank6: ;..... 略 ...... call_bank0: clr _P1_7 clr _P3_4 clr _P3_5 call_end: xch a,r0 ;restore Acc ret ;make the call

__sdcc_banked_ret

  1. 一般函數結束時都是直接執行 ret 回到上一層,但是在 banked 函數結束時,則是會呼叫 __sdcc_banked_ret,並經由 __sdcc_banked_ret 返回上一層。
  2. 一開始需先把備份的 bank 值(PSBANK)從堆疊取出,以便等會還原 address line。
  3. 由於函數可能利用 acc 來傳遞返回值,所以必須先把 acc 值保存,xch a,r0 就是把 acc 保存在 r0。
  4. 接著就是判斷 PSBANK 值來切換位址線,我只會最笨的方法,貽笑大方了。
  5. 然後還原 acc 值,並用 ret 返回上一層函數。
__sdcc_banked_ret:: pop _PSBANK ;restore bank xch a,r0 ;save Acc in r0, do not assume any register bank mov a,_PSBANK cjne a,#0x07,ret_bank6 setb _P1_7 setb _P3_4 setb _P3_5 sjmp ret_end ret_bank6: ;...... 略 ....... ret_bank0: clr _P1_7 clr _P3_4 clr _P3_5 ret_end: xch a,r0 ;restore Acc ret ;return to caller

tiny rtos for 8051 using SDCC part 3

Posted by: 邱小新 at 上午11:46 in , ,

原始碼下載

前言

在前面一個 tiny rtos 裏只是簡單平均分配時間到每一個 task 裏,但這有一個缺點,就是當某一個 task 需要暫停一下時,無法把時間交給其它的 task,這個時間無形間就被浪費掉了。所以在這一次新的 task 裏,加入了 rtos_delay_10ms 函數,讓 task 可以暫停一段時間,並把時間讓出來給別的 task。

  1. 把 timer0 的時間都切成 10ms,每一個 task 的運行時間都變成 10ms。
  2. 多了一個 rtos_tick 變數做 delay 的計數,以 10ms 為單位。

rtos_start

  1. 原本儲放 stack 的變數多了一個,用來放置 rtos_idle 的 stack。
  2. 第一個執行的 task 改成 rtos_idle,而非 task1。
  3. 由於 rtos_idle 沒做什麼事,所以只留一份必要的 stack 空間給它,其它的都分給其它 task。
  4. 不可以在此函數裏呼叫任何函數,因為呼叫函數會變更 stack 裏的內容(有 push 動作),這樣會把 task1 的 stack 破壞,造成進入 task1 時位置被變更,而跑不到 task1。
  5. 如果按照之前的方式,直接跳到 task1 執行,而不是從 rtos_idle 執行,就可以在此函數內呼叫其它函數,因為 stack 被破壞並不會影嚮 task1 的執行。
#define PUSH_SIZE 14 void rtos_start(void) { unsigned char i, size; __idata unsigned char *sp; // init stack size = (255 - SP - PUSH_SIZE - 2) >> 2; sp = (__idata unsigned char *)SP; for (i=0; i<MAX_TASKS; i++) { rtos_tick[i] = 0; *++sp = (unsigned int)task_func[i] & 0xff; *++sp = (unsigned int)task_func[i] >> 8; rtos_stack[i] = (unsigned char)sp + PUSH_SIZE; sp = sp + size - 2; } rtos_stack[MAX_TASKS] = (unsigned char)sp; // variable init rtos_task_id = MAX_TASKS; SP = rtos_stack[MAX_TASKS]; // timer0 init TMOD = (TMOD & 0xF0) | 0x01; TH0 = 0; TL0 = 0; TF0 = LOW; ET0 = HIGH; TR0 = HIGH; // jump to idle task rtos_idle(); }

rtos_idle

  1. rtos_idle 裏其實不沒做什麼事,就是一直進入 idle 模式以利省電。
  2. rtos_idle 裏不可以呼叫任何函數,因為保留給它的 stack 空間只夠做 task 切換而已,容不下其它的 push 指令。
static void rtos_idle(void) { while (1) { PCON |= 1; } }

rtos_nexttask

  1. rtos_nexttask 新增了計數功能,用來計算每一個 task delay 時間,當 rtos_tick==0 時,該 task 才擁有執行時間。
  2. 如果所有 task 都處於閒置狀態,就進入 idle task 等待其它 task 被喚醒。
  3. rtos_nexttask 裏不可以呼叫任何函數,因為如果遇到從 rots_idle 進入的 stack,它的 stack 空間只夠做 task 切換而已,容不下其它的 push 指令。
void rtos_nexttask(void) __naked { unsigned char i; _asm clr ea _endasm; // save current stack rtos_stack[rtos_task_id] = SP; // check tick for (i=0; i<MAX_TASKS; i++) { if (rtos_tick[i] > 0) rtos_tick[i]--; } // load next stack for (i=0; i<MAX_TASKS; i++) { if (++rtos_task_id >= MAX_TASKS) rtos_task_id = 0; if (rtos_tick[rtos_task_id] == 0) break; } if (i == MAX_TASKS) rtos_task_id = MAX_TASKS; SP = rtos_stack[rtos_task_id]; // restore register _asm pop _bp pop ar7 pop ar6 pop ar5 pop ar4 pop ar3 pop ar2 pop ar1 pop ar0 pop psw pop dpl pop dph pop b pop acc setb ea ret _endasm; }

執行結果

ABCDEFGHIJKLMNOPabcdefghijklmnopqrstuvwxyzQRSTUVWXYZ0123456789(0)SP = '1A' (1)SP = '53' (2)SP = '8C' (3)SP = 'C5' (4)SP = 'FE' abcdefghijklmnopABCDEFGHIJKLMNOPQRSTUVWXYZqrstuvwxyz0123456789(0)SP = '1A' (1)SP = '53' (2)SP = '8C' (3)SP = 'C5' (4)SP = 'FE' ABCDEFGHIJKLMNOPabcdefghijklmnopqrstuvwxyzQRSTUVWXYZ0123456789(0)SP = '1A' (1)SP = '53' (2)SP = '8C' (3)SP = 'C5' (4)SP = 'FE'

extern unsigned char *rtos_stack 跟 extern unsigned char rtos_stack[] 的差異

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

extern __xdata unsigned char *rtos_stack;

;../source/test.c:66: printf_tiny("(%d)SP = '%x'\n\r", i, rtos_stack[i]); mov a,r2 add a,_rtos_stack mov dpl,a clr a addc a,(_rtos_stack + 1) mov dph,a movx a,@dptr

extern __xdata unsigned char rtos_stack[];

;../source/test.c:66: printf_tiny("(%d)SP = '%x'\n\r", i, rtos_stack[i]); mov a,r2 add a,#_rtos_stack mov dpl,a clr a addc a,#(_rtos_stack >> 8) mov dph,a movx a,@dptr

原來的變數為 __xdata unsigned char rtos_stack[3],很明顯的在 SDCC 裏 (char *) 不能等同於 (char []),這點要特別注意。

tiny rtos for 8051 using SDCC part 2

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

原始碼下載

前言

使用 SDCC 來寫 rtos 比起用 keil-c 來得方便許多,因為 SDCC 有一個 naked 修飾元。naked function 會省略前置的 push 動作及最後的 pop 及 ret 指令,這些動作都需要使用者自己加入。在 sdccman.pdf 內的說明指出 naked function 主要是用於 interrupt function 來省略一堆無意義的 push/pop 動作,剛好被我用來增加 push/pop 動作(完全相反的立意)。

rtos_start

  1. 其實我寫的 rtos 很簡單,也就是把 stack 分成四份,配給四個 task,讓每個 task 的 stack 各自獨立不互相干擾。
  2. 再利用 timer 0 的中斷處理來切換 task。
  3. rtos_task_id 是記錄目前正在執行的 task 是那一個。
  4. rtos_stack 是記錄每一個 task 的 stack 位置。
  5. 由於在切換 task 時,會 pop 14 個 register 及 task 的執行位置。所以一開始需要把每個 task 的起始位置 push 到堆疊里,並把 stack 起始位置加上 14,在做切換 task 時,才會順利執行相應的函數。
  6. 由於第一個 task 就是主程序,所以不用做 push 動作,因為在第一次切換 task 時,會自動 push,因此才會有 rtos_stack[0] = SP; 的指令。
  7. call task1() 及 call rtos_start() 理論上會浪費 stack 空間才對,但因為被 SDCC 最佳化的關係,二個指令都變成 ljmp task1 及 ljmp rtos_start,並沒有浪費到 stack 空間,請安心使用。
#define PUSH_SIZE 14 void rtos_start(void) { unsigned char i, size; __idata unsigned char *sp; // variable init rtos_task_id = 0; // init stack size = (255 - SP) >> 2; // size = (256 - SP) / MAX_TASKS; sp = (__idata unsigned char *)SP; for (i=0; i<MAX_TASKS; i++) { *++sp = (unsigned int)task_func[i] & 0xff; *++sp = (unsigned int)task_func[i] >> 8; rtos_stack[i] = (unsigned char)sp + PUSH_SIZE; sp = sp + size - 2; } rtos_stack[0] = SP; // timer0 init TMOD = (TMOD & 0xF0) | 0x01; TH0 = 0; TL0 = 0; TF0 = LOW; ET0 = HIGH; TR0 = HIGH; // jump to task 1 task1(); }

rtos_nexttask

  1. rtos_nexttask 的動作很簡單,就是保存現在所有的 register (r0~r7, acc, b, dph, dpl, psw, bp)及目前的 sp 位置,然後變更到下一個 task 的 sp,再還原所有的 register,最後 return 到新 task 的位置(在 rtos_start 時已經記錄到 堆疊里)。
  2. bp 這個東東應該不算 register,這個主要是被用在 reentrant 函數,一個很特殊的全域變數,忘了保存它還會造成程式錯亂喔。
void rtos_nexttask(void) __naked { // 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]; // restore register _asm pop _bp pop ar7 pop ar6 pop ar5 pop ar4 pop ar3 pop ar2 pop ar1 pop ar0 pop psw pop dpl pop dph pop b pop acc ret _endasm; } void rtos_isr(void) __interrupt (1) __naked { // store register _asm push acc push b push dph push dpl push psw push ar0 push ar1 push ar2 push ar3 push ar4 push ar5 push ar6 push ar7 push _bp _endasm; // call rtos_nexttask *(__idata unsigned char *)++SP = (unsigned int)&rtos_nexttask & 0xff; *(__idata unsigned char *)++SP = (unsigned int)&rtos_nexttask >> 8; _asm reti _endasm; }

執行結果

567890123456789012345678901234567890123456SP = 1D rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgSP = 5A QRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGSP = 97 [\]^_`[\]^_`[\]^_`[\]^_`[\]^_`[\]^_`[\]^_`SP = D4 789012345678901234567890123456789012345678SP = 1D hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwSP = 5A HIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWSP = 97 [\]^_`[\]^_`[\]^_`[\]^_`[\]^_`[\]^_`[\]^_`[SP = D4

tiny rtos for 8051 using SDCC part 1

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

注意事項

  1. 由於是多工的系統,所以對於 SDCC 內建的 library 的使用要求就是要有 --stack-auto 的選項。而內建的 library 只有 small-stack-auto,所以要在 sdcc-eclipse 的 SDCC Linker Command 選項加入 sdcc --model-small --stack-auto 才會選用 small-stack-auto library。
  2. 至於 SDCC Compiler Command 要不要加 --stack-auto 都可以,如果沒有加入此參數,則需自己判斷是否為可重入函數,在函數宣告後面加入 __reentrant 關鍵字。
  3. 為了避免 task 之間的 local variable 因為 overlay 的關係互相干擾,所以要在 sdcc-eclipse 的 SDCC Compiler Command 選項加入 --nooverlay 參數。
  4. PUSH 指令是先將 SP 加一,再將直接定址資料放入堆疊空間裏。相反的,POP/RET 指令是先把堆疊空間資料取出放到直接定址空間,再將 SP 減一。
  5. 在 tiny rots 內不要使用 printf,儘可能使用 printf_tiny 代替。因為 printf 會用到 bit variable,造成 stack 空間減少。
  6. 確定會被不同 bank 呼叫的函數才需要加 __banked,否則不用浪費堆疊空間去記錄相同的 PSBANK 值。
  7. 一般的初始函數可以放在 bank0,而且也不用加 __banked,這樣可以把 common area 縮小。
  8. common area 越小,相對的每個 bank 可用的空間越大。
  9. __reentrant 函數的建立要視函數內的變數是否被轉換成 idata 變數,如果區域變數都是使用 register,那麼就不需要加 __reentrant,加了還可以能浪費到 stack 空間。
  10. 要減少函數用到 idata 變數,可以試著關掉編譯器的最佳化功能,比如 #pragma nogcse 等。
  11. ...

reentrant function with _bp

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

由於 8051 單晶片內部的堆疊空間有限,所以無法像 windows/unix 那樣使用堆疊。一般在 C 語言裏,呼叫函數時會將函數參數及函數使用的局部變數放入堆疊。因為 8051 堆疊空間有限,所以無法使用此方式,而是為每個函數的局部變數及參數設定一個空間來存放。比如下面的範例就是如此,參數 a 及 b 被分配到二個全域變數 _test_a_1_1 及 _test_PARM_2。

541 ;------------------------------------------------------------ 542 ;Allocation info for local variables in function 'test' 543 ;------------------------------------------------------------ 544 ;b Allocated with name '_test_PARM_2' 545 ;a Allocated with name '_test_a_1_1' 546 ;------------------------------------------------------------ 547 ; ../source/main.c:60: int test(int a, int b) 548 ; ----------------------------------------- 549 ; function test 550 ; ----------------------------------------- 551 _test: 552 push ar2 553 push ar3 554 push ar4 555 push ar5 556 mov r2,dph 557 mov a,dpl 558 mov dptr,#_test_a_1_1 559 movx @dptr,a 560 inc dptr 561 mov a,r2 562 movx @dptr,a 563 ; ../source/main.c:62: return a + b; 564 mov dptr,#_test_PARM_2 565 movx a,@dptr 566 mov r2,a 567 inc dptr 568 movx a,@dptr 569 mov r3,a 570 mov dptr,#_test_a_1_1 571 movx a,@dptr 572 mov r4,a 573 inc dptr 574 movx a,@dptr 575 mov r5,a 576 mov a,r2 577 add a,r4 578 mov r2,a 579 mov a,r3 580 addc a,r5 581 mov r3,a 582 mov dpl,r2 583 mov dph,r3 584 pop ar5 585 pop ar4 586 pop ar3 587 pop ar2 588 ret

呼叫方式也是直接把傳遞值直接存入全域變數 _test_PARM_2 裏。

607 ; ../source/main.c:74: test(1, 2); 608 mov dptr,#_test_PARM_2 609 mov a,#0x02 610 movx @dptr,a 611 clr a 612 inc dptr 613 movx @dptr,a 614 mov dptr,#0x0001 615 lcall _test

SDCC 把局部變數被放到全域變數裏,相當於被宣告成靜態變數一樣,只是差在沒宣告 static 而已。由於此種原因,造成此類函數變成不可當成遞迴函數來使用,也就是不能自我呼叫函數。而且在 interrupt function 裏不可以呼叫此類函數,因為當中斷發生在此類函數時,interrupt function 又呼叫一次原來函數,會造成局部變數被修改,而造成不可預期的錯誤。所以需要在函數宣告後加上 __reentrant,讓編譯器按照一般 C 語言方式把局部變數都放到堆疊裏。下面的函數就是如此,把局部變數放到堆疊裏(sp-4)。

536 ;------------------------------------------------------------ 537 ;Allocation info for local variables in function 'test' 538 ;------------------------------------------------------------ 539 ;b Allocated to stack - offset -4 540 ;a Allocated to registers r2 r3 541 ;------------------------------------------------------------ 542 ; ../source/main.c:60: int test(int a, int b) __reentrant 543 ; ----------------------------------------- 544 ; function test 545 ; ----------------------------------------- 546 _test: 547 push ar2 548 push ar3 549 push ar0 550 push ar1 551 push _bp 552 mov _bp,sp 553 mov r2,dpl 554 mov r3,dph 555 ; ../source/main.c:62: return a + b; 556 mov a,_bp 557 add a,#0xf8 558 mov r0,a 559 mov a,@r0 560 add a,r2 561 mov r2,a 562 inc r0 563 mov a,@r0 564 addc a,r3 565 mov r3,a 566 mov dpl,r2 567 mov dph,r3 568 pop _bp 569 pop ar1 570 pop ar0 571 pop ar3 572 pop ar2 573 ret

呼叫時,直接把傳遞值放入堆疊裏,而且在呼叫結束也會還原堆疊指針。

592 ; ../source/main.c:74: test(1, 2); 593 mov a,#0x02 594 push acc 595 clr a 596 push acc 597 mov dptr,#0x0001 598 lcall _test 599 dec sp 600 dec sp

各位有沒有在 reentrant function 裏發現一個不名的變數 _bp,用來做堆疊指標的運算,以取得參數的值。而這個 _bp 變數是宣告在 SDCC\lib\src\_bp.c --> __data unsigned char bp;。而且這個變數還被 printf_tiny 及 printf_fast 所使用,當然這二個函數有宣告成 __reentrant 函數,使用 _bp 變數也只是變相利用而已啦。

當指標遇上結構

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個寫法。

二的補數 (電腦負數表示法)

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

二的補數法(2's complement)是電腦中為了儲存及運算有正負號的整數而使用的一種方法,要用二進位表達一個負整數的方法很多,但是二的補數表示法使得負整數與正整數的運算(加、減、及乘法)完全一致,不需要額外的硬體來處理。

範例

以 4 位元二的補數數字來說。例如:1 + (-2) = -1,0001(1) + 1110(-2) = 1111(-1)。你可以發現二的補數的加法只要使用標準的二進位加法就可以了。

2 進位無號整數有號整數
111115-1
111014-2
110113-3
110012-4
101111-5
101010-6
10019-7
10008-8
011177
011066
010155
010044
001133
001022
000111
000000

十進制轉成二進制

以 -3 為例。

  1. 11: 先把數字部分轉換成二進制。
  2. 0011: 依照 bit 數,補齊空缺。
  3. 1100: 使用1的補數,將1變0,0變1。
  4. 1101: 再把1的補數加上1,形成2的補數。

二進制轉成十進制

以 1101 為例。

  1. 1100: 2的補數減1,還原成1的補數。
  2. 0011: 1變0,0變1。
  3. 3: 二進制轉成十進制。
  4. -3: 數字前加上負號。

while (i--) or while (--i)

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

while (i--)

mov r2,#0xC8 mov ar3, r2 dec r2 mov ar6, r2 mov a, r3

while (--i)

mov r2,#0xC8 dec r2 mov a, r2

結論

這樣結果很明顯吧,i-- 比 --i 多用了一個 r6 register,而且也多了二個指令,下次記得寫 while (--i)。

運算子優先順序

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

最近在寫一段程式碼,剛好有去注意它編譯出來的assembly code,發現怎麼沒有預期出來的結果。結果發現卻是運算子優先順序的問題。切記位元運算的優先權為 shift > and > xor > or,之前一直以為三者是一樣大,原來卻不是如此。希望以前寫的產品不要因為如此而出問題啊,阿彌陀佛,善哉善哉。

原始錯誤程式碼 tmp = *(volatile unsigned char __xdata *)(0x8001) | 0x10 & ~0x08; mov dptr,#0x8001 movx a,@dptr mov r2,a orl ar2,#0x10 修改正確程式碼 tmp = (*(volatile unsigned char __xdata *)(0x8001) | 0x10) & ~0x08; mov dptr,#0x8001 movx a,@dptr mov r2,a mov a,#0x10 orl a,r2 anl a,#0xF7

運算子優先順序

優先
順序
評估順序運算子說明

15

由左至右

., [], ()

欄位存取、陣列索引、函式呼叫和運算式群組

14

由右至左

++, --, -, ~, !, delete, new, typeof, void

一元運算子、傳回資料型別、物件建立、未定義的值

13

由左至右

*, /, %

乘法、除法、modulo 除法

12

由左至右

+, -

加法和字串串連、減法

11

由左至右

<<, >>, >>>

位元移位

10

由左至右

<, <=, >, >=, instanceof

小於、小於或等於、大於、大於或等於、instanceof

9

由左至右

==, !=, ===, !==

等號比較、不等比較、嚴格等號比較和嚴格不等比較

8

由左至右

&

位元 AND

7

由左至右

^

位元 XOR

6

由左至右

|

位元 OR

5

由左至右

&&

邏輯 AND

4

由左至右

||

邏輯 OR

3

由右至左

?:

條件式

2

由右至左

=, OP=

指派、複合指派

1

由左至右

, (逗號)

多重評估

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"

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); }