姓名:莫云軻? ?學(xué)號(hào):19020100320? ? 學(xué)院:電子工程學(xué)院
轉(zhuǎn)載自:http://blog.csdn.net/a514371309/article/details/73481423竞川,有刪節(jié)
【嵌牛導(dǎo)讀】:單片機(jī)與計(jì)算機(jī)系統(tǒng)最關(guān)鍵的莫過于信息與數(shù)據(jù)礁芦,本文將在上篇文章的基礎(chǔ)上沟于,詳細(xì)介紹了單片機(jī)的串口通信協(xié)議摩梧。
【嵌牛鼻子】:單片機(jī)矢腻,C語言祭阀,串口通信協(xié)議
【嵌牛提問】:單片機(jī)是通過協(xié)議進(jìn)行通信的爱态,那么協(xié)議是怎么一回事衬潦,它又是怎么讓單片機(jī)進(jìn)行通信呢回右?
【嵌牛正文】:
現(xiàn)實(shí)生活中隆圆, 我們總是要與人打交道,互通有無翔烁。單片機(jī)也一樣渺氧,需要跟各種設(shè)備交互。例如汽車的顯示儀表需要知道汽車的轉(zhuǎn)速及電動(dòng)機(jī)的運(yùn)行參數(shù)蹬屹,那么顯示儀表就需要從汽車的底層控制器取得數(shù)據(jù)侣背。而這個(gè)數(shù)據(jù)的獲得過程就是一個(gè)通信過程。類似的例子還有控制器通常是單片機(jī)或者PLC與變頻器的通信慨默。通信的雙方需要遵守一套既定的規(guī)則也稱為協(xié)議贩耐,這就好比我們?nèi)酥g的對(duì)話,需要在雙方都遵守一套語言語法規(guī)則才有可能達(dá)成對(duì)話厦取。
通信協(xié)議又分為硬件層協(xié)議和軟件層協(xié)議潮太。硬件層協(xié)議主要規(guī)范了物理上的連線,傳輸電平信號(hào)及傳輸?shù)闹刃虻扔布再|(zhì)的內(nèi)容。常用的硬件協(xié)議有串口铡买,IIC更鲁, SPI, RS485奇钞, CAN和 USB岁经。軟件層協(xié)議則更側(cè)重上層應(yīng)用的規(guī)范,比如modbus協(xié)議蛇券。
好了,那這里我們就著重介紹51單片機(jī)的串口通信協(xié)議樊拓,以下簡稱串口纠亚。串口的6個(gè)特征如下。
(1)筋夏、物理上的連線至少3根蒂胞,分別是Tx數(shù)據(jù)發(fā)送線,Rx數(shù)據(jù)接收線条篷,GND共用地線。
(2)、0與1的約定胰伍。RS232電平兵扬,約定﹣5V至﹣25V之間的電壓信號(hào)為1,﹢5V至﹢25V之間的電壓信號(hào)為0 乞巧。TTL電平涨椒,約定5V的電壓信號(hào)為1,0V電壓信號(hào)為0 绽媒。CMOS電平蚕冬,約定3.3V的電壓信號(hào)為1,0V電壓信號(hào)為0 是辕。其中囤热,CMOS電平一般用于ARM芯片中。
(3)获三、發(fā)送秩序旁蔼。低位先發(fā)。
(4)石窑、波特率牌芋。收發(fā)雙方共同約定的一個(gè)數(shù)據(jù)位(0或1)在數(shù)據(jù)傳輸線上維持的時(shí)間。也可理解為每秒可以傳輸?shù)奈粩?shù)松逊。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s躺屁。
(5)、通信的起始信號(hào)经宏。發(fā)送方在沒有發(fā)送數(shù)據(jù)時(shí)犀暑,應(yīng)該將Tx置1 驯击。 當(dāng)需發(fā)送時(shí),先將Tx置0耐亏,并且保持1位的時(shí)間徊都。接受方不斷地偵測(cè)Rx,如果發(fā)現(xiàn)Rx常時(shí)間變高后广辰,突然被拉低(置為0)暇矫,則視為發(fā)送方將要發(fā)送數(shù)據(jù),迅速啟動(dòng)自己的定時(shí)器择吊,從而保證了收發(fā)雙方定時(shí)器同步定時(shí)李根。
(6)、停止信號(hào)几睛。發(fā)送方發(fā)送完最后一個(gè)有效位時(shí)房轿,必須再將Tx保持1位的時(shí)間,即為停止位所森。
好了囱持,理論暫時(shí)到這里,現(xiàn)在我們要做一個(gè)實(shí)驗(yàn)焕济,將一個(gè)字節(jié)從51單片機(jī)發(fā)送到電腦串口調(diào)試助手上纷妆。這個(gè)實(shí)驗(yàn)的目的是為了掌握串口通信協(xié)議的收發(fā)過程。
虛擬串口
實(shí)驗(yàn)一吼蚁、虛擬串口實(shí)驗(yàn)
一般單片機(jī)都有專門的串口引腳凭需,51里面分別是P3.0和P3.1,這些引腳擁有串口的硬件電路肝匆,因此使用它們并不需要設(shè)置信號(hào)的發(fā)送停止粒蜈。為了掌握協(xié)議,我們使用其他的引腳來模擬串口旗国,所以也叫虛擬串口枯怖。這里我們選用P1.0,然而注意到我們51單片機(jī)要發(fā)送數(shù)據(jù)給電腦能曾,必須經(jīng)過一個(gè)串口轉(zhuǎn)USB設(shè)備(即TTL電平轉(zhuǎn)換為RS232電平)度硝,而限于我們的開發(fā)板只有P3.0與P3.1連接到了串口轉(zhuǎn)USB設(shè)備,所以我們可以將P1.0短接到P3.1 寿冕。 下圖是這個(gè)串口轉(zhuǎn)USB的原理圖蕊程。
好了直接上代碼吧。
#include?"reg51.h"
/*
將P1.0虛擬成串口發(fā)送腳TX
以9600bit/s的比特率向外發(fā)送數(shù)據(jù)
因?yàn)椴ㄌ芈适????9600bit/s
所以me發(fā)送一位的時(shí)間是?t=1000000us/9600=104us
*/
sbit?TX=P3^1;//P1^0?output?TTL?signal,?need?to?transferred?to?rs232?signal,?can?be?connected?to?P3^1
#define?u16?unsigned?int?//宏定義
#define?u8?unsigned?char
u8?sbuf;
bit?ti=0;
voiddelay(u16?x)
{
while(x--);
}
voidTimer0_Init()
{
TMOD?|=?0x01;
TH0=65440/256;
TH0=65440%256;
TR0=0;
}
voidIsr_Init()
{
EA=1;
ET0=1;
}
voidSend_Byte(u8?dat)
{
sbuf=dat;//通過引入全局變量sbuf驼唱,可以保存形參dat
TX=0;//A?起始位
TR0=1;
while(ti==0);//等待發(fā)送完成
ti=0;//清除發(fā)送完成標(biāo)志
}
voidTF0_isr()?interrupt?1//每104us進(jìn)入一次中斷
{
staticu8?i;//記錄進(jìn)入中斷的次數(shù)
TH0=65440/256;
TL0=65440%256;
i++;
if(i>=1?&&?i<=8)
{
if((sbuf&(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);
}
}
實(shí)驗(yàn)引入了定時(shí)器0來控制發(fā)送線上的各個(gè)位的保持時(shí)間藻茂。首先main函數(shù)進(jìn)入,TX置1則使發(fā)送線處于空閑,這時(shí)候發(fā)送方和接受方都處于空閑辨赐。接下來初始化定時(shí)器0优俘,TR0置0表示還不要啟動(dòng)定時(shí)器0。接著中斷系統(tǒng)初始化掀序,此時(shí)中斷系統(tǒng)已經(jīng)開啟帆焕。進(jìn)入while循環(huán),先進(jìn)Send_Byte()函數(shù)不恭,將65傳給形參dat叶雹,dat再將65賦值給sbuf,到這里準(zhǔn)備工作就做好了换吧。接著TX置0浑娜,這個(gè)是起始位,要保持這個(gè)起始位104us式散。于是就啟動(dòng)定時(shí)器TR0置1,計(jì)時(shí)器開始計(jì)數(shù)打颤。當(dāng)?shù)谝淮我绯龅臅r(shí)候暴拄,也就是過了104us,進(jìn)入中斷编饺,同時(shí)接收方也偵測(cè)到了這個(gè)突然被拉低的信號(hào)乖篷,于是迅速啟動(dòng)自己的定時(shí)器。進(jìn)入中斷子函數(shù)后透且,先是重裝定時(shí)器初值撕蔼,然后i加1,也就是當(dāng)i=1時(shí)秽誊,就應(yīng)該發(fā)送數(shù)據(jù)的最低位了鲸沮,總共有8位數(shù)據(jù),所以使用條件語句if(i>=1 && i<=8)來判斷是否發(fā)送完數(shù)據(jù)位锅论。然后再通過if(i==9) 來發(fā)送停止位讼溺,最后當(dāng)i=10時(shí),也就是發(fā)送完了最易,這時(shí)候要關(guān)閉定時(shí)器(那么程序也就)怒坯,同時(shí)i置0,ti置1(才能跳出while(ti==0)循環(huán))藻懒,最后將ti置0剔猿,保證下次要發(fā)送字節(jié)時(shí)讓程序停留在while(ti==0)。
片上串口
以上說的是虛擬串口嬉荆,上文中談到與串口相關(guān)的引腳P3.0與P3.1归敬,事實(shí)上51單片機(jī)自帶片上串口,那這個(gè)串口又該怎么使用呢?
片上串口支持同步模式與異步模式弄慰。簡單來說同步模式就是指有時(shí)鐘線第美,而異步模式無時(shí)鐘線。這里的時(shí)鐘線是指在同步通信時(shí)陆爽,用一根線專門傳輸時(shí)鐘信號(hào)什往,這個(gè)信號(hào)用來與要發(fā)送的每一位保持同步,這樣就避免了例如異步通信中因?yàn)椴捎枚〞r(shí)器而引入的時(shí)間誤差慌闭。
片上串口還支持8位模式和9位模式别威。如下圖所示
其中D0-D7是一個(gè)字節(jié)的8個(gè)位。9位模式只是多了一個(gè)位TB8驴剔,這個(gè)TB8的作用是奇偶校驗(yàn)或多機(jī)通信省古。奇偶校驗(yàn)原理這不加分析。多機(jī)通信時(shí)比如主機(jī)只發(fā)送數(shù)據(jù)給網(wǎng)絡(luò)中的一臺(tái)地址為0x02的設(shè)備丧失,這時(shí)候先讓TB8為1豺妓,前面的D0-D7則為地址即0x02,之后再讓TB8為0布讹,前面的D0-D7則為數(shù)據(jù)了琳拭。
上面設(shè)置了片上串口的模式,另外還要設(shè)置串口的波特率描验。
片上串口的波特率等于定時(shí)器1工作在方式2時(shí)溢出率的32分頻白嘁。如果要定時(shí)器1工作在方式2,那么TMOD=0x20膘流。另外要保證為32分頻絮缅,我們還必須設(shè)置計(jì)數(shù)器初值。設(shè)晶振為11.0592Mhz呼股,則定時(shí)器的計(jì)數(shù)脈沖為F=f/12耕魄,則定時(shí)器每計(jì)一個(gè)脈沖的時(shí)間為T=12/f。又令計(jì)數(shù)器的起點(diǎn)為x彭谁,則溢出一次要計(jì)的脈沖數(shù)為(256-x)屎开。所以在計(jì)數(shù)起點(diǎn)為x時(shí),溢出一次的時(shí)間為t=12/f*(256-x)马靠。則對(duì)應(yīng)的溢出率為1/t=f/(12*(256-x))奄抽。對(duì)應(yīng)的波特率就為b=f/(384*(256-x))。
x=256-f/(384*b)
其中f為晶振頻率甩鳄,b為希望的波特率逞度,x為定時(shí)器的計(jì)數(shù)起點(diǎn)TH1的值。
例如當(dāng)晶振為11.0592M妙啃,希望波特率為9600bit/s档泽,則TH1=253俊戳。題外話,我們同樣可以演算出在其他常用波特率情況下馆匿,TH1始終為一個(gè)整數(shù)抑胎。這里也就解釋了為什么51里面選用了11.0592M的晶振而不是12M,這樣就保證了串口的時(shí)序更加準(zhǔn)確渐北,雖然犧牲了定時(shí)器的準(zhǔn)確度阿逃。
實(shí)驗(yàn)二,片外串口發(fā)送一個(gè)字節(jié)赃蛛。
好了現(xiàn)在開始我們的實(shí)驗(yàn)之旅恃锉。直接看代碼吧。
#include?"reg51.h"
#define?u16?unsigned?int
#define?u8?unsigned?char
voiddelay(u16?x)
{
while(x--);
}
voidUart_Init()//串口初始化
{
SCON=0x50;//8位異步模式
TMOD|=0x20;//定時(shí)器1工作方式2
TH1=253;//9600bit/s
TR1=1;
}
voidSend_Byte(u8?dat)
{
SBUF=dat;//啟動(dòng)發(fā)送呕臂,只需要把發(fā)送內(nèi)容給SBUF這個(gè)寄存器
while(TI==0);//等待發(fā)送完成破托,因?yàn)門I為1時(shí)表示在發(fā)送停止位
TI=0;
}
voidmain()
{
Uart_Init();
while(1)
{
Send_Byte('m');
delay(60000);
}
}
實(shí)驗(yàn)二較之實(shí)驗(yàn)一,代碼減少了很多歧蒋,而且不用考慮繁瑣的位發(fā)送時(shí)序土砂。只需要明白各個(gè)寄存器SCON,TMOD谜洽,TCON瘟芝,SBUF的用法。TI是SCON中的第一位褥琐,為發(fā)送中斷請(qǐng)求標(biāo)志位。在本方式中晤郑,在停止位開始發(fā)送時(shí)由內(nèi)部硬件置位敌呈,響應(yīng)中斷后TI必須又軟件清零。
實(shí)驗(yàn)三造寝、片上串口發(fā)送一個(gè)字符串
上面介紹了如何發(fā)送一個(gè)字節(jié)磕洪,那如何發(fā)送一個(gè)字符串甚至文本呢?這里我們首先介紹下字符串的概念诫龙。
字符串:從存儲(chǔ)器的某個(gè)地址開始析显,連續(xù)存放多個(gè)字符的ASCII碼,并且在最后一個(gè)字符的后面存放一個(gè)0签赃,這段連續(xù)的內(nèi)存空間就叫字符串谷异,最后的0叫字符串的結(jié)束符。注意這里的0和加單引號(hào)的0不是一個(gè)概念锦聊,加單引號(hào)的0是指0的ASCII碼歹嘹。
數(shù)組與字符串的關(guān)系:字符串是數(shù)組的一種特殊情況,數(shù)組在特定條件下可當(dāng)做字符串用孔庭。C語言用雙引號(hào)描述一個(gè)字符串尺上,如“abcd”材蛛。
下面我們通過一個(gè)實(shí)驗(yàn)來展示如何發(fā)送字符串。我們實(shí)驗(yàn)的目標(biāo)是打印字符串“Hello World ! 第一!”到打印機(jī)怎抛。直接上代碼卑吭。
#include?"reg51.h"
#define?u16?unsigned?int
#define?u8?unsigned?char
voiddelay(u16?x)
{
while(x--);
}
voidUart_Init()//串口初始化
{
SCON=0x50;//8位異步模式
TMOD|=0x20;//定時(shí)器1工作方式2
TH1=253;//9600bit/s
TR1=1;
}
voidSend_Byte(u8?dat)//串口發(fā)送一個(gè)字節(jié)
{
SBUF=dat;//啟動(dòng)發(fā)送,只需要把發(fā)送內(nèi)容給SBUF這個(gè)寄存器
while(TI==0);//等待發(fā)送完成马绝,因?yàn)門I為1時(shí)表示在發(fā)送停止位
TI=0;
}
voidSend_String(u8?*str)//發(fā)送一個(gè)字符串??*str為字符串第一個(gè)字符的地址
{
abc://標(biāo)號(hào)
if(*str?!=?0)
{
Send_Byte(*str);
str++;
gotoabc;
}
}
voidmain()
{
Uart_Init();
while(1)
{
Send_String("Hello?World!?第一豆赏!");
Send_Byte(10);
delay(60000);
delay(60000);
}
}