# 微處理機與介面設計 ###### tags: `8051` `Microprocessor and interface design` ## 實驗一 內部RAM與Flash空間定址與存取 ### 進階題 請利用Keil C51的各種功能,找出問題的答案,總共12題。 #### A. ```CISC= ORG 0 MOV R0, #01H MOV R1, #0FFH SETB RS0 ; switch from RB0 to RB1 MOV R0, 00H MOV A, 01H MOV A, R0 MOV A, 08H MOV A, @R0 CLR RS0 ; switch from RB1 to RB0 MOV A, R0 SJMP $ END ``` (1) 第 6 行,A = 0x FF (2) 第 7 行,A = 0x 00 (3) 第 8 行,A = 0x 00 (4) 第 9 行,A = 0x 01 (5) 第 11 行,A = 0x 01 #### B. ```CISC= ORG 0 MOV A, #11H MOV 0E0H, #22H SJMP $ END ``` (6) 第 2 行,A = 0x11 (7) 承(6),該指令長度為 2 bytes (8) 第 3 行, A = 0x22 (9) 承(8),該指令長度為 3 bytes (10) 承 (7)、(9),為何此兩種指令達成的功能相同,但指令長度卻不一致? ANS: (A)因為兩者根本是不同指令,只是兩種指令達成同樣的效果。 #### C. ```CISC= ORG 0 MOV 20H, #10H SETB 20H.2 MOV 30H, #20H SETB 30H.1 SJMP $ END ``` (11) 為何此段程式碼無法成功編譯? ANS: (C) 因為 30H 位址不是 bit addressable area。 #### D. ```CISC= ORG 0 MOV DPTR, #TABLE START: MOV R7, #04H LOOP: MOV A, R7 MOVC A, @A+DPTR DJNZ R7, LOOP SJMP START TABLE: DB 5BH DB 7CH DB 1CH DB 1CH DB 3FH END ``` (12) 請依照程式碼順序,寫出 A+DPTR 位址在程式記憶體所儲存的數值。 ANS: 3FH,1CH,1CH,7CH,5BH ## 實驗二 GPIO輸出驅動LED及七段顯示器 ### 進階題 修改四合一七段顯示器實驗之程式,使四合一七段顯示器顯示出自己的學號後四碼。 ### 實驗結果 末三碼: 1210 影片連結(https://youtube.com/shorts/K_UOA1faM5Q) ## 實驗三 GPIO輸入 ### 進階題 掃描讀取4x4鍵盤上每一個按壓訊號。除了基礎題中的1~9,還有鍵盤上其他的符號(0、A、b、c、d、E、F,注意大小寫)。若 4x4 鍵盤有「*」和「#」符號,其中「」顯示 E,「#」顯示 F。 ### 程式碼 ```CISC= ORG 00H ; start address is 0 MOV DPTR, #TABLE ; DPTR point to TABLE START: MOV R0, #4 ; 4 LED MOV R1, #0 ; table index MOV R2, #0FEH ; LED drive in(0FEH=11111110B) LOOP: MOV P2, R2 ; select LED MOV A, R1 ; move R1 to A MOVC A, @A+DPTR ; get value from table MOV P1, A ; move value to P1 CALL DELAY ; delay 0.5 ms INC R1 ; increase R1 MOV A, R2 ; move R2 to A RL A ; rotate A MOV R2, A ; move A back to R2 DJNZ R0, LOOP ; decrement R0 until 0 SJMP START ; jump to start DELAY: MOV R3, #250 ; loop 250 times DJNZ R3, $ ; decrement R3 until 0 RET ; return from subroutine TABLE: DB 0F9H ; 1 DB 0A4H ; 2 DB 0F9H ; 1 DB 0C0H ; 0 END ; end of program ``` ### 照片與影片 影片連結(https://youtube.com/shorts/WJQpqDodDKU) ## 實驗四 算術及邏輯運算指令 ### 進階題 在單顆七段顯示器上執行2、3、5的四則運算。 - 按下鍵盤上的A,則執行 3+2=5 的運算,並將和5顯示在七段顯示器上。 - 按下鍵盤上的B,則執行 3-2=1 的運算,並將差1顯示在七段顯示器上。 - 按下鍵盤上的C,則執行 3×2=6 的運算,並將積6顯示在七段顯示器上。 - 按下鍵盤上的D,則執行 5÷2=2…1 的運算,先將商2顯示在七段顯示器上,延遲125毫秒後,顯示餘數1。 注意:請使用加減乘除的指令,並利用查表法找到要顯示的數字,不要直接把答案移入七段顯示器。 ### 程式碼 ```CISC= ORG 0 ; start from 0000H MOV DPTR, #TABLE ; DPTR point to TABLE MOV R5, #2 START: MOV R0, #1 ; initialize typed signal MOV R1, #4 ; set 3 row MOV R2, #11110111B ; set first row SCAN: MOV A, R2 ; move R2 to A MOV P2, A ; input scanned signal JNB P2.7, NUMBERIN ; scan column 1 INC R0 ; add #01 into R0 JNB P2.6, NUMBERIN ; scan column 2 INC R0 ; add #01 into R0 JNB P2.5, NUMBERIN ; scan column 3 INC R0 ; add #01 into R0 JNB P2.4, KEYIN ; scan column 3 RR A ; next row MOV R2, A ; move A to R2 DJNZ R1, SCAN ; scan 3 rows SJMP START ; jump to scan keypad again NUMBERIN: CALL DEBOUNCE ; call debounce function MOV A, R0 ; get typed signal DJNZ R5,NUM_ONE MOV R7,A ;R7 retain operand2 SJMP START NUM_ONE: MOV R6,A SJMP START KEYIN: JNB P2.3, AD JNB P2.2, SU JNB P2.1, MU JNB P2.0, DI AD: MOV A,R6 MOV B,R7 ADDC A,B MOV B,#0AH DIV AB SJMP LIB SU: MOV B,R6 MOV A,R7 SUBB A,B MOV B,#0AH DIV AB SJMP LIB MU: MOV A,R6 MOV B,R7 MUL AB MOV B,#0AH DIV AB SJMP LIB DI: MOV A,R6 MOV B,R7 DIV AB SJMP LIB LIB: CALL DEBOUNCE ; call debounce function MOVC A, @A+DPTR ; according A, get signal from TABLE MOV P1, A CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE MOV A,B MOVC A, @A+DPTR ; according A, get signal from TABLE MOV P1, A MOV R0, #250 DJNZ R0, $ MOV R5,#2 SJMP START DEBOUNCE: MOV R4, #50 ; move 50 times into R4 DE_500_us: MOV R3, #250 ; move 250 times into R3 DJNZ R3, $ ; jump to itself R3 times DJNZ R4, DE_500_us ; delay 25ms RET ; return to main code TABLE: DB 00000011B ; display 0 DB 10011111B ; display 1 DB 00100101B ; display 2 DB 00001101B ; display 3 DB 10011001B ; display 4 DB 01001001B ; display 5 DB 01000001B ; display 6 DB 00011011B ; display 7 DB 00000001B ; display 8 DB 00001001B ; display 9 END ``` ### 實驗結果 本範例以 9 及 7 做加減乘除 影片連結(https://www.youtube.com/shorts/HRwELxSdPEw) ## 實驗五 程式計數器與堆疊 ### 進階題 請達成以下要求: - 當8051通電後,連接於P2的8個LED開始以跑馬燈的方式依序點亮,一次亮兩顆。 範例:O表示亮的LED,X表示暗的。 第零秒 XXXXXXOO 第一秒 XXXXXOOX 第二秒 XXXXOOXX 第七秒 OXXXXXXO 第八秒 XXXXXXOO - 於P1連接上兩顆按鈕,一顆為PUSH功能鈕,另一顆則為POP功能鈕。PUSH鈕的功能為將目前的跑馬燈狀態儲存進堆疊中;POP鈕的功能則為將儲存在堆疊的跑馬燈狀態依序展現出來。 範例:我在以下的燈號狀態下按下PUSH鈕 XXXXOOXX XXXXXXOO XOOXXXXX OXXXXXXO XOOXXXXX 當我按下POP鈕後,跑馬燈會停止,且LED會以以下順序點亮 XOOXXXXX OXXXXXXO XOOXXXXX XXXXXXOO XXXXOOXX Hint: - 本題沒有要求要檢查堆疊的東西是否POP完,因此按下POP鈕後除非RESET 8051,否則LED不會再次進入跑馬燈狀態。 - 因為是使用按鈕,請記得程式中要有DEBOUNCE的功能,以免按下PUSH鈕後多次儲存重複的資料到堆疊。 - 跑馬燈的部分不建議沿用基礎題的程式,除非對基礎題的程式流程十分了解,否則最好自己重寫一個。 - P2的記憶體位址是A0H。 ### 程式碼 ```CISC= ORG 0000h MOV R2, #1H MOV SP, #32h ;SP = #32H LOOP: MOV A, #0xFC ;A = #0xfe MOV P2, A ;P2 = A JMP MARQUEE ;jump into marquee when p1.0 is low JMP LOOP ;infinite loop MARQUEE: MOV R0, #8D ;set the execution times of DELAY1 CALL DEBOUNCE ;call DELAY1 RL A ;left rotate MOV P2, A ;set the value of A into P2 JNB P1.0, PUSH1 JNB P1.1, POP1 DJNZ R0, MARQUEE ;loop back until MARQUEE execute 8 times JMP LOOP ;end of MARQUEE, back to LOOP PUSH1: CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE MOV A,0A0H PUSH 0E0H CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE MOV A,R2 ADDC A,#1D MOV R2,A JMP LOOP POP1: CALL DEBOUNCE CALL DEBOUNCE CALL DEBOUNCE POP 01H MOV P2,01H DJNZ R2, POP1 MOV R2, #1H JMP LOOP DEBOUNCE: MOV R4, #250 ; move 50 times into R4 DE_500_us: MOV R3, #250 ; move 250 times into R3 DJNZ R3, $ ; jump to itself R3 times DJNZ R4, DE_500_us ; delay 25ms RET ; return to main code END ``` ### 實驗結果 影片連結(https://youtube.com/shorts/rU3CbCmlcGk) ## 實驗六 中斷(Interrupt) ### 進階題 請達成以下要求: - 平時為8顆LED同時閃爍。 - 按下INT0的按鈕會執行三圈四顆LED跑馬燈,按下INT1的按鈕會執行兩圈單顆LED跑馬燈。 - 當四顆LED跑馬燈在跑的時候按下INT1需要先去跑完兩圈單顆LED跑馬燈,再回去繼續跑完四顆LED跑馬燈,而跑完四顆LED跑馬燈會八顆LED同時閃爍。 - 同時按下INT0和INT1會執行兩圈單顆LED跑馬燈。 - INT0與INT1皆使用低準位觸發。 範例: 單顆LED跑馬燈示意圖: XXXXXXXO XXXXXXOX XXXXXOXX XXXXOXXX XXXOXXXX XXOXXXXX XOXXXXXX OXXXXXXX 四顆LED跑馬燈示意圖: XXXXOOOO XXXOOOOX XXOOOOXX XOOOOXXX OOOOXXXX OOOXXXXO ### 程式碼 ```CISC= ORG 00H ;code start from 00H SJMP MAIN ;jump to MAIN ORG 03H ;vector address forINT0 SJMP INT0_ISR ;jump to INT0_ISR ORG 13H ;vector address forINT0 SJMP INT1_ISR ;jump to INT0_ISR ORG 030H ;after vector table space MAIN: MOV IE,#10000101B ;enable EA and EX0 MOV SP,#30H ;stack start from #30H CLR IT0 ;falling edge-triggered CLR IT1 ;falling edge-triggered SETB IP.2 MOV A, #00000000B ;set ACC as0000000B LOOP: MOV P2,A ;P1 = A (LED output) CALL DELAY ;call delay function CPL A ;reverse A SJMP LOOP ;infinite loop INT0_ISR: PUSH PSW ;push PSW into stack PUSH 0E0H ;push ACC into stack CLR RS1 SETB RS0 ;switch to RB1 MOV A, #11110000B ;set ACC as 11111110B MOV R0,#24 ;loop counter = 24 ROTATE_1: MOV P2,A ;P2 = A (LED output) CALL DELAY ;call delay function RL A ;rotate left DJNZ R0,ROTATE_1 ;loop until R0 is 0 POP 0E0H ;pop out ACC fromstack POP PSW ;pop out PSW fromstack RETI ;return from ISR INT1_ISR: PUSH PSW ;push PSW into stack PUSH 0E0H ;push ACC into stack SETB RS1 CLR RS0 ;switch to RB2 MOV A, #11111110B ;set ACC as 11111110B MOV R0,#16 ;loop counter = 24 ROTATE_2: MOV P2,A ;P2 = A (LED output) CALL DELAY ;call delay function RL A ;rotate left DJNZ R0,ROTATE_2 ;loop until R0 is 0 POP 0E0H ;pop out ACC fromstack POP PSW ;pop out PSW fromstack RETI ;return from ISR DELAY: MOV R7,#200 D1: MOV R6,#250 DJNZ R6, $ DJNZ R7,D1 RET ;return END ;end the code ``` ### 實驗結果 說名: (1)測試INT0 (2)測試INT1 (3)測試INT1中斷INT0 (4) 測試兩者同時按 影片連結(https://youtube.com/shorts/8Pe1BHZpI94) ## 實驗七 計時/計數器 ### 進階題 使8051通電後點亮P2.0的LED,並設計一顆按鈕,當按住它之後,每兩秒鐘會使點亮的LED向左平移,如同實驗五的跑馬燈,只是間隔時間變成兩秒,且只有按住按鈕時會跑動。 Hint:Timer請使用mode 1,並設定其為外部啟動(GATE = 1)。 ```C= #include <regx51.h> // include header file for 8051 #define TH0_init (15536/256) //TH0_init= 256 - 250 #define TL0_init (15536%256) //TL0_init = 256 - 250 #define Timer0_int_exe_time 10 //the parameter can be changed int counter; void main(){ TCON = 0x10; TMOD = 0x09; //set timer0 to mode 2(8-bits and autoo-reload) IE = 0x82; //enable timer0 interrupt1 P2 = 0xFE; P3_2 = 0; TL0 = TL0_init; //set TH0 & TL0 TH0 = TH0_init; while(1){ } } void timer0_interrupt(void)interrupt 1{ //"interrupt 1" is intvector of INT0 counter++; if(counter == Timer0_int_exe_time){ //250clock cycle * 2000 = 0.5 second int temp; if(P2_7 == 0){ P2 = 0xFE; }else{ temp = ~P2; temp = temp<<1; P2 = ~temp; } counter =0; } } ``` ### 實驗結果 影片連結(https://youtube.com/shorts/n6lkXaaBch4) ## 實驗八 UART傳輸 ### 進階題 利用終端機軟體輸入學號並透過UART傳送給8051,8051接收資料後判斷學號是否為正確,最後傳送判斷結果給PUTTY終端機呈現結果,正確回傳correct,錯誤則回傳wrong,且每次判定後回傳的字串要換行並從第一個字開始輸出。 HINT:可用傳送數值方法傳送ASCII。 ### 程式碼 ```C= #include <regx51.h> #include <string.h> #include <stdlib.h> void init_uart(); char recivevalue; char number[9]; int count=0; char correct[7] = "correct"; char wrong[5] = "wrong"; void main(){ init_uart(); while(1); } void init_uart(void){ SCON=0x50; TMOD=0x20; TH1=0xe6; TR1=1; IE=0x90; } void init_uart2(void) interrupt 4{ while(RI==0); RI=0; recivevalue=SBUF; P2 = recivevalue; if(count < 9){ number[count] = recivevalue; count = count+1; }else{ int i; recivevalue = 10; SBUF=recivevalue; while(TI==0); TI=0; recivevalue = 13; SBUF=recivevalue; while(TI==0); TI=0; if(strncmp(number,"N96121210",9) != 0){ for(i =0 ;i<5;i++){ recivevalue = wrong[i]; SBUF=recivevalue; while(TI==0); TI=0; } }else{ for(i =0 ;i<7;i++){ recivevalue = correct[i]; SBUF=recivevalue; while(TI==0); TI=0; } } } } ``` ### 實驗結果 可快轉到最後看結果,拍攝有點傷眼... 影片連結(https://youtube.com/shorts/-K2OPD7kcCU) 實驗結果圖片: ![](https://hackmd.io/_uploads/rygw4cvpn.jpg) ## 實驗九 ADC的轉換應用 ### 進階題 使用中斷方法,利用可變電阻輸入電壓分壓給ADC0804轉為數位訊號,並由8051接收後控制8顆LED燈在可變電阻旋轉時從全不亮漸變至全亮(漸亮的時間間隔要平均)。 ### 程式碼 ```C= #include <regx51.h> void main( ){ IT0 = 0; //enable a low-level signal on external interrupt EX0 = 1; //enable INT0 EA = 1; //enable interrupt P3_4 = 0; //WR=0,clean the data P3_4 = 1; //WR=1,analog convert to digital while(1); //infinite loop } void int_0(void)interrupt 0 {//INT0 interrupt function P3_5 = 0; //RD=0,read the digital data if (P1 > 256*8/9 ){ P2 = 0; //LED on }else if (P1 > 256*7/9 ){ P2 = 128; //LED on }else if (P1 > 256*6/9 ){ P2 = 192; //LED on }else if (P1 > 256*5/9 ){ P2 = 224; //LED on }else if (P1 > 256*4/9 ){ P2 = 240; //LED on }else if (P1 > 256*3/9 ){ P2 = 248; //LED on }else if (P1 > 256*2/9 ){ P2 = 252; //LED on }else if (P1 > 256*1/9 ){ P2 = 254; //LED on }else{ P2 = 255; //LED off } P3_5 = 1; //RD=1 P3_4 = 0; //WR=0,clean the data P3_4 = 1; //WR=1,analog convert to digital } ``` ### 實驗結果 影片連結(https://youtube.com/shorts/aF-mLapaxnU) ## 實驗十 LCD顯示器 ### 進階題 LCD第一行中顯示學號,並於第二行中從以下挑選一個喜歡的圖示顯示。 ![](https://i.imgur.com/9XwZ0zX.png) ### 程式碼 ```C= #include <regx51.h> void print_msg(char *); void write(char, int); void delay(unsigned int); void main() { write(0x38, 0); //00111000 write(0x0F, 0); //00001111 write(0x06, 0); //00000110 write(0x01, 0); //00000001 write(0x80, 0); //10000000 print_msg("N9612121"); write('0',1); write(0x40,0); //ROW1 write(0x0A,1); write(0x41,0); //ROW2 write(0x0A,1); write(0x42,0); //ROW3 write(0x1F,1); write(0x43,0); //ROW4 write(0x15,1); write(0x44,0); //ROW5 write(0x1B,1); write(0x45,0); //ROW6 write(0x1F,1); write(0x46,0); //ROW7 write(0x00,1); write(0xC0, 0); //10000000 write(0x00,1); //write icon while (1); } void print_msg(char *msg){ for (;*msg!='\0'; msg++){ write(*msg,1); } } void write(char cmd, int rs_value){ P1 = cmd; //D0-D7 P3_0= rs_value; //RS P3_1=1; // write 0.1ms delay(100); P3_1=0; } void delay(unsigned int i){ while (i--); } ``` ### 實驗結果 影片連結(https://youtube.com/shorts/2cCCuvxJUp4) 實驗結果圖片: ![](https://hackmd.io/_uploads/By9OIshTn.jpg) ## 實驗十一 步進馬達 ### 進階題 新增兩個外部中斷按鈕,一個功能為切換馬達轉動方向(順變逆、逆變順);另一個為切換馬達轉速快慢,馬達使用1.5相驅動。 ### 實驗結果 ## 實驗十二 繼電器與光耦合器 ### 基礎題 ### 程式碼 ```C= #include <regx51.h> void delay(unsigned int); void main(){ while(1){ P2_0 = 0; delay(50000); P2_0 = 1; delay(50000); } } void delay(unsigned int t){ while(t--); } ``` ### 實驗結果 影片連結(https://youtube.com/shorts/06pXcWZKoLo)