新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 第67節(jié):利用外部中斷實現(xiàn)模擬串口數(shù)據(jù)的收發(fā)

第67節(jié):利用外部中斷實現(xiàn)模擬串口數(shù)據(jù)的收發(fā)

作者: 時間:2016-11-22 來源:網(wǎng)絡(luò) 收藏
開場白:

鴻哥曾經(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語言的多文件編程技巧。



評論


技術(shù)專區(qū)

關(guān)閉