現(xiàn)在來詳細看一下寄存器,我們直接查看單片機手冊。
SCON寄存器
先來說說SCON寄存器。
前一節(jié)我們提過,我們一般使用串口用的是模式1,即8位UART,這樣我們就用不到校驗位。從手冊中可以看到,寄存器SCON中的SM0和SM1配置成01即可。
SM2寄存器明顯用不到,因為我們沒有用模式2和3.
REN寄存器控制接收串行,發(fā)送數(shù)據(jù)時候置0,接收數(shù)據(jù)時置1。
TB8和RB8同SM2,一樣用不到。
TI就比較關(guān)鍵了。我們肯定會用到。從串口結(jié)構(gòu)圖中可以看到,TI是一個標志位,來判斷發(fā)送是否結(jié)束。舉個例子,發(fā)送數(shù)據(jù)就是全自動步槍,TI寄存器就是我們的槍栓。我們發(fā)送結(jié)束后,TI的值會自動置1,我們需要手動寫程序在軟件層面給TI置0,讓它能夠再次使用。
這里插一句,軟件層面指的就是我們的代碼工程,硬件層面就是我們的板子和電路。
那么RI和TI的原理是類似的,就不詳細說了。我們在初始化配置的時候,一般兩位都置0,讓它能夠直接使用。
發(fā)送數(shù)據(jù)時,代碼如下:
void UART_Init()
{
SCON = 0x40;//0100 0000
}
接收數(shù)據(jù)時,代碼如下:
void UART_Init()
{
SCON = 0x50;//010 0000
}
PCON寄存器
再來說說PCON寄存器。
SMOD選擇波特率是否加倍。我們對晶振頻率進行過分頻,所以波特率是需要加倍的。
定時器寄存器
除了串口寄存器,我們還需要用到定時器,打開我們上一章定時器的.c文件(不用打開工程,用記事本方式以文本文件方式打開即可)
復制出定時器的初始化代碼。
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //設置定時器模式
TMOD |= 0x01; //設置定時器模式
TL0 = 0x18; //設置定時初值
TH0 = 0xFC; //設置定時初值
TF0 = 0; //清除TF0標志
TR0 = 1; //定時器0開始計時
ET0 = 1;
EA = 1;
PT0 = 0;
}
然而串口所用到的定時器是Timer1,和我們之前用的Timer0是不同的寄存器,所以我們需要修改定時器寄存器的代碼。串口用的定時器模式也不是我們之前的16位定時器模式,而是8位自動重裝載模式。(相比16位定時器模式,這個模式在計數(shù)滿后,高八位的初值數(shù)據(jù)會自動轉(zhuǎn)入低八位)
這里還是詳細說一下吧,想簡單說兩句發(fā)現(xiàn)說不清楚16位定時器高八位和低八位都用來計時,所以計數(shù)范圍會比較大,每次計數(shù)高八位和低八位都置初始值,然后開始計數(shù),計數(shù)滿了我們再手動賦初值。八位重裝載模式只用低八位寄存器來計時,高八位里面存放的是我們的定時器初值,在低八位加滿之后高八位存的數(shù)據(jù)會流入低八位,這樣就不需要我們手動給計時器置初值了
首先在TMOD中我們需要打開定時器1,我們使用邏輯語言 &= 給TMOD賦值。這樣就不會影響我們上一次對TMOD的操作了。
代碼如下:
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0x0F; //設置定時器模式
TMOD |= 0x20; //設置定時器模式
TL0 = ; //設置定時初值
TH0 = ; //設置定時初值
}
軟件生成初始化代碼
初始值的計算太復雜了,我們還是用STC-ISP軟件生成代碼,不用再用手算了。 別的寄存器也通過軟件來生成吧。
這里提一嘴,波特率倍速得勾選,上面講PCON的時候說了原因了。不過實在懶得重新截圖了。具體分析看下面小標題里的內(nèi)容?。?!
那么初始化函數(shù)里面的內(nèi)容如下
void UartInit(void) //4800bps@12.000MHz
{
PCON &= 0x80; //波特率不倍速
SCON = 0x40; //8位數(shù)據(jù),可變波特率
// AUXR &= 0xBF; //定時器1時鐘為Fosc/12,即12T
// AUXR &= 0xFE; //串口1選擇定時器1為波特率發(fā)生器
TMOD &= 0x0F; //清除定時器1模式位
TMOD |= 0x20; //設定定時器1為8位自動重裝方式
TL1 = 0xF3; //設定定時初值
TH1 = 0xF3; //設定定時器重裝值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
}
波特率計算
現(xiàn)在來說一下如何計算TL和TH。
0xF3就是十進制的243,而定時器每隔256溢出一次。我們定時器重裝值設為243,那么計數(shù)13次后定時器就溢出了(定時器那里講過,忘記了可以翻看以前的博客)。
12M的晶振,在12T模式下,每隔1us計數(shù)一次(因為12分頻了嘛,很好理解的對不對)。
那么定時器Timer1的溢出率就是1/(13us),即0.07692MHz,假如我們倍速波特率,那么SMOD的開關(guān)會走上面的路,上面的頻率需要再除以16,進入接收控制器,即0.07692MHz/16=4807.69Hz,這就是我們所設置的波特率4800。、
同理,假如我們不勾選倍速波特率,SMOD開關(guān)會走下面除以2的路,相當于頻率除以32。軟件幫我們計算出來的TH和TL值是F9,即十進制249,即7us溢出一次,溢出率1/(7us)=0.14285MHz,再除以32,進入接收控制器,即0.14285MHz/32=4464.28Hz,此時誤差就比較大了。
發(fā)送數(shù)據(jù)
我們需要將數(shù)據(jù)寫進SBUF寄存器里,這樣在配置好寄存器后,數(shù)據(jù)會直接被發(fā)送出。
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//將數(shù)據(jù)寫進SUBF中
while(TI==0);//發(fā)送完成標志位一旦變?yōu)?,說明數(shù)據(jù)發(fā)送成功了。接下來軟件復位。
TI = 0;
}
主文件里面的內(nèi)容如下(其實Delay和Timer0沒必要導入,因為沒用到):
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
void UartInit(void) //4800bps@12.000MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x40; //8位數(shù)據(jù),可變波特率
// AUXR &= 0xBF; //定時器1時鐘為Fosc/12,即12T
// AUXR &= 0xFE; //串口1選擇定時器1為波特率發(fā)生器
TMOD &= 0x0F; //清除定時器1模式位
TMOD |= 0x20; //設定定時器1為8位自動重裝方式
TL1 = 0xF3; //設定定時初值
TH1 = 0xF3; //設定定時器重裝值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
}
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//將數(shù)據(jù)寫進SUBF中
while(TI==0);//發(fā)送完成標志位一旦變?yōu)?,說明數(shù)據(jù)發(fā)送成功了。接下來軟件復位。
TI = 0;
}
void main()
{
UartInit();
UART_SendByte(0x11);
while(1)
{
}
}
燒錄程序后,連續(xù)按下復位鍵,可以看到,串口不斷接收到字符11。
當然,我們也可以把UART_SendByte函數(shù)寫在while(1)里面,這樣就能通過串口持續(xù)收到11。但是此時我們需要再發(fā)送數(shù)據(jù)之后加上一定的延時,不然因為晶振頻率和波特率的誤差,會導致接受數(shù)據(jù)發(fā)生錯誤。
串口模塊化
現(xiàn)在我們通過實驗驗證了串口發(fā)送接收數(shù)據(jù)的可行性,那么按照前面學習的內(nèi)容,就可以對串口進行模塊化了。具體操作不細說,直接上內(nèi)容(別忘了加上注釋):
//UART.c
#include <REGX52.H>
/**
* @brief 串口初始化4800bps,@12.000MHz
* @param 無
* @retval 無
*/
void UartInit(void)
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x40; //8位數(shù)據(jù),可變波特率
// AUXR &= 0xBF; //定時器1時鐘為Fosc/12,即12T
// AUXR &= 0xFE; //串口1選擇定時器1為波特率發(fā)生器
TMOD &= 0x0F; //清除定時器1模式位
TMOD |= 0x20; //設定定時器1為8位自動重裝方式
TL1 = 0xF3; //設定定時初值
TH1 = 0xF3; //設定定時器重裝值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
}
/**
* @brief 串口發(fā)送一個字節(jié)數(shù)據(jù)
* @param Byte 要發(fā)送的一個字節(jié)數(shù)據(jù)
* @retval 無
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//將數(shù)據(jù)寫進SUBF中
while(TI==0);//發(fā)送完成標志位一旦變?yōu)?,說明數(shù)據(jù)發(fā)送成功了。接下來軟件復位。
TI = 0;
}
#ifndef __UART_H__
#define __UART_H__
void UartInit(void);
void UART_SendByte(unsigned char Byte);
#endif
此時在主文件內(nèi)直接引用函數(shù)就可以正常使用了。
串口接收數(shù)據(jù)
剛才所講的內(nèi)容是關(guān)于串口向電腦發(fā)送數(shù)據(jù)?,F(xiàn)在再來說一說串口如何接收電腦數(shù)據(jù)。電腦發(fā)送數(shù)據(jù)被串口收到時,我們不能直接處理,必須進入中斷函數(shù)進行處理。否則會影響單片機正常工作。
先打開中斷使能EA,然后ES置1,相當于打開中斷。
只需要在最下面加兩行代碼。同時修改一下SCON,因為要接收數(shù)據(jù),REN需要置1,上面講SCON的時候提到過。
void UartInit(void) //4800bps@12.000MHz
{
PCON &= 0x80; //波特率不倍速
SCON = 0x50; //8位數(shù)據(jù),可變波特率
// AUXR &= 0xBF; //定時器1時鐘為Fosc/12,即12T
// AUXR &= 0xFE; //串口1選擇定時器1為波特率發(fā)生器
TMOD &= 0x0F; //清除定時器1模式位
TMOD |= 0x20; //設定定時器1為8位自動重裝方式
TL1 = 0xF3; //設定定時初值
TH1 = 0xF3; //設定定時器重裝值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
EA = 1;
ES = 1;
}
查詢串口的中斷次序號
我們可以這樣寫,當串口收到電腦發(fā)送的數(shù)據(jù)時,LED燈亮。那么中斷服務子函數(shù)寫法如下:
void UART_Routine() interrupt 4
{
P2 = 0x00;
}
寫好之后,燒錄程序,在STC-ISP上發(fā)送任意一個數(shù)據(jù),可以看到,單片機上的所有LED都亮了。
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
#include "UART.h"
void main()
{
UartInit();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
P2 = 0x00;
}
說明串口接收數(shù)據(jù)可行,現(xiàn)在我們可以發(fā)揮想象力,大展拳腳了。
電腦發(fā)送數(shù)據(jù)控制LED燈
這樣寫:
void UART_Routine() interrupt 4
{
if(RI == 1)
{
P2 = SBUF;
RI = 0;
}
}
因為TI和RI都有可能使程序進入中斷,所以我們要用判斷語句來確認是串口收到了數(shù)據(jù)。
SUBF中保存的是接收和發(fā)送的數(shù)據(jù),我們可以直接提取出來進行處理。
同時類似于TI,我們在接收時也需要進行軟件復位,在確認每次收到數(shù)據(jù)(即RI=1)后,讓RI歸零(RI=0)。
那么上面程序的效果,就是前四個LED滅,后四個LED亮。
注意,在中斷函數(shù)里面的調(diào)用的函數(shù),不能在主函數(shù)里面使用。因為假如主函數(shù)正在調(diào)用函數(shù),進入中斷后你再調(diào)用一次,主函數(shù)中的調(diào)用就會被打斷,那么程序就會出錯。
我們可以讓單片機再通過串口,把接收到的數(shù)據(jù)返回給電腦,只需要用之前寫好的語句,很簡單。
void UART_Routine() interrupt 4
{
if(RI == 1)
{
P2 = SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
將接收中斷模塊化
#include <REGX52.H>
/**
* @brief 串口初始化4800bps,@12.000MHz
* @param 無
* @retval 無
*/
void UartInit(void)
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位數(shù)據(jù),可變波特率
// AUXR &= 0xBF; //定時器1時鐘為Fosc/12,即12T
// AUXR &= 0xFE; //串口1選擇定時器1為波特率發(fā)生器
TMOD &= 0x0F; //清除定時器1模式位
TMOD |= 0x20; //設定定時器1為8位自動重裝方式
TL1 = 0xF3; //設定定時初值
TH1 = 0xF3; //設定定時器重裝值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
EA = 1;
ES = 1;
}
/**
* @brief 串口發(fā)送一個字節(jié)數(shù)據(jù)
* @param Byte 要發(fā)送的一個字節(jié)數(shù)據(jù)
* @retval 無
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//將數(shù)據(jù)寫進SUBF中
while(TI==0);//發(fā)送完成標志位一旦變?yōu)?,說明數(shù)據(jù)發(fā)送成功了。接下來軟件復位。
TI = 0;
}
/*串口中斷函數(shù)模板
void UART_Routine() interrupt 4
{
if(RI == 1)
{
RI = 0;
}
}
*/
以模板的形式加入到我們的模塊里,需要使用的時候,直接挪到主函數(shù)下面就可以了。它和主函數(shù)耦合性還是比較高的,可以直接使用。
數(shù)據(jù)顯示模式
HEX模式/十六進制模式/二進制模式
以原始數(shù)據(jù)的形式顯示
文本模式/字符模式
以原始數(shù)據(jù)編碼后的形式顯示
說人話
HEX模式就是進行ASCI編碼后的數(shù)據(jù),而文本模式,就是ASCI譯碼之后的結(jié)果,C語言大家應該了解過,可以對照圖來驗證一下。
同樣也需要留意,SendByte函數(shù)里面的參數(shù),如果是0x開頭的十六進制,用文本模式和HEX模式發(fā)送和接收,結(jié)果是不一樣的。挺簡單的,各位自行驗證。文章來源:http://www.zghlxwxcb.cn/news/detail-471158.html
隨便扯兩句
其實寫完上一篇筆記,就已經(jīng)馬不停蹄地開始寫這一篇筆記。中間鴿了近兩個月,當時以為處理完手頭的事情,就可以繼續(xù)用空閑時間學點東西,結(jié)果學了一半的內(nèi)容就被課內(nèi)的任務纏身,忙的不可開交,而且串口這一塊知識點環(huán)環(huán)相扣,時隔兩個月,很多東西都忘記了。處理完課內(nèi)大作業(yè),刪無用文件時太激動了小手一抖把之前的keil工程文件都給刪了,還好有之前的筆記,讓我不至于從頭開始去寫以前的模塊內(nèi)容。這兩天還發(fā)著低燒,不過還是硬挺著完成中斷了這么久的內(nèi)容。不能再拖了,越拖越難重新拾起來。文章來源地址http://www.zghlxwxcb.cn/news/detail-471158.html
到了這里,關(guān)于51單片機串口的應用(單片機和電腦互發(fā)數(shù)據(jù))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!