????????寫這篇文章是為了記錄下之前做過的項目中用到的一部分關鍵技術,之前做過的項目中涉及到采用最小開銷來實時接收遙控器數(shù)據(jù)、能夠準確驗證傳輸過來數(shù)據(jù)的準確性,減小誤差率,要求能穩(wěn)定適用于不同的環(huán)境。
目錄
1、為什么要用到串口空閑中斷?
2、為什么要用到DMA雙緩沖?
3、具體代碼流程。
????????(1)cubemx配置stm32串口DMA雙緩沖。
????????(2)添加串口中斷處理函數(shù)。
????????(3)根據(jù)手冊處理遙控器數(shù)據(jù)
1、為什么要用到串口空閑中斷?
????????在stm32 中,uart是最為常見的通信方式——它每次接收一個字節(jié),我們可以使用輪詢的方式,輪詢就是不斷去訪問一個信號的端口,看看有沒有信號進入,有則進行處理,但是對于某些數(shù)據(jù)不固定時間發(fā)送的數(shù)據(jù),輪詢的方式過多消耗系統(tǒng)資源了。
???????使用中斷的方式,中斷方式則是當輸入產生的時候,產生一個觸發(fā)信號告訴 STM32 有輸入信號進入,需要進行處理。如每一個字節(jié)都中斷一次,比較消耗系統(tǒng)資源。特別是HAL庫中,從中斷到回調函數(shù)運行了不少的程序,頻繁的中斷很可能造成數(shù)據(jù)溢出。
????????為了避免這個問題,我們使用指定接收一定長度的數(shù)據(jù),再調用回調函數(shù),這會讓我們可以接收大數(shù)據(jù),但是這種情況則造成了,要求每次的包是固定長度。
????????為了解決以上一些問題,網(wǎng)上最常用的辦法是使用空閑中斷,即在串口空閑的時候,觸發(fā)一次中斷,通知內核,本次運輸完成了。串口空閑中斷的判定是:當串口開始接收數(shù)據(jù)后,檢測到1字節(jié)數(shù)據(jù)的時間內沒有數(shù)據(jù)發(fā)送,則認為串口空閑了。由于我們的內核在串口接收數(shù)據(jù)到空閑這段時間,是不受理串口數(shù)據(jù)的,所以我們還需要使用DMA來協(xié)助我們把數(shù)據(jù)傳送到指定的地方,當數(shù)據(jù)傳輸完成后,通知內核去處理。
? ? ? ? 由于在項目中要用到遙控器發(fā)送數(shù)據(jù)給stm32,并且外界環(huán)境的干擾,可能會發(fā)生丟幀的情況。這時遙控器發(fā)送數(shù)據(jù)是不定時的,因此我在項目中采用的是不定時、不定長接收數(shù)據(jù)。
????????如果有一次數(shù)據(jù)被干擾,遙控器的18幀數(shù)據(jù)只有17個被串口接受,那么在下一次遙控器發(fā)送數(shù)據(jù)時,stm32會把下一次的第一個幀和上一次的17幀拼接在一次保存在緩存里,導致之后的數(shù)據(jù)全部接收錯誤。
2、為什么要用到DMA雙緩沖?
????????DMA全稱Direct Memory Access,即直接內存訪問。
普通DMA的使用是在DMA的數(shù)據(jù)流中進行的,設置好DMA的起點和終點以及傳輸?shù)臄?shù)據(jù)量即可開啟DMA傳輸。
????????DMA開啟后就開始從起點將數(shù)據(jù)搬運至終點,每搬一次,傳輸數(shù)據(jù)量大小就減1,當傳輸數(shù)據(jù)量為0時,DMA停止搬運。
普通模式:當傳輸數(shù)據(jù)量為0時,需要程序中手動將傳輸數(shù)據(jù)量重新設置滿才能開啟下一次的DMA數(shù)據(jù)傳輸。
循環(huán)模式:則當傳輸數(shù)據(jù)量為0時,會自動將傳輸數(shù)據(jù)量設置為滿的,這樣數(shù)據(jù)就能不斷的傳輸。
普通DMA模式下,普通DMA的目標數(shù)據(jù)儲存區(qū)域只有一個,也就是如果當數(shù)據(jù)存滿后,新的數(shù)據(jù)又傳輸過來了,那么舊的數(shù)據(jù)會被新的數(shù)據(jù)覆蓋。
雙緩沖模式下,我們DMA的目標數(shù)據(jù)儲存區(qū)域有兩個,也就是雙緩沖,
當一次完整的數(shù)據(jù)傳輸結束后(即Counter值從初始值變?yōu)?),會自動指向另一個內存區(qū)域。
????????但有時為了安全,當我們設置的DMA緩沖區(qū)大小大于一次完整的數(shù)據(jù)時,當接收完一次數(shù)據(jù)后,Counter值還沒有變?yōu)?,這時不會自動指向另一個緩沖區(qū),所以這時候我們就需要手動更改指向下一個緩沖區(qū)。(也就是當?shù)谝粠瑪?shù)據(jù)傳輸完成后,Counter值不會自動填滿,且內存區(qū)域還是指向Memory0,然后我們將剩余數(shù)據(jù)量保存下來,再將Counter值填滿,接著把DMA指向Memory1,最后通過判斷剩余數(shù)據(jù)量來決定是否對數(shù)據(jù)進行處理)
//設定緩沖區(qū)0
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//設定緩沖區(qū)1
hdma_usart1_rx.Instance->CR |= DMA_SxCR_CT;
3、具體代碼流程。
(1)cubemx配置stm32串口DMA雙緩沖。
????????網(wǎng)上教程很多,這里我就不具體說明了,這里可參考別人寫的這篇文章。文章來源:http://www.zghlxwxcb.cn/news/detail-698919.html
????????雖然我們使用的CubeMx來配置DMA,當然只是配置DMA模式為串口到內存(DMA初始化),但還需要在程序中進一步制定,DMA具體搬運到哪一個內存中,建立兩個數(shù)組用以存放DMA搬運的串口數(shù)據(jù),并將數(shù)組的地址傳給化函數(shù),具體代碼如下所示:文章來源地址http://www.zghlxwxcb.cn/news/detail-698919.html
//初始化代碼
#define SBUS_RX_BUF_NUM 16u //定義的DMA緩沖區(qū)的大小,為了安全我定義了16(定義8以上就行了)
#define RC_FRAME_LENGTH 8u //遙控器發(fā)送的數(shù)據(jù)長度
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
//接收原始數(shù)據(jù),為8個字節(jié),給了16個字節(jié)長度,防止DMA傳輸越界
static uint8_t sbus_rx_buf[2][SBUS_RX_BUF_NUM];
void RC_Init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
//使能DMA串口接收
SET_BIT(huart1.Instance->CR3, USART_CR3_DMAR);
//使能串口空閑中斷
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart1_rx);
while(hdma_usart1_rx.Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(&hdma_usart1_rx);
}
hdma_usart1_rx.Instance->PAR = (uint32_t) & (USART1->DR);
//內存緩沖區(qū)1
hdma_usart1_rx.Instance->M0AR = (uint32_t)(rx1_buf);
//內存緩沖區(qū)2
hdma_usart1_rx.Instance->M1AR = (uint32_t)(rx2_buf);
//數(shù)據(jù)長度
hdma_usart1_rx.Instance->NDTR = dma_buf_num;
//使能雙緩沖區(qū)
SET_BIT(hdma_usart1_rx.Instance->CR, DMA_SxCR_DBM);
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart1_rx);
}
void RC_unable(void)
{
__HAL_UART_DISABLE(&huart1);
}
void RC_restart(uint16_t dma_buf_num)
{
__HAL_UART_DISABLE(&huart1);
__HAL_DMA_DISABLE(&hdma_usart1_rx);
hdma_usart1_rx.Instance->NDTR = dma_buf_num;
__HAL_DMA_ENABLE(&hdma_usart1_rx);
__HAL_UART_ENABLE(&huart1);
}
//遙控器初始化
void remote_control_init(void)
{
RC_Init(sbus_rx_buf[0], sbus_rx_buf[1], SBUS_RX_BUF_NUM);
}
(2)添加串口中斷處理函數(shù)。
void USART1_IRQHandler(void)
{
//HAL_GPIO_TogglePin(GPIOA, led2_Pin);
if(huart1.Instance->SR & UART_FLAG_RXNE)//接收到數(shù)據(jù)
{
__HAL_UART_CLEAR_PEFLAG(&huart1);//清除空閑中斷標志(否則會一直不斷進入中斷)
}
else if(USART1->SR & UART_FLAG_IDLE)//串口空閑時執(zhí)行
{
static uint16_t this_time_rx_len = 0;
__HAL_UART_CLEAR_PEFLAG(&huart1);
if ((hdma_usart1_rx.Instance->CR & DMA_SxCR_CT) == RESET)//現(xiàn)在是緩沖區(qū)1
{
/* Current memory buffer used is Memory 0 */
//失效DMA或者停止DMA傳輸
__HAL_DMA_DISABLE(&hdma_usart1_rx); //HAL_UART_DMAStop(&huart1);
//獲取接收數(shù)據(jù)長度,長度 = 設定長度 - 剩余長度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;
//重新設定數(shù)據(jù)長度
hdma_usart1_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//設定緩沖區(qū)1
hdma_usart1_rx.Instance->CR |= DMA_SxCR_CT;
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart1_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);
//判斷遙控器數(shù)據(jù)是否出錯
RC_data_is_error();
//sbus_to_usart1(sbus_rx_buf[0]);//這里可以把接收的數(shù)據(jù)發(fā)送給出去
}
}
else
{
/* Current memory buffer used is Memory 1 */
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart1_rx);
//獲取接收數(shù)據(jù)長度,長度 = 設定長度 - 剩余長度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;
//重新設定數(shù)據(jù)長度
hdma_usart1_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//設定緩沖區(qū)0
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart1_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
//處理遙控器數(shù)據(jù)
sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
//判斷遙控器數(shù)據(jù)是否出錯
RC_data_is_error();
//sbus_to_usart1(sbus_rx_buf[1]);//這里可以把接收的數(shù)據(jù)發(fā)送給出去
}
}
//RC_data_is_error();
}
}
(3)根據(jù)手冊處理遙控器數(shù)據(jù)。
/**
* @brief 遙控器協(xié)議解析
* @param[in] sbus_buf: 原生數(shù)據(jù)指針
* @param[out] rc_ctrl: 遙控器數(shù)據(jù)指
*/
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl)
{
if (sbus_buf == NULL || rc_ctrl == NULL)
{
return;
}
rc_ctrl->header = sbus_buf[0];
rc_ctrl->alt = sbus_buf[1];
rc_ctrl->ele = sbus_buf[2];
rc_ctrl->thr = sbus_buf[3];
rc_ctrl->rudd = sbus_buf[4];
rc_ctrl->flag = sbus_buf[5]; //標志位
rc_ctrl->verify = sbus_buf[6]; //(BYTE[1]^BYTE[2]^BYTE[3]^BYTE[4]^BYTE[5])&0xff;
rc_ctrl->tail = sbus_buf[7]; //數(shù)據(jù)尾,固定為 0x99
}
//判斷遙控器數(shù)據(jù)是否出錯,
uint8_t RC_data_is_error(void)
{
//使用了go to語句 方便出錯統(tǒng)一處理遙控器變量數(shù)據(jù)歸零
if (rc_ctrl.header != 0x66)
{
goto error;
}
//if (RC_abs(rc_ctrl.alt) > RC_CHANNAL_ERROR_VALUE)
//{
// goto error;
//}
if (rc_ctrl.verify != ((rc_ctrl.alt ^ rc_ctrl.ele ^ rc_ctrl.thr ^ rc_ctrl.rudd ^ rc_ctrl.flag)&0xff))
{
goto error;
}
if (rc_ctrl.tail != 0x99)
{
goto error;
}
return 0;
error:
rc_ctrl.header = 0;
rc_ctrl.alt = 128;
rc_ctrl.ele = 128;
rc_ctrl.thr = 0;
rc_ctrl.rudd = 128;
rc_ctrl.flag = 0;
rc_ctrl.verify = 0;
rc_ctrl.tail = 0;
return 1;
}
//獲取遙控器數(shù)據(jù)指針
RC_ctrl_t *get_remote_control_point(void)
{
return &rc_ctrl;
}
到了這里,關于STM32 cubemx+串口空閑中斷+DMA雙緩沖的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!