【134.1 應用層的“半雙工”和“全雙工”。】
應用層的“半雙工”。主機與從機在程序應用層采用“一問一答”的查詢模式,主機是主動方,從機是被動方,主機問一句從機答一句,“聊天對話“的氛圍很無趣很呆板。從機沒有發言權,當從機想主動給主機發送一些數據時就“憋得慌”。半雙工適用于大多數單向通訊的場合。
應用層的“全雙工”。主機與從機在程序應用層可以實現任意雙向的通訊,這時從機也可變為主機,主機也可變為從機,就像兩個人平時聊天,無所謂誰是從機誰是主機,也無所謂非要對方對我每句話都要應答附和(只要對方能聽得清我講什么就可以),“聊天對話”的氛圍很生動很活潑。全雙工適用于通訊更復雜的場合。
本節從“半雙工“開始講,讓初學者先熟悉雙機通訊的基本程序框架,下一節再講“全雙工“。
【134.2 雙機通訊的三類核心函數。】
雙機通訊在程序框架層面有三類核心的涵數,它們分別是:通訊過程的控制涵數,發送的隊列驅動涵數,接收數據后的處理涵數。
“通訊過程的控制涵數”的數量可以不止1個,每一個通訊事件都對應一個獨立的“通訊過程的控制涵數”,根據通訊事件的數量,一個系統往往有N個“通訊過程的控制涵數”。顧名思義,它負責過程的控制,無論什么項目,凡是過程控制我都首選switch語句。此函數是屬于上層應用的函數,它的基礎底層是“發送的隊列驅動涵數”和“接收數據后的處理涵數”這兩個函數。
“發送的隊列驅動涵數”在系統中只有1個“發送的隊列驅動涵數”,負責“通訊管道的占用”的分配,負責數據的具體發送。當同時存在很多“待發送”的請求指令時,此函數會根據“if ,else if...”的優先級,像隊列一樣安排各指令發送的先后順序,確保各指令不會發生沖突。此函數屬于底層的驅動函數。
“接收數據后的處理涵數”在系統中只有1個,負責處理當前接收到的數據,它既屬于“底層函數”也屬于“應用層函數”,二者成分皆有。
我們一旦深刻地領悟了這三類函數各自的分工與關聯方式,將來應付再復雜的通訊系統都會脈絡清析,游刃有余。
【134.3 例程的功能需求。】
上位機與下位機都有一個一模一樣的57個字節的大數組。在上位機端按下獨立按鍵K1后,上位機開始與下位機建立通訊,上位機的目的是讀取下位機的那57個字節的大數組,分批讀取,每批讀取10個字節,最后一批讀取的是余下的7個字節。讀取完畢后,上位機把讀取到的大數組與自己的大數組進行對比:如果相等,表示通訊正確,蜂鳴器“長鳴”一聲;如果不相等,表示通訊錯誤,蜂鳴器“短鳴”一聲。在通訊過程中,如果出現通信異常(比如因為接收超時或者接收某批次數據錯誤而導致重發的次數超過最大限制的次數)也表示通訊錯誤,蜂鳴器也會發出“短鳴”一聲的提示。
【134.4 例程的電路圖。】
兩個單片機進行232串口通訊,一共需要3根線:1根作為共地線,其它2根是交叉的收發數據線(上位機的“接收線”連接下位機的“發送線”,上位機的“發送線”連接下位機的“接收線”),如下圖所示:

上圖134.4.1 雙機通訊的232串口接線圖

上圖134.4.2 上位機的獨立按鍵

上圖134.4.3 上位機的有源蜂鳴器
【134.5 例程的通訊協議。】
(1) 通訊參數。波特率9600,校驗位NONE(無),數據位8,停止位1。
(二)上位機讀取下位機的數組容量的大小的指令。
(1)上位機發送十六進制的數據:EB 01 00 00 00 07 ED。
EB是數據頭。
01是指令類型,01代表請求下位機返回大數組的容量大小。
00 00 00 07代表整個指令的數據長度。
ED是前面所有字節數據的異或結果,用來作為校驗數據。
(2)下位機返回十六進制的數據:EB 01 00 00 00 0C XX XX XX XX ZZ。
EB是數據頭。
01是指令類型,01代表返回大數組的容量大小。
00 00 00 0B代表整個指令的數據長度
XX XX XX XX代表大數組的容量大小
ZZ是前面所有字節數據的異或結果,用來作為校驗數據。
(三)上位機讀取下位機的大數組的分段數據的指令。
(1)上位機發送十六進制的數據:EB 02 00 00 00 0F RR RR RR RR YY YY YY YY ZZ
EB是數據頭
02是指令類型,02代表請求下位機返回當前分段的數據。
00 00 00 0F代表整個指令的數據長度
RR RR RR RR代表請求下位機返回的數據的“請求起始地址”
YY YY YY YY代表請求下位機從“請求起始地址”一次返回的數據長度
ZZ是前面所有字節數據的異或結果,用來作為校驗數據。
(2)下位機返回十六進制的數據:EB 02 TT TT TT TT RR RR RR RR YY YY YY YY HH ...HH ZZ
EB是數據頭
02是指令類型,02代表返回大數組當前分段的數據
TT TT TT TT 代表整個指令的數據長度
RR RR RR RR代表下位機返回數據時的“請求起始地址”
YY YY YY YY代表下位機從“請求起始地址”一次返回的數據長度
HH ...HH代表中間有效的數據內容
ZZ是前面所有字節數據的異或結果,用來作為校驗數據。
【134.6 解決本節例程編譯不過去的方法。】
因為本節用到的全局變量比較多,如果有初學者在編譯的時候出現“error C249: 'DATA': SEGMENT TOO LARGE”的提示,請按下圖的窗口提示來設置一下編譯的環境。

上圖134.5.1 設置編譯的環境
【134.7 例程的上位機程序。】
\#include "REG52.H"
\#define RECE\_TIME\_OUT 2000 //通訊過程中字節之間的超時時間2000ms
\#define REC\_BUFFER\_SIZE 30 //常規控制類數組的長度
\#define KEY\_FILTER\_TIME 25 //按鍵濾波的“穩定時間”
void usart(void); //串口接收的中斷函數
void T0\_time(); //定時器的中斷函數
void BigBufferUsart(void); //讀取下位機大數組的“通訊過程的控制涵數”。三大核心函數之一
void QueueSend(void); //發送的隊列驅動涵數。三大核心函數之一
void ReceDataHandle(void); //接收數據后的處理涵數。三大核心函數之一
void UsartTask(void); //串口收發的任務函數,放在主函數內
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //異或的算法函數
unsigned long u32BufferSize);
//比較兩個數組的是否相等。返回1代表相等,返回0代表不相等
//u32BufferSize是參與對比的數組的大小
unsigned char CmpTwoBufferIsSame(const unsigned char \*pCu8Buffer\_1,
const unsigned char \*pCu8Buffer\_2,
unsigned long u32BufferSize);
void UsartSendByteData(unsigned char u8SendData); //發送一個字節的底層驅動函數
//發送帶協議的函數
void UsartSendMessage(const unsigned char \*pCu8SendMessage,unsigned long u32SendMaxSize);
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void VoiceScan(void);
void KeyScan(void);
void KeyTask(void);
sbit P3\_4=P3^4; //蜂鳴器的驅動輸出口
sbit KEY\_INPUT1=P2^2; //K1按鍵識別的輸入口。
//下面表格數組的數據與下位機的表格數據一模一樣,目的用來檢測接收到的數據是否正確
code unsigned char Cu8TestTable\[\]=
{
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,
0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,
0x51,0x52,0x53,0x54,0x55,0x56,0x57
};
unsigned char Gu8ReceTable\[57\]; //從下位機接收到的表格數據的數組
//把一些針對某個特定事件的全局變量放在一個結構體內,可以讓全局變量的分類更加清晰
struct StructBigBufferUsart //控制讀取大數組的通訊過程的結構體
{
unsigned char u8Status; //通訊過程的狀態 0為初始狀態 1為通訊成功 2為通訊失敗
unsigned char u8ReSendCnt; //重發計數器
unsigned char u8Step; //通訊過程的步驟
unsigned char u8Start; //通訊過程的啟動
unsigned long u32NeedSendSize; //一共需要發送的全部數據量
unsigned long u32AlreadySendSize; //實際已經發送的數據量
unsigned long u32CurrentAddr; //當前批次需要發送的起始地址
unsigned long u32CurrentSize; //當前批次從起始地址開始發送的數據量
unsigned char u8QueueSendTrig;//隊列驅動函數的發送的啟動
unsigned char u8QueueSendBuffer\[30\]; //隊列驅動函數的發送指令的數組
unsigned char u8QueueStatus; //隊列驅動函數的通訊狀態 0為初始狀態 1為通訊成功 2為通訊失敗
};
unsigned char Gu8QueueReceUpdate=0; //1代表“隊列發送數據后,收到了新的數據”
struct StructBigBufferUsart GtBigBufferUsart;//此結構體變量專門用來控制讀取大數組的通訊事件
volatile unsigned char vGu8BigBufferUsartTimerFlag=0; //過程控制的超時定時器
volatile unsigned int vGu16BigBufferUsartTimerCnt=0;
volatile unsigned char vGu8QueueSendTimerFlag=0; //隊列發送的超時定時器
volatile unsigned int vGu16QueueSendTimerCnt=0;
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;
unsigned char Gu8SendByteFinish=0; //發送一個字節完成的標志
unsigned char Gu8ReceBuffer\[REC\_BUFFER\_SIZE\]; //常規控制類的小內存
unsigned char \*pGu8ReceBuffer; //用來切換接收內存的“中轉指針”
unsigned long Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大緩存
unsigned long Gu32ReceCnt=0; //接收緩存數組的下標
unsigned char Gu8ReceStep=0; //接收中斷函數里的步驟變量
unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作變量。
unsigned char Gu8ReceType=0; //接收的數據類型
unsigned char Gu8Rece\_Xor=0; //接收的異或
unsigned long Gu32ReceDataLength=0; //接收的數據長度
unsigned char Gu8FinishFlag=0; //是否已接收完成一串數據的標志
unsigned long \*pu32Data; //用于數據轉換的指針
volatile unsigned char vGu8ReceTimeOutFlag=0;//通訊過程中字節之間的超時定時器的開關
volatile unsigned int vGu16ReceTimeOutCnt=0; //通訊過程中字節之間的超時定時器,“喂狗”的對象
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
UsartTask(); //串口收發的任務函數
KeyTask();
}
}
void KeyTask(void) //按鍵任務函數,放在主函數內
{
if(0==vGu8KeySec)
{
return; //按鍵的觸發序號是0意味著無按鍵觸發,直接退出當前函數,不執行此函數下面的代碼
}
switch(vGu8KeySec) //根據不同的按鍵觸發序號執行對應的代碼
{
case 1: //1號按鍵。K1的獨立按鍵
//GtBigBufferUsart.u8Start在開機初始化函數里必須初始化為0!這一步很關鍵!
if(0==GtBigBufferUsart.u8Start) //只有在還沒有啟動的情況下,才能啟動
{
GtBigBufferUsart.u8Status=0; //通訊過程的狀態 0為初始狀態
GtBigBufferUsart.u8Step=0; //通訊過程的步驟 0為從當前開始的步驟
GtBigBufferUsart.u8Start=1; //通訊過程的啟動
}
vGu8KeySec=0; //響應按鍵服務處理程序后,按鍵編號必須清零,避免一致觸發
break;
}
}
/\* 注釋一:
\* 每一個通訊事件都對應的一個獨立的“通訊過程的控制涵數”,一個系統中有多少個通訊事件,就存在
\* 多少個“通訊過程的控制涵數”。該函數負責某個通訊事件從開始到結束的整個過程。比如本節項目,
\* 在通訊過程中,如果發現接收到的數據錯誤,則繼續啟動重發的機制。當發現接收到的累加字節數等于
\* 預期想要接收的數量時,則結束這個通訊的事件。
\*/
void BigBufferUsart(void) //讀取下位機大數組的“通訊過程的控制涵數”
{
static const unsigned char SCu8ReSendCntMax=3; //重發的次數
static unsigned long \*pSu32Data; //用于數據與數組轉換的指針
switch(GtBigBufferUsart.u8Step) //過程控制,我首選switch語句!
{
case 0:
if(1==GtBigBufferUsart.u8Start) //通訊過程的啟動
{
//根據實際項目需要,在此第0步驟里可以添加一些初始化相關的數據
GtBigBufferUsart.u8ReSendCnt=0; //重發計數器清零
GtBigBufferUsart.u8Step=1; //切換到下一步
}
break;
//-----------先發送“讀取下位機的數組容量的大小的指令”---------------------
//-----------EB 01 00 00 00 07 ED ---------------------
case 1:
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //數據頭
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x01; //數據類型 讀取數組容量大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=7; //數據長度 本條指令的數據總長是7個字節
//異或算法的函數
GtBigBufferUsart.u8QueueSendBuffer\[6\]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
6) ; //最后一個字節不納入計算
//隊列驅動函數的狀態 0為初始狀態 1為通訊成功 2為通訊失敗
GtBigBufferUsart.u8QueueStatus=0; //隊列驅動函數的通訊狀態
GtBigBufferUsart.u8QueueSendTrig=1;//隊列驅動函數的發送的啟動
vGu8BigBufferUsartTimerFlag=0;
vGu16BigBufferUsartTimerCnt=2000;
vGu8BigBufferUsartTimerFlag=1; //過程控制的超時定時器的啟動
GtBigBufferUsart.u8Step=2; //切換到下一步
break;
case 2: //發送之后,等待下位機返回的數據的狀態
if(1==GtBigBufferUsart.u8QueueStatus) //當前批次的接收到的數據成功
{
GtBigBufferUsart.u8ReSendCnt=0; //重發計數器清零
GtBigBufferUsart.u32AlreadySendSize=0; //實際已經發送的數據量清零
GtBigBufferUsart.u32CurrentAddr=0; //當前批次需要發送的起始地址
GtBigBufferUsart.u32CurrentSize=10; //從當前批次起始地址開始發送的數據量
GtBigBufferUsart.u8Step=3; //切換到下一步
}
else if(2==GtBigBufferUsart.u8QueueStatus) //當前批次的接收到的數據失敗
{
GtBigBufferUsart.u8ReSendCnt++;
if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重發次數
{
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //結束當前的過程通訊
GtBigBufferUsart.u8Status=2; //對外宣布“通訊失敗”
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30; //讓蜂鳴器“短鳴”一聲
vGu8BeepTimerFlag=1;
}
else
{
GtBigBufferUsart.u8Step=1; //返回上一步,重發當前段的數據
}
}
else if(0==vGu16BigBufferUsartTimerCnt) //當前批次在等待接收返回數據時,超時
{
GtBigBufferUsart.u8ReSendCnt++;
if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重發次數
{
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //結束當前的過程通訊
GtBigBufferUsart.u8Status=2; //對外宣布“通訊失敗”
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30; //讓蜂鳴器“短鳴”一聲
vGu8BeepTimerFlag=1;
}
else
{
GtBigBufferUsart.u8Step=1; //返回上一步,重發當前段的數據
}
}
break;
//-----------接著發送“讀取下位機的大數組的分段數據的指令”---------------------
//-----------EB 02 00 00 00 0F RR RR RR RR YY YY YY YY ZZ ---------------------
case 3:
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //數據頭
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x02; //數據類型 讀取分段數據
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=15; //數據長度 本條指令的數據總長是15個字節
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=GtBigBufferUsart.u32CurrentAddr; //當前批次需要發送的起始地址
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4+4\];
\*pSu32Data=GtBigBufferUsart.u32CurrentSize; //從當前批次起始地址發送的數據量
//異或算法的函數
GtBigBufferUsart.u8QueueSendBuffer\[14\]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
14); //最后一個字節不納入計算
//隊列驅動函數的狀態 0為初始狀態 1為通訊成功 2為通訊失敗
GtBigBufferUsart.u8QueueStatus=0; //隊列驅動函數的通訊狀態
GtBigBufferUsart.u8QueueSendTrig=1;//隊列驅動函數的發送的啟動
vGu8BigBufferUsartTimerFlag=0;
vGu16BigBufferUsartTimerCnt=2000;
vGu8BigBufferUsartTimerFlag=1; //過程控制的超時定時器的啟動
GtBigBufferUsart.u8Step=4; //切換到下一步
break;
case 4: //發送之后,等待下位機返回的數據的狀態
if(1==GtBigBufferUsart.u8QueueStatus) //當前批次的接收到的數據成功
{
//更新累加當前實際已經發送的字節數
GtBigBufferUsart.u32AlreadySendSize=GtBigBufferUsart.u32AlreadySendSize+
GtBigBufferUsart.u32CurrentSize;
//更新下一步起始的發送地址
GtBigBufferUsart.u32CurrentAddr=GtBigBufferUsart.u32CurrentAddr+
GtBigBufferUsart.u32CurrentSize;
//更新下一步從起始地址開始發送的字節數
if((GtBigBufferUsart.u32CurrentAddr+GtBigBufferUsart.u32CurrentSize)>
GtBigBufferUsart.u32NeedSendSize) //最后一段數據的臨界點的判斷
{
GtBigBufferUsart.u32CurrentSize=GtBigBufferUsart.u32NeedSendSize-
GtBigBufferUsart.u32CurrentAddr;
}
else
{
GtBigBufferUsart.u32CurrentSize=10;
}
//判斷是否已經把整個大數組的57個字節都已經接收完畢。如果已經接收完畢,則
//結束當前通信;如果還沒結束,則繼續請求下位機發送下一段新數據。
if(GtBigBufferUsart.u32AlreadySendSize>=GtBigBufferUsart.u32NeedSendSize)
{
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //結束當前的過程通訊
if(1==CmpTwoBufferIsSame(Cu8TestTable, //如果接收的數據與存儲的相等
Gu8ReceTable,
57))
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=1000; //讓蜂鳴器“長鳴”一聲
vGu8BeepTimerFlag=1;
GtBigBufferUsart.u8Status=1; //對外宣布“通訊成功”
}
else
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30; //讓蜂鳴器“短鳴”一聲
vGu8BeepTimerFlag=1;
GtBigBufferUsart.u8Status=2; //對外宣布“通訊失敗”
}
}
else
{
GtBigBufferUsart.u8ReSendCnt=0; //重發計數器清零
GtBigBufferUsart.u8Step=3; //返回上一步,繼續發下一段的新數據
}
}
else if(2==GtBigBufferUsart.u8QueueStatus) //當前批次的接收到的數據失敗
{
GtBigBufferUsart.u8ReSendCnt++;
if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重發次數
{
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //結束當前的過程通訊
GtBigBufferUsart.u8Status=2; //對外宣布“通訊失敗”
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30; //讓蜂鳴器“短鳴”一聲
vGu8BeepTimerFlag=1;
}
else
{
GtBigBufferUsart.u8Step=3; //返回上一步,重發當前段的數據
}
}
else if(0==vGu16BigBufferUsartTimerCnt) //當前批次在等待接收返回數據時,超時
{
GtBigBufferUsart.u8ReSendCnt++;
if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重發次數
{
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //結束當前的過程通訊
GtBigBufferUsart.u8Status=2; //對外宣布“通訊失敗”
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30; //讓蜂鳴器“短鳴”一聲
vGu8BeepTimerFlag=1;
}
else
{
GtBigBufferUsart.u8Step=3; //返回上一步,重發當前段的數據
}
}
break;
}
}
/\* 注釋二:
\* 整個項目中只有一個“發送的隊列驅動涵數”,負責“通訊管道的占用”的分配,負責數據的具體發
\* 送。當同時存在很多“待發送”的請求指令時,此函數會根據“if ,else if...”的優先級,像隊列一
\* 樣安排各指令發送的先后順序,確保各指令不會發生沖突。
\*/
void QueueSend(void) //發送的隊列驅動涵數
{
static unsigned char Su8Step=0;
switch(Su8Step)
{
case 0: //分派即將要發送的任務
if(1==GtBigBufferUsart.u8QueueSendTrig)
{
GtBigBufferUsart.u8QueueSendTrig=0; //及時清零。驅動層,不管結果,只發一次。
Gu8QueueReceUpdate=0; //接收應答數據的狀態恢復初始值
//發送帶指令的數據
UsartSendMessage((const unsigned char \*)&GtBigBufferUsart.u8QueueSendBuffer\[0\],
30);
vGu8QueueSendTimerFlag=0;
vGu16QueueSendTimerCnt=2000;
vGu8QueueSendTimerFlag=1; //隊列發送的超時定時器
Su8Step=1;
}
// else if(...) //當有其它發送的指令時,可以在此處繼續添加判斷,越往下優先級越低
// else if(...) //當有其它發送的指令時,可以在此處繼續添加判斷,越往下優先級越低
break;
case 1: //發送之后,等待下位機的應答。驅動層,只管有沒有應答,不管應答對不對。
if(1==Gu8QueueReceUpdate) //如果“接收數據后的處理涵數”接收到應答數據
{
Su8Step=0; //返回上一步繼續處理其它“待發送的指令”
}
if(0==vGu16QueueSendTimerCnt) //發送指令之后,等待應答超時
{
Su8Step=0; //返回上一步繼續處理其它“待發送的指令”
}
break;
}
}
/\* 注釋三:
\* 整個項目中只有一個“接收數據后的處理涵數”,負責即時處理當前接收到的數據。
\*/
void ReceDataHandle(void) //接收數據后的處理涵數
{
static unsigned long \*pSu32Data; //數據轉換的指針
static unsigned long i;
static unsigned char Su8Rece\_Xor=0; //計算的“異或”
static unsigned long Su32CurrentAddr; //讀取的起始地址
static unsigned long Su32CurrentSize; //讀取的發送的數據量
if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及時更新一次“超時檢測的定時器”的初值
{
Gu8ReceFeedDog=0;
vGu8ReceTimeOutFlag=0;
vGu16ReceTimeOutCnt=RECE\_TIME\_OUT;//更新一次“超時檢測的定時器”的初值
vGu8ReceTimeOutFlag=1;
}
else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超時,并且步驟不在接頭暗號的步驟
{
Gu8ReceStep=0; //串口接收數據的中斷函數及時切換回接頭暗號的步驟
}
if(1==Gu8FinishFlag) //1代表已經接收完畢一串新的數據,需要馬上去處理
{
switch(Gu8ReceType) //接收到的數據類型
{
case 0x01: //讀取下位機的數組容量的大小
Gu8QueueReceUpdate=1; //告訴“隊列驅動函數”收到了新的應答數據
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“異或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //計算“異或”
if(Gu32ReceDataLength>=11&& //接收到的數據長度必須大于或者等于11個字節
Su8Rece\_Xor==Gu8Rece\_Xor) //驗證“異或”,“計算的”與“接收的”是否一致
{
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //數據轉換。
GtBigBufferUsart.u32NeedSendSize=\*pSu32Data; //提取將要接收數組的大小
GtBigBufferUsart.u8QueueStatus=1; //告訴“過程控制函數”,當前通訊成功
}
else
{
GtBigBufferUsart.u8QueueStatus=2; //告訴“過程控制函數”,當前通訊失敗
}
break;
case 0x02: //讀取下位機的分段數據
Gu8QueueReceUpdate=1; //告訴“隊列驅動函數”收到了新的應答數據
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“異或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //計算“異或”
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //數據轉換。
Su32CurrentAddr=\*pSu32Data; //讀取的起始地址
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6+4\]; //數據轉換。
Su32CurrentSize=\*pSu32Data; //讀取的發送的數據量
if(Gu32ReceDataLength>=11&& //接收到的數據長度必須大于或者等于11個字節
Su8Rece\_Xor==Gu8Rece\_Xor&& //驗證“異或”,“計算的”與“接收的”是否一致
Su32CurrentAddr==GtBigBufferUsart.u32CurrentAddr&& //驗證“地址”,相當于驗證“動態密匙”
Su32CurrentSize==GtBigBufferUsart.u32CurrentSize) //驗證“地址”,相當于驗證“動態密匙”
{
for(i=0;i<Su32CurrentSize;i++)
{
//及時把接收到的數據存儲到Gu8ReceTable數組
Gu8ReceTable\[Su32CurrentAddr+i\]=Gu8ReceBuffer\[6+4+4+i\];
}
GtBigBufferUsart.u8QueueStatus=1; //告訴“過程控制函數”,當前通訊成功
}
else
{
GtBigBufferUsart.u8QueueStatus=2; //告訴“過程控制函數”,當前通訊失敗
}
break;
}
Gu8FinishFlag=0; //上面處理完數據再清零標志,為下一次接收新的數據做準備
}
}
void UsartTask(void) //串口收發的任務函數,放在主函數內
{
BigBufferUsart(); //讀取下位機大數組的“通訊過程的控制涵數”
QueueSend(); //發送的隊列驅動涵數
ReceDataHandle(); //接收數據后的處理涵數
}
void usart(void) interrupt 4 //串口接發的中斷函數,中斷號為4
{
if(1==RI) //接收完一個字節后引起的中斷
{
RI = 0; //及時清零,避免一直無緣無故的進入中斷。
if(0==Gu8FinishFlag) //1代表已經完成接收了一串新數據,并且禁止接收其它新的數據
{
Gu8ReceFeedDog=1; //每接收到一個字節的數據,此標志就置1及時更新定時器的值。
switch(Gu8ReceStep)
{
case 0: //“前部分的”數據頭。接頭暗號的步驟。
Gu8ReceBuffer\[0\]=SBUF; //直接讀取剛接收完的一個字節的數據。
if(0xeb==Gu8ReceBuffer\[0\]) //等于數據頭0xeb,接頭暗號吻合。
{
Gu32ReceCnt=1; //接收緩存的下標
Gu8ReceStep=1; //切換到下一個步驟,接收其它有效的數據
}
break;
case 1: //“前部分的”數據類型和長度
Gu8ReceBuffer\[Gu32ReceCnt\]=SBUF; //直接讀取剛接收完的一個字節的數據。
Gu32ReceCnt++; //每接收一個字節,數組下標都自加1,為接收下一個數據做準備
if(Gu32ReceCnt>=6) //前6個數據。接收完了“數據類型”和“數據長度”。
{
Gu8ReceType=Gu8ReceBuffer\[1\]; //提取“數據類型”
//以下的數據轉換,在第62節講解過的指針法
pu32Data=(unsigned long \*)&Gu8ReceBuffer\[2\]; //數據轉換
Gu32ReceDataLength=\*pu32Data; //提取“數據長度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“數據長度”來判斷是否完成
{
Gu8FinishFlag=1; //接收完成標志“置1”,通知主函數處理。
Gu8ReceStep=0; //及時切換回接頭暗號的步驟
}
else //如果還沒結束,繼續切換到下一個步驟,接收“有效數據”
{
//本節只用到一個接收數組,把指針關聯到Gu8ReceBuffer本身的數組
pGu8ReceBuffer=(unsigned char \*)&Gu8ReceBuffer\[6\];
Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大緩存
Gu8ReceStep=2; //切換到下一個步驟
}
}
break;
case 2: //“后部分的”數據
pGu8ReceBuffer\[Gu32ReceCnt-6\]=SBUF; //這里的指針就是各種不同內存的化身!!!
Gu32ReceCnt++; //每接收一個字節,數組下標都自加1,為接收下一個數據做準備
//靠“數據長度”來判斷是否完成。也不允許超過數組的最大緩存的長度
if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=Gu32ReceCntMax)
{
Gu8FinishFlag=1; //接收完成標志“置1”,通知主函數處理。
Gu8ReceStep=0; //及時切換回接頭暗號的步驟
}
break;
}
}
}
else //發送數據引起的中斷
{
TI = 0; //及時清除發送中斷的標志,避免一直無緣無故的進入中斷。
Gu8SendByteFinish=1; //從0變成1通知主函數已經發送完一個字節的數據了。
}
}
void UsartSendByteData(unsigned char u8SendData) //發送一個字節的底層驅動函數
{
static unsigned int Su16TimeOutDelay; //超時處理的延時計時器
Gu8SendByteFinish=0; //在發送以字節之前,必須先把此全局變量的標志清零。
SBUF =u8SendData; //依靠寄存器SBUF作為載體發送一個字節的數據
Su16TimeOutDelay=0xffff; //超時處理的延時計時器裝載一個相對合理的計時初始值
while(Su16TimeOutDelay>0) //超時處理
{
if(1==Gu8SendByteFinish)
{
break; //如果Gu8SendByteFinish為1,則發送一個字節完成,退出當前循環等待。
}
Su16TimeOutDelay--; //超時計時器不斷遞減
}
//Delay();//在實際應用中,當連續發送一堆數據時如果發現丟失數據,可以嘗試在此增加延時
}
//發送帶協議的函數
void UsartSendMessage(const unsigned char \*pCu8SendMessage,unsigned long u32SendMaxSize)
{
static unsigned long i;
static unsigned long \*pSu32;
static unsigned long u32SendSize;
pSu32=(const unsigned long \*)&pCu8SendMessage\[2\];
u32SendSize=\*pSu32; //從帶協議的數組中提取整包數組的有效發送長度
if(u32SendSize>u32SendMaxSize) //如果“有效發送長度”大于“最大限制的長度”,數據異常
{
return; //數據異常,直接退出當前函數,預防數組越界
}
for(i=0;i<u32SendSize;i++) //u32SendSize為發送的數據長度
{
UsartSendByteData(pCu8SendMessage\[i\]); //基于“發送單字節的最小接口函數”來實現的
}
}
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //此處加const代表數組“只讀”
unsigned long u32BufferSize) //參與計算的數組的大小
{
unsigned long i;
unsigned char Su8Rece\_Xor;
Su8Rece\_Xor=pCu8Buffer\[0\]; //提取數據串第“i=0”個數據作為異或的原始數據
for(i=1;i<u32BufferSize;i++) //注意,這里是從第“i=1”個數據開始
{
Su8Rece\_Xor=Su8Rece\_Xor^pCu8Buffer\[i\]; //計算“異或”
}
return Su8Rece\_Xor; //返回運算后的異或的計算結果
}
//比較兩個數組的是否相等。返回1代表相等,返回0代表不相等
unsigned char CmpTwoBufferIsSame(const unsigned char \*pCu8Buffer\_1,
const unsigned char \*pCu8Buffer\_2,
unsigned long u32BufferSize) //參與對比的數組的大小
{
unsigned long i;
for(i=0;i<u32BufferSize;i++)
{
if(pCu8Buffer\_1\[i\]!=pCu8Buffer\_2\[i\])
{
return 0; //只要有一個不相等,則返回0并且退出當前函數
}
}
return 1; //相等
}
void KeyScan(void) //此函數放在定時中斷里每1ms掃描一次
{
static unsigned char Su8KeyLock1; //1號按鍵的自鎖
static unsigned int Su16KeyCnt1; //1號按鍵的計時器
//1號按鍵
if(0!=KEY\_INPUT1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
{
Su8KeyLock1=0; //按鍵解鎖
Su16KeyCnt1=0; //按鍵去抖動延時計數器清零,此行非常巧妙,是全場的亮點。
}
else if(0==Su8KeyLock1)//有按鍵按下,且是第一次被按下。
{
Su16KeyCnt1++; //累加定時中斷次數
if(Su16KeyCnt1>=KEY\_FILTER\_TIME) //濾波的“穩定時間”KEY\_FILTER\_TIME,長度是25ms。
{
Su8KeyLock1=1; //按鍵的自鎖,避免一直觸發
vGu8KeySec=1; //觸發1號鍵
}
}
}
void T0\_time() interrupt 1
{
VoiceScan();
KeyScan();
if(1==vGu8BigBufferUsartTimerFlag&&vGu16BigBufferUsartTimerCnt>0) //過程控制的超時定時器
{
vGu16BigBufferUsartTimerCnt--;
}
if(1==vGu8QueueSendTimerFlag&&vGu16QueueSendTimerCnt>0) //隊列發送的超時定時器
{
vGu16QueueSendTimerCnt--;
}
if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通訊過程中字節之間的超時定時器
{
vGu16ReceTimeOutCnt--;
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
unsigned char u8\_TMOD\_Temp=0;
//以下是定時器0的中斷的配置
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
//以下是串口接收中斷的配置
//串口的波特率與內置的定時器1直接相關,因此配置此定時器1就等效于配置波特率。
u8\_TMOD\_Temp=0x20; //即將把定時器1設置為:工作方式2,初值自動重裝的8位定時器。
TMOD=TMOD&0x0f; //此寄存器低4位是跟定時器0相關,高4位是跟定時器1相關。先清零定時器1。
TMOD=TMOD|u8\_TMOD\_Temp; //把高4位的定時器1填入0x2,低4位的定時器0保持不變。
TH1=256-(11059200L/12/32/9600); //波特率為9600。11059200代表晶振11.0592MHz,
TL1=256-(11059200L/12/32/9600); //L代表long的長類型數據。根據芯片手冊提供的計算公式。
TR1=1; //開啟定時器1
SM0=0;
SM1=1; //SM0與SM1的設置:選擇10位異步通訊,波特率根據定時器1可變
REN=1; //允許串口接收數據
//為了保證串口中斷接收的數據不丟失,必須設置IP = 0x10,相當于把串口中斷設置為最高優先級,
//這個時候,串口中斷可以打斷任何其他的中斷服務函數實現嵌套,
IP =0x10; //把串口中斷設置為最高優先級,必須的。
ES=1; //允許串口中斷
EA=1; //允許總中斷
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
GtBigBufferUsart.u8Start=0; //通訊過程的啟動變量必須初始化為0!這一步很關鍵!
}
void BeepOpen(void)
{
P3\_4=0;
}
void BeepClose(void)
{
P3\_4=1;
}
void VoiceScan(void)
{
static unsigned char Su8Lock=0;
if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
{
if(0==Su8Lock)
{
Su8Lock=1;
BeepOpen();
}
else
{
vGu16BeepTimerCnt--;
if(0==vGu16BeepTimerCnt)
{
Su8Lock=0;
BeepClose();
}
}
}
}
【134.8 例程的下位機程序。】
下位機作為從機應答上位機的指令,程序相對簡化了很多。不需要“通訊過程的控制涵數”,直接在
“接收數據后的處理涵數”里啟動“發送的隊列驅動涵數”來發送應答的數據即可。發送應答數據后,也不用等待上位機的應答數據。
\#include "REG52.H"
\#define RECE\_TIME\_OUT 2000 //通訊過程中字節之間的超時時間2000ms
\#define REC\_BUFFER\_SIZE 30 //常規控制類數組的長度
void usart(void); //串口接收的中斷函數
void T0\_time(); //定時器的中斷函數
void QueueSend(void); //發送的隊列驅動涵數
void ReceDataHandle(void); //接收數據后的處理涵數
void UsartTask(void); //串口收發的任務函數,放在主函數內
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //異或的算法的函數
unsigned long u32BufferSize);
void UsartSendByteData(unsigned char u8SendData); //發送一個字節的底層驅動函數
//發送帶協議的函數
void UsartSendMessage(const unsigned char \*pCu8SendMessage,unsigned long u32SendMaxSize);
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
//下面表格數組的數據與上位機的表格數據一模一樣,目的用來讓上位機檢測接收到的數據是否正確
code unsigned char Cu8TestTable\[\]=
{
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,
0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,
0x51,0x52,0x53,0x54,0x55,0x56,0x57
};
//把一些針對某個特定事件的全局變量放在一個結構體內,可以讓全局變量的分類更加清晰
struct StructBigBufferUsart //應答讀取大數組的通訊過程的結構體
{
unsigned char u8QueueSendTrig;//隊列驅動函數的發送的啟動
unsigned char u8QueueSendBuffer\[30\]; //隊列驅動函數的發送指令的數組
unsigned char u8QueueStatus; //隊列驅動函數的通訊狀態 0為初始狀態 1為通訊成功 2為通訊失敗
};
unsigned char Gu8QueueReceUpdate=0; //1代表“隊列發送數據后,收到了新的數據”
struct StructBigBufferUsart GtBigBufferUsart;//此結構體變量專門用來應答讀取大數組的通訊事件
volatile unsigned char vGu8QueueSendTimerFlag=0; //隊列發送的超時定時器
volatile unsigned int vGu16QueueSendTimerCnt=0;
unsigned char Gu8SendByteFinish=0; //發送一個字節完成的標志
unsigned char Gu8ReceBuffer\[REC\_BUFFER\_SIZE\]; //常規控制類的小內存
unsigned char \*pGu8ReceBuffer; //用來切換接收內存的“中轉指針”
unsigned long Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大緩存
unsigned long Gu32ReceCnt=0; //接收緩存數組的下標
unsigned char Gu8ReceStep=0; //接收中斷函數里的步驟變量
unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作變量。
unsigned char Gu8ReceType=0; //接收的數據類型
unsigned char Gu8Rece\_Xor=0; //接收的異或
unsigned long Gu32ReceDataLength=0; //接收的數據長度
unsigned char Gu8FinishFlag=0; //是否已接收完成一串數據的標志
unsigned long \*pu32Data; //用于數據轉換的指針
volatile unsigned char vGu8ReceTimeOutFlag=0;//通訊過程中字節之間的超時定時器的開關
volatile unsigned int vGu16ReceTimeOutCnt=0; //通訊過程中字節之間的超時定時器,“喂狗”的對象
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
UsartTask(); //串口收發的任務函數
}
}
/\* 注釋一:
\* 整個項目中只有一個“發送的隊列驅動涵數”,負責“通訊管道的占用”的分配,負責數據的具體發
\* 送。當同時存在很多“待發送”的請求指令時,此函數會根據“if ,else if...”的優先級,像隊列一
\* 樣安排各指令發送的先后順序,確保各指令不會發生沖突。
\*/
void QueueSend(void) //發送的隊列驅動涵數
{
static unsigned char Su8Step=0;
switch(Su8Step)
{
case 0: //分派即將要發送的任務
if(1==GtBigBufferUsart.u8QueueSendTrig)
{
GtBigBufferUsart.u8QueueSendTrig=0; //及時清零。驅動層,不管結果,只發一次。
Gu8QueueReceUpdate=0; //接收應答數據的狀態恢復初始值
//發送帶指令的數據
UsartSendMessage((const unsigned char \*)&GtBigBufferUsart.u8QueueSendBuffer\[0\],
30);
//注意,這里是從機應答主機的數據,不需要等待返回的數據,因此不需要切換Su8Step
}
// else if(...) //當有其它發送的指令時,可以在此處繼續添加判斷,越往下優先級越低
// else if(...) //當有其它發送的指令時,可以在此處繼續添加判斷,越往下優先級越低
break;
case 1: //發送之后,等待下位機的應答。驅動層,只管有沒有應答,不管應答對不對。
if(1==Gu8QueueReceUpdate) //如果“接收數據后的處理涵數”接收到應答數據
{
Su8Step=0; //返回上一步繼續處理其它“待發送的指令”
}
if(0==vGu16QueueSendTimerCnt) //發送指令之后,等待應答超時
{
Su8Step=0; //返回上一步繼續處理其它“待發送的指令”
}
break;
}
}
/\* 注釋二:
\* 整個項目中只有一個“接收數據后的處理涵數”,負責即時處理當前接收到的數據。
\*/
void ReceDataHandle(void) //接收數據后的處理涵數
{
static unsigned long \*pSu32Data; //數據轉換的指針
static unsigned long i;
static unsigned char Su8Rece\_Xor=0; //計算的“異或”
static unsigned long Su32CurrentAddr; //讀取的起始地址
static unsigned long Su32CurrentSize; //讀取的發送的數據量
if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及時更新一次“超時檢測的定時器”的初值
{
Gu8ReceFeedDog=0;
vGu8ReceTimeOutFlag=0;
vGu16ReceTimeOutCnt=RECE\_TIME\_OUT;//更新一次“超時檢測的定時器”的初值
vGu8ReceTimeOutFlag=1;
}
else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超時,并且步驟不在接頭暗號的步驟
{
Gu8ReceStep=0; //串口接收數據的中斷函數及時切換回接頭暗號的步驟
}
if(1==Gu8FinishFlag) //1代表已經接收完畢一串新的數據,需要馬上去處理
{
switch(Gu8ReceType) //接收到的數據類型
{
case 0x01: //返回下位機的數組容量的大小
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“異或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //計算“異或”
if(Su8Rece\_Xor!=Gu8Rece\_Xor) //驗證“異或”,如果不相等,退出當前switch
{
break; //退出當前switch
}
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //數據頭
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x01; //數據類型 返回數組容量的大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=11; //數據長度 本條指令的數據總長是11個字節
//提取數組容量的大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=sizeof(Cu8TestTable);//相當于\*pSu32Data=57;sizeof請參考第69節
//異或算法的函數
GtBigBufferUsart.u8QueueSendBuffer\[10\]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
10); //最后一個字節不納入計算
//隊列驅動函數的狀態 0為初始狀態 1為通訊成功 2為通訊失敗
GtBigBufferUsart.u8QueueStatus=0; //隊列驅動函數的通訊狀態
GtBigBufferUsart.u8QueueSendTrig=1;//隊列驅動函數的發送的啟動
Gu8QueueReceUpdate=1; //告訴“隊列驅動函數”此發送指令無需等待上位機的應答
break;
case 0x02: //返回下位機的分段數據
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“異或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //計算“異或”
if(Su8Rece\_Xor!=Gu8Rece\_Xor) //驗證“異或”,如果不相等,退出當前switch
{
break; //退出當前switch
}
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //數據轉換。
Su32CurrentAddr=\*pSu32Data; //讀取的起始地址
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6+4\]; //數據轉換。
Su32CurrentSize=\*pSu32Data; //讀取的發送的數據量
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //數據頭
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x02; //數據類型 返回分段數據
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=6+4+4+Su32CurrentSize+1; //數據總長度
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=Su32CurrentAddr; //返回接收到的起始地址
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4+4\];
\*pSu32Data=Su32CurrentSize; //返回接收到的當前批次的數據量
for(i=0;i<Su32CurrentSize;i++)
{
//裝載即將要發送的分段數據
GtBigBufferUsart.u8QueueSendBuffer\[6+4+4+i\]=Cu8TestTable\[Su32CurrentAddr+i\];
}
//異或算法的函數
GtBigBufferUsart.u8QueueSendBuffer\[6+4+4+Su32CurrentSize\]=
CalculateXor(GtBigBufferUsart.u8QueueSendBuffer, 6+4+4+Su32CurrentSize);
//隊列驅動函數的狀態 0為初始狀態 1為通訊成功 2為通訊失敗
GtBigBufferUsart.u8QueueStatus=0; //隊列驅動函數的通訊狀態
GtBigBufferUsart.u8QueueSendTrig=1;//隊列驅動函數的發送的啟動
Gu8QueueReceUpdate=1; //告訴“隊列驅動函數”此發送指令無需等待上位機的應答
break;
}
Gu8FinishFlag=0; //上面處理完數據再清零標志,為下一次接收新的數據做準備
}
}
void UsartTask(void) //串口收發的任務函數,放在主函數內
{
QueueSend(); //發送的隊列驅動涵數
ReceDataHandle(); //接收數據后的處理涵數
}
void usart(void) interrupt 4 //串口接發的中斷函數,中斷號為4
{
if(1==RI) //接收完一個字節后引起的中斷
{
RI = 0; //及時清零,避免一直無緣無故的進入中斷。
if(0==Gu8FinishFlag) //1代表已經完成接收了一串新數據,并且禁止接收其它新的數據
{
Gu8ReceFeedDog=1; //每接收到一個字節的數據,此標志就置1及時更新定時器的值。
switch(Gu8ReceStep)
{
case 0: //“前部分的”數據頭。接頭暗號的步驟。
Gu8ReceBuffer\[0\]=SBUF; //直接讀取剛接收完的一個字節的數據。
if(0xeb==Gu8ReceBuffer\[0\]) //等于數據頭0xeb,接頭暗號吻合。
{
Gu32ReceCnt=1; //接收緩存的下標
Gu8ReceStep=1; //切換到下一個步驟,接收其它有效的數據
}
break;
case 1: //“前部分的”數據類型和長度
Gu8ReceBuffer\[Gu32ReceCnt\]=SBUF; //直接讀取剛接收完的一個字節的數據。
Gu32ReceCnt++; //每接收一個字節,數組下標都自加1,為接收下一個數據做準備
if(Gu32ReceCnt>=6) //前6個數據。接收完了“數據類型”和“數據長度”。
{
Gu8ReceType=Gu8ReceBuffer\[1\]; //提取“數據類型”
//以下的數據轉換,在第62節講解過的指針法
pu32Data=(unsigned long \*)&Gu8ReceBuffer\[2\]; //數據轉換
Gu32ReceDataLength=\*pu32Data; //提取“數據長度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“數據長度”來判斷是否完成
{
Gu8FinishFlag=1; //接收完成標志“置1”,通知主函數處理。
Gu8ReceStep=0; //及時切換回接頭暗號的步驟
}
else //如果還沒結束,繼續切換到下一個步驟,接收“有效數據”
{
//本節只用到一個接收數組,把指針關聯到Gu8ReceBuffer本身的數組
pGu8ReceBuffer=(unsigned char \*)&Gu8ReceBuffer\[6\];
Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大緩存
Gu8ReceStep=2; //切換到下一個步驟
}
}
break;
case 2: //“后部分的”數據
pGu8ReceBuffer\[Gu32ReceCnt-6\]=SBUF; //這里的指針就是各種不同內存的化身!!!
Gu32ReceCnt++; //每接收一個字節,數組下標都自加1,為接收下一個數據做準備
//靠“數據長度”來判斷是否完成。也不允許超過數組的最大緩存的長度
if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=Gu32ReceCntMax)
{
Gu8FinishFlag=1; //接收完成標志“置1”,通知主函數處理。
Gu8ReceStep=0; //及時切換回接頭暗號的步驟
}
break;
}
}
}
else //發送數據引起的中斷
{
TI = 0; //及時清除發送中斷的標志,避免一直無緣無故的進入中斷。
Gu8SendByteFinish=1; //從0變成1通知主函數已經發送完一個字節的數據了。
}
}
void UsartSendByteData(unsigned char u8SendData) //發送一個字節的底層驅動函數
{
static unsigned int Su16TimeOutDelay; //超時處理的延時計時器
Gu8SendByteFinish=0; //在發送以字節之前,必須先把此全局變量的標志清零。
SBUF =u8SendData; //依靠寄存器SBUF作為載體發送一個字節的數據
Su16TimeOutDelay=0xffff; //超時處理的延時計時器裝載一個相對合理的計時初始值
while(Su16TimeOutDelay>0) //超時處理
{
if(1==Gu8SendByteFinish)
{
break; //如果Gu8SendByteFinish為1,則發送一個字節完成,退出當前循環等待。
}
Su16TimeOutDelay--; //超時計時器不斷遞減
}
//Delay();//在實際應用中,當連續發送一堆數據時如果發現丟失數據,可以嘗試在此增加延時
}
//發送帶協議的函數
void UsartSendMessage(const unsigned char \*pCu8SendMessage,unsigned long u32SendMaxSize)
{
static unsigned long i;
static unsigned long \*pSu32;
static unsigned long u32SendSize;
pSu32=(const unsigned long \*)&pCu8SendMessage\[2\];
u32SendSize=\*pSu32; //從帶協議的數組中提取整包數組的有效發送長度
if(u32SendSize>u32SendMaxSize) //如果“有效發送長度”大于“最大限制的長度”,數據異常
{
return; //數據異常,直接退出當前函數,預防數組越界
}
for(i=0;i<u32SendSize;i++) //u32SendSize為發送的數據長度
{
UsartSendByteData(pCu8SendMessage\[i\]); //基于“發送單字節的最小接口函數”來實現的
}
}
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //此處加const代表數組“只讀”
unsigned long u32BufferSize) //參與計算的數組的大小
{
unsigned long i;
unsigned char Su8Rece\_Xor;
Su8Rece\_Xor=pCu8Buffer\[0\]; //提取數據串第“i=0”個數據作為異或的原始數據
for(i=1;i<u32BufferSize;i++) //注意,這里是從第“i=1”個數據開始
{
Su8Rece\_Xor=Su8Rece\_Xor^pCu8Buffer\[i\]; //計算“異或”
}
return Su8Rece\_Xor; //返回運算后的異或的計算結果
}
void T0\_time() interrupt 1
{
if(1==vGu8QueueSendTimerFlag&&vGu16QueueSendTimerCnt>0) //隊列發送的超時定時器
{
vGu16QueueSendTimerCnt--;
}
if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通訊過程中字節之間的超時定時器
{
vGu16ReceTimeOutCnt--;
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
unsigned char u8\_TMOD\_Temp=0;
//以下是定時器0的中斷的配置
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
//以下是串口接收中斷的配置
//串口的波特率與內置的定時器1直接相關,因此配置此定時器1就等效于配置波特率。
u8\_TMOD\_Temp=0x20; //即將把定時器1設置為:工作方式2,初值自動重裝的8位定時器。
TMOD=TMOD&0x0f; //此寄存器低4位是跟定時器0相關,高4位是跟定時器1相關。先清零定時器1。
TMOD=TMOD|u8\_TMOD\_Temp; //把高4位的定時器1填入0x2,低4位的定時器0保持不變。
TH1=256-(11059200L/12/32/9600); //波特率為9600。11059200代表晶振11.0592MHz,
TL1=256-(11059200L/12/32/9600); //L代表long的長類型數據。根據芯片手冊提供的計算公式。
TR1=1; //開啟定時器1
SM0=0;
SM1=1; //SM0與SM1的設置:選擇10位異步通訊,波特率根據定時器1可變
REN=1; //允許串口接收數據
//為了保證串口中斷接收的數據不丟失,必須設置IP = 0x10,相當于把串口中斷設置為最高優先級,
//這個時候,串口中斷可以打斷任何其他的中斷服務函數實現嵌套,
IP =0x10; //把串口中斷設置為最高優先級,必須的。
ES=1; //允許串口中斷
EA=1; //允許總中斷
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}
- 首頁
- 第一節:我的價值觀
- 第二節:初學者的疑惑
- 第三節:單片機最重要的一個特性
- 第四節:平臺軟件和編譯器軟件的簡介
- 第五節:用Keil2軟件關閉,新建,打開一個工程的操作流程
- 第六節:把.c源代碼編譯成.hex機器碼的操作流程
- 第七節:本節預留
- 第八節:把.hex機器碼程序燒錄到單片機的操作流程
- 第九節:本節預留
- 第十節:程序從哪里開始,要到哪里去?
- 第十一節:一個在單片機上練習C語言的模板程序
- 第十二節:變量的定義和賦值
- 【TODO】第十三節:賦值語句的覆蓋性
- 【TODO】第十四節:二進制與字節單位,以及常用三種變量的取值范圍
- 【TODO】第十五節:二進制與十六進制
- 【TODO】第十六節:十進制與十六進制
- 【TODO】第十七節:加法運算的5種常用組合
- 【TODO】第十八節:連加、自加、自加簡寫、自加1
- 【TODO】第十九節:加法運算的溢出
- 【TODO】第二十節:隱藏中間變量為何物?
- 【TODO】第二十一節:減法運算的5種常用組合。
- 【TODO】第二十二節:連減、自減、自減簡寫、自減1
- 【TODO】第二十三節:減法溢出與假想借位
- 【TODO】第二十四節:借用unsigned long類型的中間變量可以減少溢出現象
- 【TODO】第二十五節:乘法運算中的5種常用組合
- 【TODO】第二十六節:連乘、自乘、自乘簡寫,溢出
- 【TODO】第二十七節:整除求商
- 【TODO】第二十八節:整除求余
- 【TODO】第二十九節:“先余后商”和“先商后余”提取數據某位,哪家強?
- 【TODO】第三十節:邏輯運算符的“與”運算
- 【TODO】第三十一節:邏輯運算符的“或”運算
- 【TODO】第三十二節:邏輯運算符的“異或”運算
- 【TODO】第三十三節:邏輯運算符的“按位取反”和“非”運算
- 【TODO】第三十四節:移位運算的左移
- 【TODO】第三十五節:移位運算的右移
- 【TODO】第三十六節:括號的強制功能---改變運算優先級
- 【TODO】第三十七節:單字節變量賦值給多字節變量的疑惑
- 【TODO】第三十八節:第二種解決“運算過程中意外溢出”的便捷方法
- 【TODO】第三十九節:if判斷語句以及常量變量的真假判斷
- 【TODO】第四十節:關系符的等于“==”和不等于“!=”
- 【TODO】第四十一節:關系符的大于“>”和大于等于“>=”
- 【TODO】第四十二節:關系符的小于“<”和小于等于“<=”
- 【TODO】第四十三節:關系符中的關系符:與“&&”,或“||”
- 【TODO】第四十四節:小括號改變判斷優先級
- 【TODO】第四十五節: 組合判斷if...else if...else
- 【TODO】第四十六節: 一維數組
- 【TODO】第四十七節: 二維數組
- 【TODO】第四十八節: while循環語句
- 【TODO】第四十九節: 循環語句do while和for
- 【TODO】第五十節: 循環體內的continue和break語句
- 【TODO】第五十一節: for和while的循環嵌套
- 【TODO】第五十二節: 支撐程序框架的switch語句
- 【TODO】第五十三節: 使用函數的三要素和執行順序
- 【TODO】第五十四節: 從全局變量和局部變量中感悟“棧”為何物
- 【TODO】第五十五節: 函數的作用和四種常見書寫類型
- 【TODO】第五十六節: return在函數中的作用以及四個容易被忽略的功能
- 【TODO】第五十七節: static的重要作用
- 【TODO】第五十八節: const(./book/或code)在定義數據時的作用
- 【TODO】第五十九節: 全局“一鍵替換”功能的#define
- 【TODO】第六十節: 指針在變量(./book/或常量)中的基礎知識
- 【TODO】第六十一節: 指針的中轉站作用,地址自加法,地址偏移法
- 【TODO】第六十二節: 指針,大小端,化整為零,化零為整
- 【TODO】第六十三節: 指針“化整為零”和“化零為整”的“靈活”應用
- 【TODO】第六十四節: 指針讓函數具備了多個相當于return的輸出口
- 【TODO】第六十五節: 指針作為數組在函數中的入口作用
- 【TODO】第六十六節: 指針作為數組在函數中的出口作用
- 【TODO】第六十七節: 指針作為數組在函數中既“入口”又“出口”的作用
- 【TODO】第六十八節: 為函數接口指針“定向”的const關鍵詞
- 【TODO】第六十九節: 宏函數sizeof(./book/)
- 【TODO】第七十節: “萬能數組”的結構體
- 【TODO】第七十一節: 結構體的內存和賦值
- 【TODO】第七十二節: 結構體的指針
- 【TODO】第七十三節: 結構體數據的傳輸存儲和還原
- 【TODO】第七十四節: 結構體指針在函數接口處的頻繁應用
- 【TODO】第七十五節: 指針的名義(例:一維指針操作二維數組)
- 【TODO】第七十六節: 二維數組的指針
- 【TODO】第七十七節: 指針唯一的“單向輸出”通道return
- 【TODO】第七十八節: typedef和#define和enum
- 【TODO】第七十九節: 各種變量常量的命名規范
- 【TODO】第八十節: 單片機IO口驅動LED
- 【TODO】第八十一節: 時間和速度的起源(指令周期和晶振頻率)
- 【TODO】第八十二節: Delay“阻塞”延時控制LED閃爍
- 【TODO】第八十三節: 累計主循環的“非阻塞”延時控制LED閃爍
- 【TODO】第八十四節: 中斷與中斷函數
- 【TODO】第八十五節: 定時中斷的寄存器配置
- 【TODO】第八十六節: 定時中斷的“非阻塞”延時控制LED閃爍
- 【TODO】第八十七節: 一個定時中斷產生N個軟件定時器
- 【TODO】第八十八節: 兩大核心框架理論(四區一線,switch外加定時中斷)
- 【TODO】第八十九節: 跑馬燈的三種境界
- 【TODO】第九十節: 多任務并行處理兩路跑馬燈
- 【TODO】第九十一節: 蜂鳴器的“非阻塞”驅動
- 【TODO】第九十二節: 獨立按鍵的四大要素(自鎖,消抖,非阻塞,清零式濾波)
- 【TODO】第九十三節: 獨立按鍵鼠標式的單擊與雙擊
- 【TODO】第九十四節: 兩個獨立按鍵構成的組合按鍵
- 【TODO】第九十五節: 兩個獨立按鍵的“電腦鍵盤式”組合按鍵
- 【TODO】第九十六節: 獨立按鍵“一鍵兩用”的短按與長按
- 【TODO】第九十七節: 獨立按鍵按住不松手的連續均勻觸發
- 【TODO】第九十八節: 獨立按鍵按住不松手的“先加速后勻速”的觸發
- 【TODO】第九十九節: “行列掃描式”矩陣按鍵的單個觸發(原始版)
- 【TODO】第一百節: “行列掃描式”矩陣按鍵的單個觸發(優化版)
- 【TODO】第一百零一節: 矩陣按鍵鼠標式的單擊與雙擊
- 【TODO】第一百零二節: 兩個“任意行輸入”矩陣按鍵的“有序”組合觸發
- 【TODO】第一百零三節: 兩個“任意行輸入”矩陣按鍵的“無序”組合觸發
- 【TODO】第一百零四節: 矩陣按鍵“一鍵兩用”的短按與長按
- 【TODO】第一百零五節: 矩陣按鍵按住不松手的連續均勻觸發
- 【TODO】第一百零六節: 矩陣按鍵按住不松手的“先加速后勻速”觸發
- 【TODO】第一百零七節: 開關感應器的識別與軟件濾波
- 【TODO】第一百零八節: 按鍵控制跑馬燈的啟動和暫停和停止
- 【TODO】第一百零九節: 按鍵控制跑馬燈的方向
- 【TODO】第一百一十節: 按鍵控制跑馬燈的速度
- 第一百一十一節: 工業自動化設備的開關信號的運動控制
- 【TODO】第一百一十二節: 數碼管顯示的基礎知識
- 【TODO】第一百一十三節: 動態掃描的數碼管顯示數字
- 【TODO】第一百一十四節: 動態掃描的數碼管顯示小數點
- 【TODO】第一百一十五節: 按鍵控制數碼管的秒表
- 【TODO】第一百一十六節: 按鍵控制數碼管的倒計時
- 【TODO】第一百一十七節: 按鍵切換數碼管窗口來設置參數
- 【TODO】第一百一十八節: 按鍵讓某位數碼管閃爍跳動來設置參數
- 【TODO】第一百一十九節: 一個完整的人機界面的程序框架的脈絡
- 【TODO】第一百二十節: 按鍵切換窗口切換局部來設置參數
- 【TODO】第一百二十一節: 可調參數的數碼管倒計時
- 【TODO】第一百二十二節: 利用定時中斷做的“時分秒”數顯時鐘
- 【TODO】第一百二十三節: 一種能省去一個lock自鎖變量的按鍵驅動程序
- 【TODO】第一百二十四節: 數顯儀表盤顯示“速度、方向、計數器”的跑馬燈
- 【TODO】第一百二十五節: “雙線”的肢體接觸通信
- 【TODO】第一百二十六節: “單線”的肢體接觸通信
- 【TODO】第一百二十七節: 單片機串口接收數據的機制
- 【TODO】第一百二十八節: 接收“固定協議”的串口程序框架
- 【TODO】第一百二十九節: 接收帶“動態密匙”與“累加和”校驗數據的串口程序框架
- 【TODO】第一百三十節: 接收帶“動態密匙”與“異或”校驗數據的串口程序框架
- 【TODO】第一百三十一節: 靈活切換各種不同大小“接收內存”的串口程序框架
- 【TODO】第一百三十二節:“轉發、透傳、多種協議并存”的雙緩存串口程序框架
- 【TODO】第一百三十三節:常用的三種串口發送函數
- 【TODO】第一百三十四節:“應用層半雙工”雙機串口通訊的程序框架