PC與單片機RS-232串口的通訊和控制
先說說我硬件的情況。我用的PC是個二手的IBM240小本本,十寸屏,賽揚400,機子很老了。但也有它的優點:1、串口,并口,PS鼠標口、USB口、PCM插槽全有。 調試硬件電路最好還是用真實串、并口好些,因為用USB轉換的串、并口有時會出現兼容性上的問題,就會增加你調試上的復雜性。
本文引用地址:http://cqxgywz.com/article/201612/325233.htm下圖為本人的IBM 240及各種接口圖:

下圖是PC的大小對比圖
單片機還是我一步步做出來的那個了,USB-ISP編程線也是我前面秀過,好!現在我放上PC與單片機連接圖:
用本本的好處就是調整方便,接口、器件都在旁邊,假若是用臺式機,你還得鉆到桌子底下去插拔那些接口,而現在本本卻又沒有串、并口了。
言歸正傳,單片機的RS-232串口通過9針串口線接到本本的串口上,單片機的ISP編程口通過USB-ISP編程器接到本本的USB口。
另外本本要接電源,單片機也要接5V電源,還有千萬千要記得,本本是要插上鼠標才玩得轉哦!
我將這個一步一步掌握串口的通訊與控制分為五步:
1、測試單片機與PC的串口連接是否正確好用。
2、用VB自己編寫的程序替換掉串口調試器軟件來接收單片機發送的數據。
3、掌握單片機端如何發送字符和數值數據。
4、掌握PC端程序如何接收發送字符和數值型數據。
5、做一個A/D轉換(ADC0809)獲取數據發送到PC,并在PC上顯示實時趨勢的例子。
我們要用到的四個軟件:
1、USB接口編程軟件:是PC機給單片機進行燒寫編程用的
2、串口調試軟件:用來測試單片機內串口電路、程序工作是否正常。
3、單片機程序的編程軟件KEIL:用于編寫單片機內的程序并生成HEX文件。
4、VB6.0:用于編寫PC機上的應用程序
先進行第一步工作:
在前面一篇《板子上最一個部件——RS232串口》講過如何對單片機上的串口進行調試,我們還是先對這個連接進行測試,我們首先得確認連接正確,電路正常,才能進行后面程序編寫和調試工作。
依舊是那個最簡單的程序,AT89S52從串口不停地發送“hello world!”
#include
#include
void main(void)
{
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
TI=1;
TR1=1; //啟動定時器
while(1)
{
printf("hello world!n");
}
}
把原先生成好的這個HEX文件用上面講到的Progisp軟件寫到AT89S52里。
然后打開sscom32串口調試器,設定好串口號、波特率和數據位,按下“打開串口”鈕,應該就能收到那個一行行的“hello world!”了。能看到這一行行的文字,說明電路、連接和程序都正常了。
如果是亂碼,則要將單片機上的復位鈕按一下。如果還是亂碼,那一般就是波特率不對,晶振應為11.0592MHZ或串口接觸不良,線過長等原因。如果跟本就收不到任何字符,就說明電路或連接有故障,或者程序有問題。那就要好好查查了。
好!現在電路、連接、程序都可以正常工作。但在串口調試器接收框里看到接收到的一行行“hello world!”顯示太快,不容易看出它一次一次發送的過程,不便于分析問題,我們得給它加點延時。
#include
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
TI=1;
TR1=1; //啟動定時器
while(1)
{
printf("hello world!n"); //向串口送出數據
delay(); //調用延時
delay();
}
}
程序寫好了,在KEIL里添加上延時語句后,重新生成HEX文件。再用Progisp將它寫進AT89S52里,這時,你就可以看到串口調試器已經接收到大約一秒一次的“Hello world!”了。
在這里說明一下,只要像最上面我圖上給出的那樣把單片機和PC連接好后,無論你往AT89S52里燒寫程序,還是單片機連接到PC并向PC串口發送數據,都不用再插拔器件了,只需要在這幾個程序間切換工作便可以了。(是不是很方便呢
)
這樣第一項工作就完成了,確認電路的連接及單片機程序都工作正常。
下面要進行第二項工作:
目的:用我自己的PC程序把串口調試器軟件替換掉。因為我最終要的是接收單片機上的數據,并將接收的數據在PC上進行處理、存儲,而串口調試器只能接收固定的內容,你也不無法把收到的數據接管過來,僅僅能做連接測試而已。
我用的是最簡單易用的Visual Basic 6.0。具體如何操作運用,網上有很多教程,也很容易上手。
打開VB6.0,新建一個工程,也就是要建立一個新的程序。
這是個標準的VB6.0界面,我們要進行串口的操作需要添加一個串口控件MSCOMM32.OCX,或許你的機子上有,也許沒有,沒有的在網上搜了下一個裝在你c:winntsystem32 。然后你用鼠標右鍵點擊VB界面左側的工具箱,彈出菜單后選部件,或者在上部的主菜單上點“工程”--“部件”,就會彈出如下界面:
在列出的項里找到Microsoft Comm Control 6.0。在前面的小方框里點上鉤。注意看下面的提示欄里就告訴你這個控件的文件名和所在的目錄。點“確定”鈕,這時在VB主界面的工具箱里就會多出個小電話的控件圖標了:
接下來點擊這個控件圖標,然后在Form1的窗口上拉出個框(或者雙擊小電話圖標)把圖標放到Form1窗口上去。如下圖:
如果你的圖標放不上去并彈出如下提示框:
就說明你的VB6.0是簡化版,不是正式安裝的。解決方法如下:
首先把MSComm32.OCX拷進C:WINNTSYSTEM32 (我的機器一開始并沒有這個控件,我去網上下了一個,機器里面有此控件的此步不做?。ㄗⅲ郝窂揭晕覚C器的winXP系統為例)
然后點擊 開始>運行>regsvr32 c:winntsystem32mscomm32.ocx 成功后,開始>運行>regedit,進入注冊表,找到HKEY_CLASSES_ROOTLicenses,然后新建一個項,命名為:“4250E830-6AC2-11cf-8ADB-00AA00C00905”,值為:“kjljvjjjoquqmjjjvpqqkqmqykypoqjquoun”。一切就OK了。
VB6.0設置正常后,我由簡入繁地進行程序的編寫。在窗體上先放上一個串口控件,一個文本框,一個按鈕,一個定時器。如下圖:
串口控件是單片機串口和PC串口進行通訊的橋梁;文本框用來顯示我們收到的數據,按鈕用來啟動這個接收,定時器用來定時檢查每一小段時間檢查是否有串口數據收到。
我們先對串口控件進行屬性設置,Commport是串口號設置,一般設置為1,Settings是對串口的波特率、有無奇偶校驗,數據位數,停止位數進行設置,因為我的單片機程序上用的波特率是19200,所以在這兒我只對波特率進行調整,其它都是都用默認值。如下圖:
對按鈕控鍵進行設置:只是將“Caption”標題屬性改為“接收數據”。
對定時器進行設置,將“Interval”間歇時間改為400。這樣就是每400毫秒檢查一次有無數據收到。
對文本框進行設置:將“MultiLine”多行顯示設為“True”允許。
控件的屬性設置完了,下面我們為程序寫代碼,先雙擊“接收數據”按鈕。會彈出代碼窗口,我了如下代碼,如圖:
上面就是我們編寫的按鈕事件代碼。寫完后我們在鍵盤上按“Shift”+“F7”,回到對象窗口。再雙擊那個定時器控件,界面就切換到定時器的代碼窗口,我們寫程序如下圖:
現在我們的這個VB程序就寫好了。接著就試著運行一下這個程序,且慢!我們還是要先啟動串口調試器看看單片機是否還在不斷的發送著“hello world!”,確認它還是在不停地顯示著那行“hello world!”,就可以關了串口調試器。然后在VB6.0的主界面點擊那個小三角的播放鈕。我的程序就運行如下了:
第二項任務完成!我自己接管了接收的數據。點菜單的保存這項工程。
第三項任務:掌握單片機端如何發送字符和數值數據。
接下來我要做的是對AT89S52內串行數據的發送進行解和掌握,以便我們能隨心所欲地將單片機獲得的數值數據或字符數據發送給PC機進行處理或存儲。
先來看看原來我寫的AT89S52不斷發送“hello world!”的那段程序。
#include
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
TI=1;
TR1=1; //啟動定時器
while(1)
{
printf("hello world!n"); //向串口送出數據
delay(); //調用延時
delay();
}
}
上面的程序中除了設置串口的語句和延時語句外,負責向串口發送的語句只有一行,即“printf("helleo world!n")”。學習過編程的一般都知道,print語句的作用是輸出字符串的,但我們如果從單片機的A/D模塊上獲得了數據想發送到PC,應該怎么做呢?雖然你也可以先將這些數據轉換成數字字符串,例如我們從一個8位的A/D模塊上獲得的數據是個數值從0-255的8位的數值,如果數值是1,那你得先將1這個數值轉換成“1”字符對應的代碼49(二進制110001,十六進制31H),再用printf語句發送出去。如果值是255,那你得先把它轉換成3個字符“2”、“5”、“5”,再用printf發送出去。但這樣既復雜又不規范,“1”是一個字符,“2”、“5”、“5”是三個字符,隨著數值的不同,發送的數據的字節數據也不同,這樣可不行。
我們還是先蹲蹲馬步,了解一下單片機串口發送數據的實質:

上圖是串口的發送時序示意圖,最上面的TX表示的是單片機串口的發送線,,第二根CLK是內部時鐘線,最下面的是發送標志信號TI。
我們以最常用的串行方式1,即10位異步通信方式來簡單分析一下。它規定了1位起始位、8位數據位和1位停止位。其中第一位(起始位)和最后一位(停止位)是在你設定好串口的方式,打開串口后由芯片內串口模塊自動插入的,不用人為加。
當你想通過串口發送數據時,只需要向AT89S52內的一個8位的特殊功能寄存器SBUF(99H)送入一個字節你想要發送給PC的數據,它就會自動連同起始位,數據位,停止位一起產生10位串行的電位信號送出。在第10個脈沖后將TX線的電位拉高,同時將標志位TI置1,告訴自己的程序發送結束。
接收方也是以規定好的相同的波特率時鐘脈沖為基準,當某一個脈沖到來后檢測到RX線上的電位被拉低,就知道對方開始發送數據了,然后從下一個脈沖起計數并在每個脈沖后檢查RX線上的電位,若是高電位便記做1,低電位便記做0,如此得到8個位的數據,然后在第10個脈沖后,檢測到RX線上的電位為1就知道這幀數據傳送完畢。(注意:單片機的串行發送口(TX)和PC的接收口(RX)是通過串行線直接連接的,所以這兩點的信號是相同的)
例如要發送“1”這個字符,代碼是49(二進制110001,十六進制31H),串口發送時低位在前,如下圖

歸納起來,若想發送數據只要向SBUF送一個字節的數據,然后等TI變為1后,就再發第二個字節依,此類推。
再說說字符和數值的關系,對于電路來說,它不知道什么是字符,什么是數值,只是按高低電位發送一幀幀的電信號。例如00000000代表0(00H),10101010代表170(AAH),11111111代表255(FF),但對于接收方PC就有不同了,大家都知道,電腦下載文本比下載一幅圖像的數據量要小得多,原因就是文本只是用一個代碼來代表一個將要顯示的文字圖像,而這個文字的圖像數據就預先存在自己的電腦里,就是所說的字庫。而你下載一幅圖像,則需要每一個陣點的數據都得傳送,所以數據量很大。西文也是一樣的,也是用代碼來代表一個需要顯示的西文字符圖像,這就是ACSII碼。這樣用一個字節的數(0-255)的范圍就能代表所有的西文字符和常用符號了,例如用數值65(十六進制為41H)代表“A”。用數值49(十六進制為31H)代表“1”,我只要向SBUF里輸入值65,PC只要以字符方式接收,就會顯示“A”字。如果以數值方式接收,變量的值就是65。這里面也包括有一些非字符的功能控制符號。例如13代表回車,10代表換行。
下面我們就來試試改寫一下發送程序:試著用送數值和送字符兩種方式發送。同樣是“A”“B”“C”“D”四個字符。
#include
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
TI=1;
TR1=1; //啟動定時器
while(1)
{
SBUF=65; //向SBUF內寫入65的數值,也就是字符“A”的代碼
while(TI==0); //檢測TI,當TI=0時,說明還沒發送完,就循環等待。
TI=0; //當TI=1時,就把TI的值置0,以便下一組發送。
SBUF=66; //向SBUF送數值66.即字符“B”的代碼
while(TI==0);
TI=0;
SBUF=C; //向SBUF送字符“C”
while(TI==0);
TI=0;
SBUF=D"; //向SBUF送字符“D”
while(TI==0);
TI=0;
delay(); //調用延時
delay();
}
}
將程序編譯生成HEX文件后寫入AT89S52。打開我上面用VB6編好那個程序,點擊“接收數據”鈕。如下圖:

它的確按我預想的執行了。只要是節字數據,無論是數值還是字符代友都是可以進行發送的。這是C語言的優點。
另外我們順便看一下原先用printf函數發送生成HEX和直接寫SBUF來成HEX的差別:

上圖的提示顯示了生成的代碼共用了1120個字節。這是用printf函數發送的。
下圖是直接寫SBUF后的編譯提示信息:

哈!直接操作串口緩沖寄存器只用了89個字節。這是直接進行底層操作的優勢。
上面的程序發送“A”“B”“C”“D”四個數據,因為沒有發送回車符,所以一次次的字符都是連續顯示的。我們再修改一下,把要發送的ABCD這四個數據再加兩個代表回車的控制字符數據定義到一個字節數組中變量中,再改用循環的方式來發送,程序如下:
#include
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void)
{
unsigned char buf[ ]={65,66,C,D,13,10}; //定義一個單字節數組最后兩個數值13和10是回車符。
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
//TI=1;
TR1=1; //啟動定時器
while(1)
{
for(i=0;i<6;i++)
{
SBUF=buf[i]; //向串口送出數據
while(TI==0);
TI=0;
}
delay(); //調用延時
delay();
}
}
下面是運行結果:

這樣就和原先printf函數輸出的效果一樣了。
從上面程序我們知道了,如果我們想發送測量的數值數據,可以把用A/D模塊獲得的測量數據賦給buf[ ]數組里的變量,然后就可以進行發送處理了。字符直接用上面的發送方法就行了。
這樣第三項工作也完成?。ò。±哿?,要歇歇,歇歇!
)
第四步:掌握PC端程序如何接收發送字符和數值型數據。
接下來我們來看看PC端的程序如何正常接收并處理收到的數據。字符沒有問題,因為剛才就是顯示的字符,但我主要是想看看采用字符方式接收對于0-255范圍內那些非字符數值能否正常接收和處理。
打開VB6,調出我們原先編寫的程序,在串口控件的屬性中有個InputMode屬性,如果是0就是以字符方式接收,如果是1就是以二進制方式接收。

原先我們就是用了缺省的字符方式。為了便于分析收到的數據,我們得分別修改一下單片機和PC里的程序,首先不能讓單片機不停的發送,而是從PC機先向AT89S52發送一個字符“s”,AT89S52收到并確認是“s”字符后再發送一組數據,發完后停下,等待PC的下次請求。這樣我們可以準確和穩定地看到這組數據的情況。
VB程序的修改如下:
我們在Command1+_Click事件的代碼里添加了一行MSComm1.Output="s",也就是每當我按下“接收數據”鈕時向AT89S52發送了一個"s"字符,然后清空文本框內容,然后啟動定時器子程序Timer1每100毫秒檢查一次有無收到數據,收到數據便顯示出來。
對AT89S52內的程序做修改,程序循環檢查有無收到數據,當的RI=1時便有數據收到,確認收到的數據為字符“s”時,便送出數組內數據。修改程序如下:
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void)
{
unsigned char buf[]={65,66,C,D,E,F};//定義一個單字節數組
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
//TI=1;
TR1=1; //啟動定時器
while(1)
{ if(RI==1) //如果接收到數據則進入以下操作
{if(SBUF==s) //如果收到的數據為“s”字符則進入發送操作
{
for(i=0;i<6;i++)//循環發送出數組buf[ ]的6個數據
{
SBUF=buf[i]; //向串口送出數據
while(TI==0);
TI=0;
delay(); //調用延時子程序
}
RI=0;//上面的發送操作完畢后將標志RI清0等待PC下次請求
}
}
}
}
編譯、生成HEX文件然后將文件寫進AT89S52。我們運行VB6編寫的Scom1程序。
現在我點擊“接收數據”鈕后,文本框內一個字符一個字符的依次顯示出“ABCDEF”如下圖:

當我再次點擊“接收數據”鈕時,文本框先清空,然后再重復上面的顯示。達到我們的要求。PC機每請求一次,單片機就發送一次數據。
接下來我要做的是讓文本框里不再顯示“ABCDEF”這幾個字符,而是要顯示AT89S52發過來的數值,例如我發送65的值,文本框里就顯示65,我發送255,文本框就顯示255,這樣我就能測試出用字符接收的方法能否將一個字節的值(0-255)都能正確接收和顯示。我們把那句Text1.Text = Text1.Text + MSComm1.Input 。改成Text1.Text = Text1.Text + str(asc(MSComm1.Input )),就是把原先字符串變量MSComm1.Input先轉換成ASCII值,再把這個值顯示出來。改完了。我們運行下試試。

上圖顯示的確可以正確顯示出數值,但這僅僅只是有相對應字符幾個代碼數值,我再來試試,非字符的ASCII值,看是否都能正確收到并顯示。先修改AT89S52里我們原先發送的那些數組buf[ ]里的值,讓它們部分不在字符范圍里,看能不能正確接收到顯示。我們將數組unsigned char buf[]={65,66,C,D,E,F};改寫為既有字符也有非字符范圍的數組unsigned char buf[]={0,1,2,D,254,255};

從上圖來看,后兩個數據顯示不對,254沒有顯示出來,255顯示為63,經過修改AT89S52程序,把0-255的數值都發送一遍,發現大于128的數值幾乎都不能正確接收。所以,得出結論:以字符方式接收數值數據是不可行的!
如下圖:

接下來我改變串口控件的InputMode屬性,將它的值改為1,即用二進制讀取來試試。但二進制方式怎么讀取呢,看了很多資料,也試了很多次,終于弄明白了,原來先要定義一個字節型的可變數組,這樣當接收到數據時把接收到的一個或多個數值的首地址變量Mscomm1.input賦給這個字節數組名。于是你就可以運用這個字節數組里的變量了。
我先在VB里修改串口控件的InputMode屬性,如下圖:

然后我要在VB程序里先定義一個單字節Byte類型的數組,將收到的數據變量(Mscomm1.Input)賦給inbuff這個數組名,程序修改如下圖:

AT89S52里程序,基本不動,只是等待PC機發來請求字符“s”。收到請求后,發送0-255的全部數值。
修改的程序如下:
#include
void delay(void) //定義一個延時子程序
{
unsigned int i;
for (i=15535;i>0;i--);
}
void main(void)
{
unsigned char buf[]={3,4,5,D,255,253};//定義一個單字節數組
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定時器1,定時方式為2
PCON=0x80; //設定串口工作方式為1
TCON=0x40; //設定時器1開始計數
TH1=0xfd; //設定波特率為19200
TL1=0xfd; //
//TI=1;
TR1=1; //啟動定時器
while(1)
{ if(RI==1) //如果接收到數據則進入以下操作
{if(SBUF==s) //如果收到的數據為“s”字符則進入發送操作
{for(i=0;i<255;i++)//循環發送出數,這里做為調試,我們先發0-255的數值。
{
SBUF=i; //向串口送出數據,這里不發buf [ ]數組的數據,而是直接發送循環體里的i 值(0-254)。
while(TI==0);
TI=0;
delay(); //調用延時
}
SBUF=255; //上面循環里沒有包括255這個值,這里補發送一次
while(TI==0);
TI=0;
RI=0;//發送完畢將收到請求標志清0等待PC下次請求
}
}
}
}
顯示結果如下:

這樣,所有的數值都是可以正確接收并顯示了。
結論:要接收數值數據,串口控件必須修改為二進制的接收屬性。即:InputMode=1


評論