(3)、發(fā)送秩序。低位先發(fā)。(4)、波特率。收發(fā)雙方共同約定的一個數(shù)據(jù)位(0或1)在數(shù)據(jù)傳輸線上維持的時間。也可理解為每秒可以傳輸?shù)奈粩?shù)。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s。(5)、通信的起始信號。發(fā)送方在沒有發(fā)送數(shù)據(jù)時,應(yīng)該將Tx置1 。 當(dāng)需發(fā)送時,先將Tx置0,并且保持1位的時間。接受方不斷地偵測Rx,如果發(fā)現(xiàn)Rx常時間變高后,突然被拉低(置為0),則視為發(fā)送方將要發(fā)送數(shù)據(jù),迅速啟動自己的定時器,從而保證了收發(fā)雙方定時器同步定時。
(6)、停止信號。發(fā)送方發(fā)送完最后一個有效位時,必須再將Tx保持1位的時間,即為停止位。
好了,理論暫時到這里,現(xiàn)在我們要做一個實驗,將一個字節(jié)從51單片機發(fā)送到電腦串口調(diào)試助手上。這個實驗的目的是為了掌握串口通信協(xié)議的收發(fā)過程。
虛擬串口
實驗一、虛擬串口實驗
一般單片機都有專門的串口引腳,51里面分別是P3.0和P3.1,這些引腳擁有串口的硬件電路,因此使用它們并不需要設(shè)置信號的發(fā)送停止。為了掌握協(xié)議,我們使用其他的引腳來模擬串口,所以也叫虛擬串口。這里我們選用P1.0,然而注意到我們51單片機要發(fā)送數(shù)據(jù)給電腦,必須經(jīng)過一個串口轉(zhuǎn)USB設(shè)備(即TTL電平轉(zhuǎn)換為RS232電平),而限于我們的開發(fā)板只有P3.0與P3.1連接到了串口轉(zhuǎn)USB設(shè)備,所以我們可以將P1.0短接到P3.1 。 下圖是這個串口轉(zhuǎn)USB的原理圖。
好了直接上代碼吧。
[cpp]view plaincopy
-
#include"reg51.h"
-
/*
-
將P1.0虛擬成串口發(fā)送腳TX
-
以9600bit/s的比特率向外發(fā)送數(shù)據(jù)
-
因為波特率是9600bit/s
-
所以me發(fā)送一位的時間是t=1000000us/9600=104us
-
*/
-
sbitTX=P3^1;//P1^0outputTTLsignal,needtotransferredtors232signal,canbeconnectedtoP3^1
-
#defineu16unsignedint//宏定義
-
#defineu8unsignedchar
-
u8sbuf;
-
bitti=0;
-
voiddelay(u16x)
-
{
-
while(x--);
-
}
-
voidTimer0_Init()
-
{
-
TMOD|=0x01;
-
TH0=65440/256;
-
TH0=65440%256;
-
TR0=0;
-
}
-
voidIsr_Init()
-
{
-
EA=1;
-
ET0=1;
-
}
-
voidSend_Byte(u8dat)
-
{
-
sbuf=dat;//通過引入全局變量sbuf,可以保存形參dat
-
TX=0;//A起始位
-
TR0=1;
-
while(ti==0);//等待發(fā)送完成
-
ti=0;//清除發(fā)送完成標(biāo)志
-
}
-
voidTF0_isr()interrupt1//每104us進入一次中斷
-
{
-
staticu8i;//記錄進入中斷的次數(shù)
-
TH0=65440/256;
-
TL0=65440%256;
-
i++;
-
if(i>=1&&i<=8)??
-
{
-
if((sbuf&(1<<(i-1)))==0)??//?(sbuf&(1<<(i-1)))表示取出i-1位??
-
{
-
TX=0;
-
}
-
else
-
{
-
TX=1;
-
}
-
}
-
if(i==9)//停止位
-
{
-
TX=1;
-
}
-
if(i==10)
-
{
-
TR0=0;
-
i=0;
-
ti=1;//發(fā)送完成
-
}
-
}
-
voidmain()
-
{
-
TX=1;//使TX處于空閑狀態(tài)
-
Timer0_Init();
-
Isr_Init();
-
while(1)
-
{
-
Send_Byte(65);//0x41
-
delay(60000);
-
}
-
}
實驗引入了定時器0來控制發(fā)送線上的各個位的保持時間。首先main函數(shù)進入,TX置1則使發(fā)送線處于空閑,這時候發(fā)送方和接受方都處于空閑。接下來初始化定時器0,TR0置0表示還不要啟動定時器0。接著中斷系統(tǒng)初始化,此時中斷系統(tǒng)已經(jīng)開啟。進入while循環(huán),先進Send_Byte()函數(shù),將65傳給形參dat,dat再將65賦值給sbuf,到這里準(zhǔn)備工作就做好了。接著TX置0,這個是起始位,要保持這個起始位104us。于是就啟動定時器TR0置1,計時器開始計數(shù)。當(dāng)?shù)谝淮我绯龅臅r候,也就是過了104us,進入中斷,同時接收方也偵測到了這個突然被拉低的信號,于是迅速啟動自己的定時器。進入中斷子函數(shù)后,先是重裝定時器初值,然后i加1,也就是當(dāng)i=1時,就應(yīng)該發(fā)送數(shù)據(jù)的最低位了,總共有8位數(shù)據(jù),所以使用條件語句if(i>=1 && i<=8)來判斷是否發(fā)送完數(shù)據(jù)位。然后再通過if(i==9) 來發(fā)送停止位,最后當(dāng)i=10時,也就是發(fā)送完了,這時候要關(guān)閉定時器(那么程序也就),同時i置0,ti置1(才能跳出while(ti==0)循環(huán)),最后將ti置0,保證下次要發(fā)送字節(jié)時讓程序停留在while(ti==0)。
片上串口
以上說的是虛擬串口,上文中談到與串口相關(guān)的引腳P3.0與P3.1,事實上51單片機自帶片上串口,那這個串口又該怎么使用呢?
片上串口支持同步模式與異步模式。簡單來說同步模式就是指有時鐘線,而異步模式無時鐘線。這里的時鐘線是指在同步通信時,用一根線專門傳輸時鐘信號,這個信號用來與要發(fā)送的每一位保持同步,這樣就避免了例如異步通信中因為采用定時器而引入的時間誤差。
片上串口還支持8位模式和9位模式。如下圖所示
其中D0-D7是一個字節(jié)的8個位。9位模式只是多了一個位TB8,這個TB8的作用是奇偶校驗或多機通信。奇偶校驗原理這不加分析。多機通信時比如主機只發(fā)送數(shù)據(jù)給網(wǎng)絡(luò)中的一臺地址為0x02的設(shè)備,這時候先讓TB8為1,前面的D0-D7則為地址即0x02,之后再讓TB8為0,前面的D0-D7則為數(shù)據(jù)了。
上面設(shè)置了片上串口的模式,另外還要設(shè)置串口的波特率。
片上串口的波特率等于定時器1工作在方式2時溢出率的32分頻。如果要定時器1工作在方式2,那么TMOD=0x20。另外要保證為32分頻,我們還必須設(shè)置計數(shù)器初值。設(shè)晶振為11.0592Mhz,則定時器的計數(shù)脈沖為F=f/12,則定時器每計一個脈沖的時間為T=12/f。又令計數(shù)器的起點為x,則溢出一次要計的脈沖數(shù)為(256-x)。所以在計數(shù)起點為x時,溢出一次的時間為t=12/f*(256-x)。則對應(yīng)的溢出率為1/t=f/(12*(256-x))。對應(yīng)的波特率就為b=f/(384*(256-x))。
x=256-f/(384*b)
其中f為晶振頻率,b為希望的波特率,x為定時器的計數(shù)起點TH1的值。
例如當(dāng)晶振為11.0592M,希望波特率為9600bit/s,則TH1=253。題外話,我們同樣可以演算出在其他常用波特率情況下,TH1始終為一個整數(shù)。這里也就解釋了為什么51里面選用了11.0592M的晶振而不是12M,這樣就保證了串口的時序更加準(zhǔn)確,雖然犧牲了定時器的準(zhǔn)確度。
實驗二,片外串口發(fā)送一個字節(jié)。
好了現(xiàn)在開始我們的實驗之旅。直接看代碼吧。
[cpp]view plaincopy
-
#include"reg51.h"
-
#defineu16unsignedint
-
#defineu8unsignedchar
-
voiddelay(u16x)
-
{
-
while(x--);
-
}
-
voidUart_Init()//串口初始化
-
{
-
SCON=0x50;//8位異步模式
-
TMOD|=0x20;//定時器1工作方式2
-
TH1=253;//9600bit/s
-
TR1=1;
-
}
-
voidSend_Byte(u8dat)
-
{
-
SBUF=dat;//啟動發(fā)送,只需要把發(fā)送內(nèi)容給SBUF這個寄存器
-
while(TI==0);//等待發(fā)送完成,因為TI為1時表示在發(fā)送停止位
-
TI=0;
-
}
-
voidmain()
-
{
-
Uart_Init();
-
while(1)
-
{
-
Send_Byte('m');
-
delay(60000);
-
}
-
}
實驗二較之實驗一,代碼減少了很多,而且不用考慮繁瑣的位發(fā)送時序。只需要明白各個寄存器SCON,TMOD,TCON,SBUF的用法。TI是SCON中的第一位,為發(fā)送中斷請求標(biāo)志位。在本方式中,在停止位開始發(fā)送時由內(nèi)部硬件置位,響應(yīng)中斷后TI必須又軟件清零。
實驗三、片上串口發(fā)送一個字符串
上面介紹了如何發(fā)送一個字節(jié),那如何發(fā)送一個字符串甚至文本呢?這里我們首先介紹下字符串的概念。
字符串:從存儲器的某個地址開始,連續(xù)存放多個字符的ASCII碼,并且在最后一個字符的后面存放一個0,這段連續(xù)的內(nèi)存空間就叫字符串,最后的0叫字符串的結(jié)束符。注意這里的0和加單引號的0不是一個概念,加單引號的0是指0的ASCII碼。
數(shù)組與字符串的關(guān)系:字符串是數(shù)組的一種特殊情況,數(shù)組在特定條件下可當(dāng)做字符串用。C語言用雙引號描述一個字符串,如“abcd”。
下面我們通過一個實驗來展示如何發(fā)送字符串。我們實驗的目標(biāo)是打印字符串“Hello World ! 第一!”到打印機。直接上代碼。
[cpp]view plaincopy
-
#include"reg51.h"
-
#defineu16unsignedint
-
#defineu8unsignedchar
-
voiddelay(u16x)
-
{
-
while(x--);
-
}
-
voidUart_Init()//串口初始化
-
{
-
SCON=0x50;//8位異步模式
-
TMOD|=0x20;//定時器1工作方式2
-
TH1=253;//9600bit/s
-
TR1=1;
-
}
-
voidSend_Byte(u8dat)//串口發(fā)送一個字節(jié)
-
{
-
SBUF=dat;//啟動發(fā)送,只需要把發(fā)送內(nèi)容給SBUF這個寄存器
-
while(TI==0);//等待發(fā)送完成,因為TI為1時表示在發(fā)送停止位
-
TI=0;
-
}
-
voidSend_String(u8*str)//發(fā)送一個字符串*str為字符串第一個字符的地址
-
{
-
abc://標(biāo)號
-
if(*str!=0)
-
{
-
Send_Byte(*str);
-
str++;
-
gotoabc;
-
}
-
}
-
voidmain()
-
{
-
Uart_Init();
-
while(1)
-
{
-
Send_String("HelloWorld!第一!");
-
Send_Byte(10);
-
delay(60000);
-
delay(60000);
-
}
-
}
-
單片機
+關(guān)注
關(guān)注
6037文章
44558瀏覽量
635299 -
串口通信
+關(guān)注
關(guān)注
34文章
1626瀏覽量
55528
原文標(biāo)題:單片機C語言之串口通信協(xié)議
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論