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