[ SDCC For 8051 ] 05-UART

Morgan Ting
閱益如美
Published in
17 min readOct 8, 2023
Photo by Frank Leuderalbert on Unsplash

Universal Asynchronous Receiver / Transmitter, UART 通用非同步接收傳輸是一種常見的通訊協定屬於串列傳輸 ( Serial Transmission) ,8051 內建一組全雙工 ( Full-Duplex ) 的 UART 介面。

本文章將介紹 8051 的 UART 並使用 VScode 與 Embedded IDE 以及 SDCC 搭建環境建立專案學習如何使用 8051 的 UART 介面。

UART 非同步串列傳輸

Universal Synchronous Asynchronous Receiver Transmitter, UART 是一種以串列資料傳輸介面也是一種標準電腦傳輸介面,常見的是 RS-232 、RS-485 等廣泛用在電腦與工業傳輸。

因為微處理器通常運作於 3 ~ 5 v 不利於長距離傳輸,因此通常會經過電位轉換提高電壓準位藉此提高傳輸距離與抵抗干擾。

UART 的資料框 ( Data Frame ) 包含一個起始位元 ( 低電位 ) 與資料位元以及結束位元 ( 高電位 ),資料位元基本是 8 位元因此一個資料框最精簡需要 1+8+1 共 10 位元,為了傳送 8 位元資料需要傳送 10 位元似乎很浪費時間若資料包含同位元 ( Parity bit ) 那麼整體資料框長度就會更大,但是串列傳輸的好處是硬體開銷較小以 UART 而言只需要一條資料傳送線 TxD 與一條資料接收線 RxD 若非傳輸大量資料仍是很經濟的做法。

回到目錄

8051 的 UART

8051 內建一組支援全雙工的 UART 傳輸介面其傳輸鮑率 ( Baud rate ) 與資料長度可以調整,使用者能依據需求進行合適設定。

8051 的 UART 資料傳送腳 TxD 位於 P3_1 腳位,資料接收腳 RxD 位於 P3_0 ,傳送與接收皆有緩衝器 Serail Port Buffer, SBUF 提供暫存空間,操作時須留意傳送與接收時機。

回到目錄

相關暫存器

8051 的 UART 需要相關暫存器搭配才能使用,各暫存器與功能介紹如下。

中斷致能暫存器
  • EA : 中斷總開關。EA=1 啟動中斷功能,EA=0 關閉中斷功能。
  • ES :串列埠中斷開關。ES=1 啟動串列埠中斷,ES=0關閉串列埠中斷。
  • ET1 :Timer 1 中斷開關。ET1 =1 啟動 Timer 1 中斷, ET1=0 關閉 Timer 1 中斷。
  • EX1 :INT 1外部中斷開關。EX1=1 啟動INT_1 外部中斷, EX1=0 關閉INT_1 外部中斷。
  • ET0 :Timer 0 中斷開關。ET0 =1 啟動 Timer 0 中斷, ET0=0 關閉 Timer 0 中斷。
  • EX0 :INT 0外部中斷開關。EX0=1 啟動INT_0 外部中斷, EX0=0 關閉INT_0 外部中斷。

中斷優先暫存器可以設定各個中斷的優先權,設置 1 表示高優先權,設置 0 表示低優先權。

中斷優先暫存器
  • PS : 串列埠中斷優先權。PS=1 高優先權,PS=0 低優先權。
  • PT1 : Timer 1 中斷優先權。PT1=1 高優先權,PT1=0 低優先權。
  • PX1 :INT 1 外部中斷優先權。PX1=1 高優先權,PX1=0 低優先權。
  • PT0 : Timer 0 中斷優先權。PT0=1 高優先權,PT0=0 低優先權。
  • PX0 :INT 0 外部中斷優先權。PX0=1 高優先權,PX0=0 低優先權。
計時/計數 控制暫存器

啟用 UART 時會借助 Timer_1 產生時序,TCON 暫存器在此只會用到 TF1 與 TR1。

  • TF1 :TF1 中斷旗標。Timer 1 觸發中斷時自動設置 1,執行完中斷函式時自動清零。
  • TR1 :TR1 啟動開關。TR1 設置 1 時啟動 Timer 1 , TR1 設置 0 時關閉 Timer 1。
串列埠控制暫存器

SM0 與 SM1 構成傳輸模式,共有四種如下說明。

串列傳輸模式

通常使用模式 1進行 8 位元資料傳輸。

  • SM2 :多重處理器通訊致能。工作於模式 0 時 SM2 設為 0,工作於模式 1 若 SM2為 1 則收到正確的停止位元才會使 RI 設為 1,工作於模式 2 與模式 3 時,若 SM2 為 1 則收到第 9 位元時 ( RB8 = 1 ) 才會使 RI 設為 1。
  • REN: Receive Enable 接收致能。REN 為 1 時可以接收資料,REN 為 0 時停止接收資料。
  • TB8 : 工作於模式 2 與模式 3 時,待傳送的資料第 9 位元存放此處。
  • RB8 : 工作於模式 2 與模式 3 時,接收的資料第 9 位元存放此處。
  • TI : 傳送資料中斷旗標。完成傳送一筆 8 位元或 9 位元資料時該旗標設置 1 並觸發中斷,執行完中斷服務常式後該旗標需軟體設置 0 ,不會自動設置回 0。
  • RI : 接收資料中斷旗標。完成接收一筆 8 位元或 9 位元資料時該旗標設置 1 並觸發中斷,執行完中斷服務常式後該旗標需軟體設置 0 ,不會自動設置回 0。
電源控制暫存器

SMOD: Double baud rate bit 鮑率加倍。SMOD 設置 1 會使傳輸鮑率乘 2 ,可用於模式 1 ~ 3。

回到目錄

四種傳輸模式

一、模式 0 ,SM0=0,SM1=0。

此模式作為移位暫存器通常用於擴充 I / O 埠。半雙工傳輸,資料由 RxD 腳位 ( P3_0 ) 負責傳輸與接收,TxD 腳位 ( P3_1 ) 則是提供移位暫存器時脈來源,其時脈頻率為震盪器時脈的 1/12,即 Fosc / 12 。傳輸資料時由資料的最低位元 LSB 先傳送,且每筆資料長度固定為 8 位元。

二、模式 1 ,SM0=0,SM1=1。

一般以此模式進行料傳輸,其傳輸鮑率可以調整且為全雙工模式。其資料框 ( Data Frame )

如下圖所示,由一個起始位元與 8 位元資料以及一個結束位元組成,共有 10 位元。

資料由 RxD ( P3_0 ) 腳位接收 TxD ( P3_1 ) 腳位傳送。

Data Frame — Mode 1

Baud Rate 鮑率計算:

工作於模式 1 時由 Timer_1 決定傳輸鮑率通常會使 Timer_1工作於模式 2 ,由於 Timer_1 模式 2 會自動加載計時器因此會自動產生時脈提供給 UART 使用。

設定 Timer_1 的 TH1 與 TL1暫存器初始值能獲得鮑率,其計算公式為

模式一公式

Fosc 為震盪器頻率,Baud Rate 為傳輸鮑率。

例如:使用 12 MHz 震盪器,傳輸率設為 9600 bps 則 TH1 如何設定 ?

模式一計算

如果把 PCON 暫存器的 SMOD 設置為 1 則頻率會乘以 2 ,計算公式成為

SMOD 加倍

相同例子計算結果為

SMOD 加倍計算

三、模式 2 ,SM0=1,SM1=0。

模式 2 運作方式與模式 1 雷同,不同點在於模式 2 資料為 9 位元加上起始位元與停止位元完整的資料框為 11 位元。

Data Frame — Mode 2

傳送資料時第 9 位元存放於 SCON 暫存器的 TB8 ,接收資料時第 9 位元存放於 RB8,第 9 位元可作為同位元 ( Parity bit ) 檢查位元對傳輸資料做檢查。

模式 2 的傳輸鮑率只有以下兩種設定。

Mode 2 公式

四、模式 3 ,SM0=1,SM1=1。

模式 3 與模式 2 雷同,資料為 9 位元加上起始位元與停止位元完整的資料框為 11 位元。

與模式 2 不同點是傳輸鮑率可以設定其計算方式與模式 1 相同。

鮑率設定參考表

依公式帶入計算 TH1,其中 12 MHz 因無法整除會產生些微誤差應用時取誤差較小的。

設定參考表
回到目錄

電路圖

實驗電路圖
回到目錄

流程圖

發送流程圖
接收流程圖
回到目錄

新建發送端專案

本次 UART 實驗使用兩組 89C51 微處理器,其一作為發送端每隔一段時間發送資料,另一組作為接收端採用中斷形式接收到資料後將對應資料輸出至 Port 1 以點亮 LED 燈。

在 Embedded IDE 中點擊 New Project,選擇 Internal Template 內建模板

,接著選擇 89C52 SDCC Quick start 。

新建專案
ㄒ選擇模板
選擇 89C52 SDCC Quick start

之後輸入專案名稱,此為發送端專案命名為 UART_Transmitter,輸入完成後按下 Enter 鍵並選擇存放資料夾即完成專案的建立。

發送端專案命名
回到目錄

發送端程式撰寫與編譯

首先引入標頭檔案 < lint.h > 與 < 8051.h >。

#include <lint.h>
#include <8051.h>

設立一個巨集定義震盪器頻率用來計算鮑率。這裡使用 12 MHz 震盪器。

#define Fosc 12000000

宣告資料發送陣列

char data_array[4] = {0,1,2,3};

宣告延遲函數

void delay(unsigned int t)
{
while (t>0) t--;
}

宣告 UART 初始化函數

void UART_init(unsigned int baudrate)
{
TMOD = 0x20; // Timer_1 Mode 2
TH1 = 256-(int)((Fosc/384)/baudrate);
TL1 = TH1;
SCON = 0x40; // SM0=0,SM1=1 Mode 1 , 10 bit
TR1 = 1; // start Timer
}

主程式 main 做初始化,之後每隔一段時間發送資料陣列內容。

void main(void)
{
P1 = 0xFF;
unsigned int index = 0;
UART_init(9600); // 設定傳輸鮑率 9600 bps

while(1)
{
for(index=0;index<4;index++)
{
SBUF = data_array[index]; // 放入資料至傳輸緩衝暫存器
while(TI==0); // 等待傳輸完成
TI = 0; // 清零傳輸完成旗標
delay(20000);
}
}
}

程式編譯

在 Embedded IDE 畫面按下編譯鈕即可進行編譯。

編譯發送端程式碼

編譯完成後終端機會提示按下任何鍵退出。完成編譯。

發送端程式碼編譯完成

在專案資料夾 build \ Debug 內找到附檔名為 hex 即為完成編譯的燒錄檔。

發送端 16 進位檔
回到目錄

新建接收端專案

在 Embedded IDE 中點擊 New Project,選擇 Internal Template 內建模板

,接著選擇 89C52 SDCC Quick start 。

新建接收端專案
選擇模板
選擇 89C52 SDCC Quick start

之後輸入專案名稱,此為發送端專案命名為 UART_Receiver,輸入完成後按下 Enter 鍵並選擇存放資料夾即完成專案的建立。

接收端專案命名
回到目錄

接收端程式撰寫與編譯

首先引入標頭檔案 < lint.h > 與 < 8051.h >。

#include <lint.h>
#include <8051.h>

設立一個巨集定義震盪器頻率用來計算鮑率。這裡使用 12 MHz 震盪器。

#define Fosc 12000000

宣告資料陣列用來點亮 LED 燈。

char LED_tab[4] = {0x01,0x02,0x04,0x08};

定義 UART 初始化函數用來設定 UART 操作模式。

void UART_init(unsigned int baudrate)
{
TMOD = 0x20; // Timer_1 Mode 2
TH1 = 256-(int)((Fosc/384)/baudrate);
TL1 = TH1;
TR1 = 1; // 啟動 Timer_1

SCON = 0x50; // SM0=0,SM1=1 Mode 1 , 10 bit. REN=1 Enable receive data
ES = 1; // 致能串列埠中斷
EA = 1; // 啟動中斷
}

定義 UART 中斷服務常式 ISR,觸發中斷時判別是否為接收中斷,將接收中斷旗標 RI 清零後取出接收資料作為陣列索引值取出 LED_tab 陣列對應資料後送出到 P1 點亮 LED 燈。

因為發送端每隔一段時間會依序發送 0,1,2,3 等資料,將此資料作為索引值取出 LED_tab陣列內容,依序會取出 0x01,0x02,0x04,0x08,輸出至 P1 會依序點亮 4 顆 LED 燈。

void UART_ISR(void) __interrupt(4)
{
if(RI==1) // 是否為接收產生中斷
{
RI = 0; // 清零接收完成旗標
P1 = ~LED_tab[SBUF]; // 取出 SBUF 接收緩衝器內容, 查表輸出至 P1 點亮 LED 燈
}
}

main 主程式做初始化動作後隨即進入空迴圈,發生中斷時才跳至中斷服務常式執行。

void main(void)
{
UART_init(9600); // 初始化 UART 設定鮑率為 9600 bps
P1 = 0xFF; // 初始化 Port 1,關閉 LED 燈
while(1);
}

程式編譯

在 Embedded IDE 畫面按下編譯鈕即可進行編譯。

編譯接收端程式碼

編譯完成後終端機會提示按下任何鍵退出。完成編譯。

完成接收端程式碼編譯

在專案資料夾 build \ Debug 內找到附檔名為 hex 即為完成編譯的燒錄檔。

接收端 16 進位檔
回到目錄

完整程式碼-發送端

/* UART 傳送端,不斷送出資料 */
#include <lint.h>
#include <8051.h>

#define Fosc 12000000

char data_array[4] = {0,1,2,3};

void delay(unsigned int t)
{
while (t>0) t--;
}

void UART_init(unsigned int baudrate)
{
TMOD = 0x20; // Timer_1 Mode 2
TH1 = 256-(int)((Fosc/384)/baudrate);
TL1 = TH1;
SCON = 0x40; // SM0=0,SM1=1 Mode 1 , 10 bit
TR1 = 1; // start Timer
}



void main(void)
{
P1 = 0xFF;
unsigned int index = 0;
UART_init(9600);

while(1)
{
for(index=0;index<4;index++)
{
SBUF = data_array[index]; // 放入資料至傳輸緩衝暫存器
while(TI==0); // 等待傳輸完成
TI = 0; // 清零傳輸完成旗標
delay(20000);
}
}
}
回到目錄

完整程式碼-接收端

/* UART 啟用中斷接收資料*/
#include <lint.h>
#include <8051.h>

#define Fosc 12000000
char LED_tab[4] = {0x01,0x02,0x04,0x08};

void UART_init(unsigned int baudrate)
{
TMOD = 0x20; // Timer_1 Mode 2
TH1 = 256-(int)((Fosc/384)/baudrate);
TL1 = TH1;
TR1 = 1; // 啟動 Timer_1

SCON = 0x50; // SM0=0,SM1=1 Mode 1 , 10 bit. REN=1 Enable receive data
ES = 1; // 致能串列埠中斷
EA = 1; // 啟動中斷
}

void UART_ISR(void) __interrupt(4)
{
if(RI==1) // 是否為接收產生中斷
{
RI = 0; // 清零接收完成旗標
P1 = ~LED_tab[SBUF]; // 取出 SBUF 接收緩衝器內容, 查表輸出至 P1 點亮 LED 燈
}
}

void main(void)
{
UART_init(9600); // 初始化 UART 設定鮑率為 9600 bps
P1 = 0xFF; // 初始化 Port 1,關閉 LED 燈
while(1);
}
回到目錄

成果展示

影片中麵包板上半部為接收端,下半部為發送端。

成果
回到目錄

總結

8051 內鍵一組非同步式串列傳輸 Universal Synchronous Asynchronous Receiver Transmitter, UART,總結如下:

  • 8051 的 UART 時脈由 Timer_1 提供,Timer_1 工作於能自動加載的模式 2 。
  • 8051 震盪器採用 11.0592 MHz 可以得到較為精確鮑率。
  • UART 模式0,SM0 = 0,SM1 = 0。此模式一般作為移位暫存器使用。
  • UART 模式1,SM0 = 0,SM1 = 1。8 位元傳輸,若含起始位元與停止位元共 10 位元。
  • 模式 1 正常頻率 ( SMOD=0 ) TH1 = 256-((震盪器頻率 / 384) / 鮑率 )。
  • 模式 1 雙倍頻率 ( SMOD=1 ) TH1 = 256-((震盪器頻率 / 192) / 鮑率 )。
  • UART 模式 2,SM0 = 1,SM1 = 0。9 位元傳輸,若含起始位元與停止位元共11位元。
  • 模式 2 正常頻率 ( SMOD=0 ) TH1 = 1 / 64 ,雙倍頻率 ( SMOD=1 ) TH1 = 1 / 32。
  • UART 模式 3,SM0 = 1,SM1 = 1。9位元傳輸,若含起始位元與停止位元共 11 位元。
  • 模式 3 TH1 設定同模式 1。
  • 傳送與接收各擁有一個 8 位元獨立緩衝器 SBUF。
  • UART 中斷服務常式編號為 4 無論是傳送或接收皆可觸發中斷,須以 TI 與 RI 旗標判斷。
  • TI 旗標,發送資料完成時會自動設置 1 ,需在軟體內清零。
  • RI 旗標,接收資料完成時會自動設置 1 ,需在軟體內清零。SBUF 內容須及時讀出否則會被新資料覆蓋。
回到目錄

參考資源

感謝讀者

若文章有幫助到您可以拍手給我鼓勵。

--

--

Morgan Ting
閱益如美

用好奇心探索世界。喜愛學習樂於分享。