# 微處理機與介面設計
###### 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)
實驗結果圖片:

## 實驗九 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第一行中顯示學號,並於第二行中從以下挑選一個喜歡的圖示顯示。

### 程式碼
```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)
實驗結果圖片:

## 實驗十一 步進馬達
### 進階題
新增兩個外部中斷按鈕,一個功能為切換馬達轉動方向(順變逆、逆變順);另一個為切換馬達轉速快慢,馬達使用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)