(1)開場白:
串口通訊的協議各種各樣,五花八門。有的協議沒有校驗,有的協議有校驗,它們的存在都有它們的道理。沒有校驗的協議,感覺更簡單直接,傳輸與處理的速度更 加快。有校驗的協議,感覺用起來更加放心點。這節將要介紹我常用校驗的三道防火墻。它們分別是提取數據頭,判斷數據總數,判斷所有字節的累加和。在上一節 中略加修改即可完成本節的實驗。
(2)功能需求:
無論是單片機還是上位機,最好在固定協議前多發送一個填充的無效字節0x00,因為硬件原因,第一個字節往往容易丟失。
通訊協議: EB 00 55 XX YY YY YY YY CY
其中前三位 EB 00 55為數據頭,XX為數據長度,YY為數據,CY為前面所有字節的累加和。累加和的意思是所有數據相加,你只要定義一個字節變量用來接收所有數據的累加和,那么超過一個字節的部分會自動丟棄。比如以下數據:
EB 00 55 04 02 12 56 A2 50
其中 EB 00 55為數據頭,04為數據總數,02 12 56 A2為數據,50為前面所有字節的累加和。累加和可以用電腦系統自帶的計算器來驗證。打開電腦上的計算器,點擊“查看”下拉的菜單,選“科學型”,然后選 左邊的“十六進制”,最后選右邊的“字節”,然后把前面所有的字節相加,它們的和就是50,沒錯吧。
任意時刻,從電腦“串口調試助手”上位機收到的一堆數據中,只要此數據滿足以上我介紹的三道防火墻,那么就往上位機發送“eb 00 aa”表示確認,同時蜂鳴器叫一聲。如果校驗出錯,則往上位機發送“eb 00 55”表示出錯。
(3)硬件原理:
把單片機串口通訊的那兩個引腳經過一個MAX3232之后直接跟電腦的9針串口通訊。我發現很多朋友會選MAX232這個芯片,而我本人更加推薦用 MAX3232。因為MAX232只支持5V,不是寬壓的,而MAX3232不但支持5V,還支持3V。每個人的記憶力都很寶貴,用232串口我只選 MAX3232,不管它是用5V工作還是3V工作。就像74系列的芯片,我的心中只有你(74HC)沒有它(74LS),一樣的道理,74HC是寬 壓,74LS不是寬壓。
(4)源碼適合的單片機:PIC18f4520,晶振為22.1184MHz,波特率115200
(5)源代碼講解如下:
#include<pic18.h> //包含芯片相關頭文件
//補充說明:吳堅鴻程序風格是這樣的,凡是輸出IO后綴都是_dr,凡是輸入的//IO后綴都//是_sr
#define beep_dr LATA2 //蜂鳴器輸出
//補充說明:吳堅鴻程序風格是這樣的,凡是做延時計數閥值的常量
//前綴都用cnt_表示。
#define cnt_voice_time 150 //蜂鳴器響的聲音長短的延時閥值
#define cnt_send 300 //確保接收緩沖區沒有繼續接收數據,是變量
//send_cnt的溢出閥值
Void usart_service(); //串口通訊服務程序,放在main函數里
unsigned char asy_recieve(); //把串口緩沖區的數據一個個提取出來
void eusart_send(unsigned char t_data); //串口發送一個字節的數據
Void Buf_clear() ; //把余下的緩沖區清零
void Delay11(unsigned int MS); //延時函數
//補充說明:吳堅鴻程序風格是這樣的,凡是計數器延時的變量
//后綴都用_cnt表示。
unsigned int voice_time_cnt; //蜂鳴器響的聲音長短的計數延時
unsigned int send_cnt=0; //一串數據從上位機發過來的時候,他們每個字節之間//的延時間隔很短,如果他們的延時間隔一旦超過了這個send_cnt變量的延時,那么就////認為他們的一串數據已經發送完畢
//補充說明:吳堅鴻程序風格是這樣的,凡是涉及統計數量的變量
//后綴都用_total表示。
unsigned int RCREG_total; //統計串口緩沖區已經收了多少個數據
unsigned int RCREG_read_total; //統計已經從串口緩沖區讀出了多少個數據
unsigned char valid_total; //一串數據中的有效數據總量
//補充說明:吳堅鴻程序風格是這樣的,凡是用來更新的標識變量,比如液晶刷屏,或者有新接收的串口數據更新等等,后綴一律用_update表示
Unsigned char send_update=0; //一旦有數據從上位機發送過來,就會引發串口接收中////斷,在串口中斷里,我把send_update=1表示目 前正在接收數據,警告單片機先不要//猴急,等串口中斷把所有從上位機連續發送過來的一堆數據接收完,再處理。那么什么///時候才知道發送的數據已經發 送完畢了呢?用send_cnt識別。因為在串口中斷里,我///每次都會把send_cnt=0,而在main函數里,一旦發現 send_update==1,send_cnt就//會開始自加,當它超過某個數值的時候,就會自動把send_update=0,表示目前已經沒// 有數據發送了。而如果有數據不斷從上位機傳來,send_cnt永遠也不會超過某個數值,//因為每個中斷它都被清零,這個原理跟看門口狗喂狗的原理很 像。
//補充說明:吳堅鴻程序風格是這樣的,凡是用來接收數據的緩沖區數組后綴都用_buf表//示
Unsigned char RCREG_buf[50]; //串口接收緩沖區,讀者可以根據實際項目設置大小
Unsigned char RCREG_buf_temp[50]; //臨時處理串口數據的緩沖區,可以不用那么大
//補充說明:吳堅鴻程序風格是這樣的,凡是自鎖變量名, 后綴都用_lock表示。
Unsigned char send_lock=0;
//補充說明:吳堅鴻程序風格是這樣的,凡是在main函數中用的中間變量,前綴m_,后//綴用_char或者_int表示類型
Unsigned int m_int; //中間變量,只要是用在main函數里,誰都可以重復用。
Unsigned char receive_sum; //一串數據中的累加和,注意,必須定義成字節變//量,這樣運算中它會自動丟棄超過一個字節的位。
//主程序
main()
{
ADCON0=0x00;
ADCON1=0x0f; //全部為數字信號
ADCON2=0xa1; //右對齊
RBPU=0; //上拉電阻
SSPEN=0; //決定RA5不作為串口
TRISA2=0; //蜂鳴器輸出
BRG16=0; //設置串口通信寄存器
BRGH=0;
SPBRGH=0x00;
SPBRG=0x02; //22.1184MHz晶振,115200波特率
SYNC=0;
SPEN=1;
TX9=0;
TXEN=1;
TXIF=1;
RX9=0;
CREN=1;
RCIE=1;
PEIE=1;
GIE=1;
T1CON=0x24; //定時器中斷配置
TMR1H=0xFE;
TMR1L=0xEF;
TMR1IF=0;
TMR1IE=1;
TMR1ON=1;
TMR1IE=1;
//補充說明,以上的內容為寄存器配置,每種不同的單片機會有點差異,
//大家不用過度關注以上寄存器的配置,只要知道有這么一回事即可
beep_dr=0; //關蜂鳴器,上電初始化IO
while(1)
{
CLRWDT(); //喂看門狗,大家不用過度關注此行
usart_service(); //串口通訊服務
}
}
//中斷
void interrupt timer1rbint(void)
{
if(RCIE==1&&RCIF==1) //串口中斷,一次只能接受一個字節
{
RCIE=0;
RCIF=0;
++RCREG_total; //以下代碼是鴻哥的在所有串口項目中用到的標準代碼
if(RCREG_total>50) //超過緩沖區
{
RCREG_total=50;
}
RCREG_buf[RCREG_total-1]=RCREG; //依次把上位機來的數據存入數組緩沖區
send_update=1; //通知單片機目前正在接收數據
send_cnt=0; //及時喂狗,雖然main函數那邊不斷在累加,但是只要串口的數//據還沒發送完畢,那么它永遠也長不大,因為每個中斷都被清零,很可憐。
RCIE=1;
}
if(TMR1IE==1&&TMR1IF==1) //定時中斷
{
TMR1IF=0; //定時中斷標志位關閉
TMR1ON=0; //定時中斷開關關閉
if(voice_time_cnt) //控制蜂鳴器聲音的長短
{
beep_dr=1; //蜂鳴器響
--voice_time_cnt; //蜂鳴器響的聲音長短的計數延時
}
else
{
Asm(“nop”); //添加此行空指令為了使else的內容跟if的內容對稱,意義////不大
beep_dr=0; //蜂鳴器停止
}
TMR1H=0xFe; //重新設置定時時間間隔
TMR1L=0x00;
TMR1ON=1; //定時中斷開關打開
}
}
void usart_service() //串口服務程序,在main函數里
{
if(send_update==1) //說明目前串口正在接收數據,不要讀緩沖區數據
{
send_lock=1; //開自鎖標志
++send_cnt; //只要有數據接收,send_cnt每次都被串口中斷清零
if(send_cnt>cnt_send) //延時一段時間,確認緩沖區沒有繼續接受數據
{
send_cnt=0;
send_update=0;
}
}
Else //說明當前沒有繼續接收數據了
{
if(send_lock==1) //在數據已經接收完畢,并且還沒有處理過數據的情況下
{
send_lock=0; //處理一次就鎖起來,不用每次都進來,除非有新接收的數據
while(RCREG_read_total<RCREG_total) //說明還沒有把緩沖區的數據讀取完
{
CLRWDT();
RCREG_buf_temp[0]= RCREG_buf_temp[1]; //數據移動,方便截取關鍵字
RCREG_buf_temp[1]= RCREG_buf_temp[2];
RCREG_buf_temp[2]=asy_recieve();
if(RCREG_buf_temp[0]==0xeb&& RCREG_buf_temp[1]==0x00&& RCREG_buf_temp[2]==0x55) //數據頭”eb 00 55”判斷
{
//把余下有效的數據都取出來
RCREG_buf_temp[3]=asy_recieve(); //數據量
Valid_total= RCREG_buf_temp[3];
For(m_int=0;m_int< Valid_total;m_int++) //提取有效數據
{
RCREG_buf_temp[4+ m_int]=asy_recieve();
}
RCREG_buf_temp[4+ Valid_total]=asy_recieve(); //自帶校驗和
Receive_sum=0; //累加校驗和計算
For(m_int=0;m_int< (Valid_total+4);m_int++) //累加校驗和計算
{
Receive_sum=Receive_sum+ RCREG_buf_temp[m_int];
}
If(Receive_sum== RCREG_buf_temp[4+ Valid_total]) //校驗和正確
{
RCREG_buf_temp[0]=0; //把臨時處理數據清零,方便下次接收
RCREG_buf_temp[1]=0;
RCREG_buf_temp[2]=0;
eusart_send(0x00); //串口發送多一個填充無效字節,避免硬件原因引起第一個字節丟失
eusart_send(0xeb); //串口發送應答的數據
eusart_send(0x00); //串口發送應答的數據
eusart_send(0xaa); //串口發送應答的數據
voice_time_cnt= cnt_voice_time; //蜂鳴器響“滴”一聲就停
}
Else //校驗和出錯
{
RCREG_buf_temp[0]=0; //把臨時處理數據清零,方便下次接收
RCREG_buf_temp[1]=0;
RCREG_buf_temp[2]=0;
eusart_send(0x00); //串口發送多一個填充無效字節,避免硬件原因引起第一個字節丟失
eusart_send(0xeb); //串口發送應答的數據
eusart_send(0x00); //串口發送應答的數據
eusart_send(0x55); //串口發送應答的數據
}
break; //退出循環
}
}
Buf_clear(); //把余下的緩沖區清零,方便下一堆數據接收與處理
}
}
}
Void Buf_clear() //把余下的緩沖區清零
{
Unsigned char buf_clear_temp;
while(RCREG_read_total<RCREG_total) //說明還沒有把緩沖區的數據讀取完
{
CLRWDT();
buf_clear_temp =asy_recieve();
}
}
unsigned char asy_recieve() //把串口緩沖區的數據一個個提取出來
{
unsigned char RCREG_dt;
++RCREG_read_total; //已經讀出了多少個數據
RCREG_dt=RCREG_buf[RCREG_read_total -1];
if(RCREG_read_total >=RCREG_total) //只要把全部數據都讀完,馬上把緩沖區清零
{
RCREG_read_total =0;
RCREG_total=0;
}
return RCREG_dt;
}
void eusart_send(unsigned char t_data) //串口發送一個字節的數據
{
unsigned int error_delay;
TXREG=t_data; //發送數據
error_delay=0;//等待把數據發送完畢
while(1) //這里也可以省略,直接用延時替代
{
CLRWDT();
if(TXIF==1) //等待把數據發送完畢
{
break;
}
Else
{
++error_delay;
if(error_delay>200) //超時也要退出,不能死等
{
break;
}
}
}
Delay11(1); //此處最玄機,要特別注意。每發送完一個字節,由于不同的項目,這//里的延時間隔都不一樣,讀者根據實際情況來改。這里最容易出問題,必須要延時,尤 其是連續發送一堆數據的時候。讀者也可以自己把這種死延時改成計數延時的方式。
}
//延時函數
void Delay11(unsigned int MS)
{
unsigned char us,usn;
while(MS!=0) //for 12M
{
CLRWDT();
usn = 2;
while(usn!=0)
{ CLRWDT();
us=0xf5;
while (us!=0){us--;};
usn--;
}
MS--;
}
}
(6)小結:
累加和就是把前面所有字節相加,然后把結果放在一個字節的變量上,超過一個字節范圍的部分會自動丟棄。
|