#創作靈感#
在我們實際使用MCU進行多串口任務分配的時候,我們會碰到這樣一種情況,即串口需要短間隔周期性發送數據,且相鄰兩幀之間需要間隔一段時間,防止連幀。我們常常需要在軟件層面對串口的發送和接受做一個緩存的處理方式。
針對發送,我們使用以下策略:
即1、上層應用在發送數據的時候其實是先將數據送入我們自己設定的發送緩存內(一個數組);
2、真正的發送是主循環在判斷出緩存內有數據的時候,自行啟動的發送;
所以我們的發送函數其實有兩個,一個用于上層應用將數據寫入緩存,一個用于串口自啟動發送過程。串口自啟動發送連續字節的時候? 我給大家提供了有三種發送方式:堵塞型發送、輪詢式發送、中斷式發送
對于MCU主頻不高的情況,通常跑完一整個while(1)循環 需要的時間相對而言比較長,而串口連續數據的發送是常常要滿足一定超時時間范圍的,比如你的串口連的是某個可以通信的電機,一幀的完整性需要得到保證,超出這個超時時間,電機可能會識別不出來這一幀指令。當然,現在大部分的MCU主控選用高速晶振的時候 都是可以滿足要求的。
堵塞型發送
通常一幀會包含很多字節數據,堵塞型發送其實就是程序在原地等待發送緩沖區空的標志位 或者發送完成標志位 ,一般發送完成肯定是比發送緩沖區空的時間晚。
u8 i = 0;if (USARTx == USART1)
{for (; i < len; i++) { if ((USART1->STATR & USART_STATR_TXE)) { USART1->DATAR = Data[i];}while (!(USART1->STATR & USART_STATR_TXE)); // 檢查發送緩沖區空標志}}
我們會使用while循環在這里等待發送緩沖區空 ,當然也會容易導致系統在此堵塞,從而對于其他控制響應很慢,如果你的系統并沒有短時間的連續發送幀的要求,響應也沒有要求很高,使用這種方式也是可以的。
輪詢型發送
輪詢的核心思想其實就是利用最外層的while(1)循環,每次都只用if來判斷條件是否成立,這樣就不會導致系統的堵塞,但是這對你的整個系統大循環一次的時間有要求,現在基本高主頻都是可以滿足要求的,也不需占用中斷資源,是個不錯的方法。在使用狀態機理論的時候,其實也是在輪詢,這樣的思想在很多產品中都會出現。
具體的實現代碼見上述堵塞型發送,只不過不再有while循環原地等待,同時要記錄下當前發送字節的位置,這樣下次循環進來可以根據位置直接發掉下個對應的字節數據。
中斷型發送
核心思想其實是先發一個字節觸發發送完成中斷,而后在中斷里將下一個需要發送的數據塞入串口的數據寄存器內,等待下一次觸發中斷,直到緩存內的幀數據全部被發送完,期間如果有多幀,那么需要延后一段時間Ts來避免連幀。
對此我常常會定義這么兩個結構體
typedef struct
{u8 length; //幀長 寫入數據的時候需要更新u8 DataBuff[20]; //幀的內容 這里的最大幀長就是20u8 ProcessLoc; //當前幀處理的字節位置 如果不是一次性發完所有字節 建議都保留使用
}Frame;
struct
{UartFrame FBuff[20]; //幀的內容u8 UsartFrameNum; //幀的數量 每次寫入這個數據就會加一 到20末尾的話回環到0u8 UsartFinishLoc; //當前處理幀的位置 每次發送完一幀 就加一 同樣也需要回環到0u8 UsartBusyFlag : 1; //幀處理間隔 就設置這個標志位 表示繁忙 20ms以后自動解封u16 UsartTimeNap; //放在定時器中斷內用來累加
}UsartSendStorage;
第一個是幀的結構體,用來保存與單幀有關系的各項數據;
第二個是串口的發送存儲,如果有很多串口需要使用,可以單獨設置;
下面是關于串口的初始化設置,每種MCU可能都不太一樣,但是建議大家使用宏定義來選擇當前的工作方式,比較方便。
void UsartInit (void) {USART_InitTypeDef USART_InitStructure = {0};NVIC_InitTypeDef NVIC_InitStructure = {0};USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_Init (USART2, &USART_InitStructure);USART_Init (USART3, &USART_InitStructure);USART_Init (USART1, &USART_InitStructure);#define USART_Interrupt_Mode // 中斷發送模式#ifdef USART_Interrupt_ModeUSART_ITConfig (USART2, USART_IT_RXNE, ENABLE); // 接收寄存器非空中斷USART_ITConfig (USART2, USART_IT_TC, ENABLE); // 發送寄存空中斷USART_ITConfig (USART3, USART_IT_RXNE, ENABLE); // 接收寄存器非空中斷USART_ITConfig (USART3, USART_IT_TC, ENABLE); // 發送寄存空中斷NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占優先級NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //響應優先級NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init (&NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init (&NVIC_InitStructure);
#endifUSART_Cmd (USART3, ENABLE);USART_Cmd (USART2, ENABLE);USART_Cmd (USART1, ENABLE);
}
搶占優先級要設置一樣,這樣兩個串口不會互相打斷,響應優先級可以設置不一樣。
下面是幾個函數用于添加幀到發送緩存以及自啟動發送過程。?
void UsartSetFrame(USART_TypeDef *USARTx, unsigned char *Data, unsigned char len)
{if(USARTx== USART1){memcpy(UsartSendStoorage.FBuff[UsartSendStoorage.UsartFrameNum].DataBuff,Data,len)UsartSendStoorage.FBuff[UsartSendStoorage.UsartFrameNum].length = len;UsartSendStoorage.UsartFrameNum++;if(UsartSendStoorage.UsartFrameNum >=10)UsartSendStoorage.UsartFrameNum = 0;}
}
注意我們這里能緩存的幀最長只有10幀,超出這個范圍以后,num的值就回頭覆蓋掉第一項了,你可以認為它就是一個會回環的寫指針,用來指示當前可以存儲幀的第一個位置。
?這里是把我們要發送的幀送到發送緩存里,
void UsartSendFrameTask(USART_TypeDef *USARTx)
{if (USARTx == USART1) {if(UsartSendStorage.UsartFrameNum != UsartSendStorage.UsartFinishLoc && !UsartSendStorage.UsartBusyFlag ) //有數據需要發 且不繁忙#ifdef USART_Interrupt_ModeUSART1->DATAR = UsartSendStorage.FBuff[UsartSendStorage.UsartFinishLoc];#elseUsartSendData(UsartSendStorage.FBuff[UsartSendStorage.UsartFinishLoc]);UsartSendStorage.UsartFinishLoc++;UsartSendStorage.UsartFrameNum--; //可選if(UsartSendStorage.UsartFinishLoc>=20)UsartSendStorage.UsartFinishLoc = 0;#endif}
}
每次發送完一幀,記得需要把finishLoc加一,這樣只要加入幀的位置和最后處理完幀的位置是一致的,說明此時就沒有幀要發送,相反,如果此時有幀需要發送,num的值和finishLoc的值是不一致的。當然,這個num我的本意是將它作為寫入幀的位置,如果你要將它理解為幀的數量,當你處理完一幀的時候,可以將它減一,只用于記錄幀的個數,是個標量。這樣的話你判斷是否有幀的標準就是num的值是不是為0,就不是兩者是否相等。
void USART_IRQHandler (void)
{u8 i;if (USART_GetITStatus (USART2, USART_IT_TC)) // 發送完成標志 表示上一次數據已經發送{if (USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc < USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameLength) {// 當前幀處理字節的位置還沒到末尾 繼續發送i = USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc; // 當前需要處理數據的位置USART1->DATAR = USARTST.USART1SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameBuff[i];USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc++;} //else // 幀處理完畢了 可以調用busyflag 來產生間隔 {USARTST.USART1BusyFlag = 1;USARTST.USART1SendFrameFinishLoc++; // 這一幀發送完畢if (USARTST.USART1SendFrameFinishLoc >= 10) // 回環處理USARTST.USART1SendFrameFinishLoc = 0;}}
}
大家可能對結構體內的TimeNap感到疑惑,其實它就是用來進行累加,這個累加函數會被調用在滴答定時器中斷內,當busyflag? = 1 的時候可以將TimeNap清0,而后在SendFrameTask()函數內部給一個if判斷,當TimeNap >= 200的時候,自動將busyflag = 0,就完成了幀間隔的設置,因為發送幀的時候是需要判斷幀是否繁忙的,不繁忙才會啟動幀的發送。
以上僅供大家參考使用,如有錯誤之處,還請多多體諒。