第67節(jié):利用外部中斷實現(xiàn)模擬串口數(shù)據(jù)的收發(fā)
鴻哥曾經(jīng)親自用外部中斷做過紅外遙控器的數(shù)據(jù)接收,步進電機圓周運動的光電反饋信號檢測,輸液器里瞬間即逝的水滴信號,以及本節(jié)的模擬串口數(shù)據(jù)的接收,其實這些項目的原理都大同小異,會一樣即可觸類旁通其它的。
本文引用地址:http://m.butianyuan.cn/article/201611/319741.htm這一節(jié)要教大家四個知識點:
第一個:如何利用外部中斷實現(xiàn)模擬串口數(shù)據(jù)的收發(fā)。
第二個:在退出外部中斷函數(shù)時,必須通過軟件把外部中斷標志位IE0清零,否則在接收到的數(shù)據(jù)包最后面會多收到一個無效的字節(jié)0xFF。
第三個:實際做項目的時候,盡量利用單片機內(nèi)部自帶的集成串口,不到萬不得已盡量不要用自制的模擬串口,如果非要用本節(jié)講的模擬串口,那么一次接收的數(shù)據(jù)包不要太長,盡可能越短越好,因為自己做的模擬串口在穩(wěn)定性上肯定比不上單片機自帶的串口。這種模擬串口在批量生產(chǎn)時容易因為晶振的誤差,以及外界各地溫度的溫差而影響產(chǎn)品的一致性,是有隱患的。
第四個:用模擬串口時,盡量不要選用動態(tài)數(shù)碼管的顯示方案,因為單片機在收發(fā)串口數(shù)據(jù)時,只能專心干一件事,此時不能中途被動態(tài)數(shù)碼管掃描程序占用。而動態(tài)數(shù)碼管得不到均勻掃描,就會產(chǎn)生略微閃爍的現(xiàn)象瑕疵。
具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:
基于朱兆祺51單片機學習板。當把程序下載到單片機之后,要做以下跳線處理:
單片機原來的P3.1引腳是TI串口輸出引腳,P3.0是RI串口輸入引腳,分別把P3.1和P3.0的黃顏色跳冒去掉,同時也把外部中斷0的引腳P3.2和一根IO口P1.0引腳的換顏色跳冒去掉,把P3.2跳冒的右針連接到P3.0跳冒的左針,作為模擬串口的接收數(shù)據(jù)線。把P1.0跳冒的右針連接到P3.1跳冒的左針,作為模擬串口的發(fā)送數(shù)據(jù)線。
(2)實現(xiàn)功能:
波特率是:9600 。
通過電腦串口調(diào)試助手模擬上位機,往單片機任意發(fā)送一串不超過10個的數(shù)據(jù)包,單片機如實地返回接收到的整包數(shù)據(jù)給上位機。
例如:
(a)上位機發(fā)送數(shù)據(jù):01 02 03 04 05 06 07 08 09 0A
單片機返回: 01 02 03 04 05 06 07 08 09 0A
(b)上位機發(fā)送數(shù)據(jù): 05 07 EE A8 F9
單片機返回: 05 07 EE A8 F9
(3)源代碼講解如下:
#include "REG52.H"
#define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間
#define const_rc_size 20 //接收串口中斷數(shù)據(jù)的緩沖區(qū)數(shù)組大小
#define const_receive_time 5 //如果超過這個時間沒有串口數(shù)據(jù)過來,就認為一串數(shù)據(jù)已經(jīng)全部接收完,這個時間根據(jù)實際情況來調(diào)整大小
/* 注釋一:
* 以下時序脈沖延時參數(shù)我是在keil uVision2 平臺下,Memory Model在small模式,Code Rom Size在Large模式下編譯的,
* 如果在不同keil版本,不同的模式下,編譯出來的程序有可能此參數(shù)會不一樣。
* 以下的時序脈沖延時參數(shù)是需要一步一步慢慢調(diào)的。我一開始的時候先編寫一個簡單的發(fā)送數(shù)據(jù)測試程序,
* 先確調(diào)試出合適的發(fā)送時序延時數(shù)據(jù)。然后再編寫串口接收數(shù)據(jù)的程序,從而調(diào)試出接收時序的延時參數(shù)。
* 比如:我第一步發(fā)送數(shù)據(jù)的測試程序是這樣的:
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
// usart_service(); //串口服務(wù)程序
eusart_send(0x08); //測試程序,讓它不斷發(fā)送數(shù)據(jù)給上位機觀察,確保發(fā)送延時時序的參數(shù)準確性
delay_long(300);
eusart_send(0xE5); //測試程序,讓它不斷發(fā)送數(shù)據(jù)給上位機觀察,確保發(fā)送延時時序的參數(shù)準確性
delay_long(300);
}
}
*/
#define const_t_1 10 //發(fā)送時序延時1 第一步先調(diào)出此數(shù)據(jù)
#define const_t_2 9 //發(fā)送時序延時2 第一步先調(diào)出此數(shù)據(jù)
#define const_r_1 7 //接收時序延時1 第二步再調(diào)出此數(shù)據(jù)
#define const_r_2 9 //接收時序延時2 第二步再調(diào)出此數(shù)據(jù)
void initial_myself(void);
void initial_peripheral(void);
void delay_long(unsigned int uiDelaylong);
void delay_short(unsigned int uiDelayShort);
void delay_minimum(unsigned char ucDelayMinimum); //細分度最小的延時,用char類型一個字節(jié)
void T0_time(void); //定時中斷函數(shù)
void INT0_int(void); //外部0中斷函數(shù),在本系統(tǒng)中是模擬串口的接收中斷函數(shù)。
void usart_service(void); //串口服務(wù)程序,在main函數(shù)里
void eusart_send(unsigned char ucSendData);
unsigned char read_eusart_byte();//從串口讀一個字節(jié)
sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口
sbit ti_dr=P1^0; //模擬串口發(fā)送數(shù)據(jù)的IO口
sbit ri_sr=P3^2; //模擬串口接收數(shù)據(jù)的IO口 也是外部中斷0的復用IO口
unsigned int uiSendCnt=0; //用來識別串口是否接收完一串數(shù)據(jù)的計時器
unsigned char ucSendLock=1; //串口服務(wù)程序的自鎖變量,每次接收完一串數(shù)據(jù)只處理一次
unsigned int uiRcregTotal=0; //代表當前緩沖區(qū)已經(jīng)接收了多少個數(shù)據(jù)
unsigned char ucRcregBuf[const_rc_size]; //接收串口中斷數(shù)據(jù)的緩沖區(qū)數(shù)組
unsigned char ucTest=0;
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
usart_service(); //串口服務(wù)程序
}
}
void usart_service(void) //串口服務(wù)程序,在main函數(shù)里
{
unsigned char i=0;
if(uiSendCnt>=const_receive_time&&ucSendLock==1) //說明超過了一定的時間內(nèi),再也沒有新數(shù)據(jù)從串口來
{
ucSendLock=0; //處理一次就鎖起來,不用每次都進來,除非有新接收的數(shù)據(jù)
//下面的代碼進入數(shù)據(jù)協(xié)議解析和數(shù)據(jù)處理的階段
for(i=0;i
{
eusart_send(ucRcregBuf[i]);
}
uiRcregTotal=0; //清空緩沖的下標,方便下次重新從0下標開始接受新數(shù)據(jù)
}
}
//往串口發(fā)送一個字節(jié)
void eusart_send(unsigned char ucSendData) //往上位機發(fā)送一個字節(jié)的函數(shù)
{
unsigned char i=8;
EA=0; //關(guān)總中斷
ti_dr=0; //發(fā)送啟始位
delay_minimum(const_t_1); //發(fā)送時序延時1 delay_minimum是本程序細分度最小的延時
while(i--)
{
ti_dr=ucSendData&0x01; //先傳低位
delay_minimum(const_t_2); //發(fā)送時序延時2 delay_minimum是本程序細分度最小的延時
ucSendData=ucSendData>>1;
}
ti_dr=1; //發(fā)送結(jié)束位
delay_short(400); //每個字節(jié)之間的延時,這里非常關(guān)鍵,也是最容易出錯的地方。延時的大小請根據(jù)實際項目來調(diào)整
EA=1; //開總中斷
}
//從串口讀取一個字節(jié)
unsigned char read_eusart_byte()
{
unsigned char ucReadData=0;
unsigned char i=8;
delay_minimum(const_r_1); //接收時序延時1 。作用是等過起始位 delay_minimum是本程序細分度最小的延時
while(i--)
{
ucReadData >>=1;
if(ri_sr==1)
{
ucReadData|=0x80; //先收低位
}
if(ri_sr==0) //此處空指令,是為了讓驅(qū)動時序的時間保持一致性
{
;
}
delay_minimum(const_r_2); //接收時序延時2 delay_minimum是本程序細分度最小的延時
}
return ucReadData;
}
void T0_time(void) interrupt 1 //定時中斷
{
TF0=0; //清除中斷標志
TR0=0; //關(guān)中斷
if(uiSendCnt
{
uiSendCnt++; //表面上這個數(shù)據(jù)不斷累加,但是在串口中斷里,每接收一個字節(jié)它都會被清零,除非這個中間沒有串口數(shù)據(jù)過來
ucSendLock=1; //開自鎖標志
}
TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //開中斷
}
void INT0_int(void) interrupt 0 //INT0外部中斷函數(shù)
{
EX0=0; //禁止外部0中斷 這個只是我個人的編程習慣,也可以不關(guān)閉
++uiRcregTotal;
if(uiRcregTotal>const_rc_size) //超過緩沖區(qū)
{
uiRcregTotal=const_rc_size;
}
ucRcregBuf[uiRcregTotal-1]=read_eusart_byte(); //將串口接收到的數(shù)據(jù)緩存到接收緩沖區(qū)里
uiSendCnt=0; //及時喂狗,雖然main函數(shù)那邊不斷在累加,但是只要串口的數(shù)據(jù)還沒發(fā)送完畢,那么它永遠也長不大,因為每個中斷都被清零。
/* 注釋二:
* 注意,此處必須把IE0中斷標志清零,否則在接收到的數(shù)據(jù)包最后面會多收到一個無效的字節(jié)0xFF。
*/
IE0=0; //外部中斷0標志位清零,必須的!
EX0=1; //打開外部0中斷
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i
{
for(j=0;j<500;j++) //內(nèi)嵌循環(huán)的空指令數(shù)量
{
; //一個分號相當于執(zhí)行一條空語句
}
}
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i
{
; //一個分號相當于執(zhí)行一條空語句
}
}
/* 注釋三:
* 由于IO口模擬的串口時序要求很高,所以用的延時函數(shù)盡可能細分度越高越好,以下用一個字節(jié)的延時計時器
*/
void delay_minimum(unsigned char ucDelayMinimum) //細分度最小的延時,用char類型一個字節(jié)
{
unsigned char i;
for(i=0;i
{
; //一個分號相當于執(zhí)行一條空語句
}
}
void initial_myself(void) //第一區(qū) 初始化單片機
{
beep_dr=1; //用PNP三極管控制蜂鳴器,輸出高電平時不叫。
//配置定時器
TMOD=0x01; //設(shè)置定時器0為工作方式1
TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral(void) //第二區(qū) 初始化外圍
{
EX0=1; //允許外部中斷0
IT0=1; //下降沿觸發(fā)外部中斷0 如果是0代表低電平中斷
IP=0x01; //設(shè)置外部中斷0為最高優(yōu)先級,可以打斷低優(yōu)先級中斷服務(wù)。實現(xiàn)中斷嵌套功能
EA=1; //開總中斷
ET0=1; //允許定時中斷
TR0=1; //啟動定時中斷
}
總結(jié)陳詞:
這節(jié)講完了外部中斷的應(yīng)用例子,下一節(jié)我會開始講單片機C語言的多文件編程技巧。很多人也把多文件編程稱作模塊化編程,其實我覺得叫多文件編程會更加符合實際一些。多文件編程有兩個最大的好處,一個是給我們的程序增加了目錄,方便我們查找。另外一個好處是方便移植別人已經(jīng)做好的功能程序模塊,利用這個特點,特別適合團隊一起做大型項目。很多初學者剛開始學多文件編程時,會經(jīng)常遇到重復定義等問題,想知道怎么解決這些問題嗎?欲知詳情,請聽下回分解----單片機C語言的多文件編程技巧。
評論