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

一個簡單的 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,有人說這樣不方便,其實用久習慣就好了。不管黑貓白貓,可以抓到老鼠就是好貓。

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

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 也是可以。

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。