問題描述
使用STM32的串口進(jìn)行DMA發(fā)送(Noraml模式),在某個(gè)任務(wù)中連續(xù)調(diào)用兩次發(fā)送函數(shù)log_printf(),但是發(fā)回的數(shù)據(jù)在串口調(diào)試助手上顯示與預(yù)期不符。第一次發(fā)送的數(shù)據(jù)有一部分被第二次發(fā)送的數(shù)據(jù)覆蓋,如圖所示:
任務(wù)代碼如下:
/* Log_Task function */void Log_Task(void const * argument)
{ /* USER CODE BEGIN Log_Task */
/* Infinite loop */
for(;;)
{ if(router_rx_flag == 1)
{
router_rx_flag = 0;
log_printf(“Get ok\r\n”);
log_printf(“%s”,router_rx_buffer);
}
osDelay(100);
} /* USER CODE END Log_Task */}1234567891011121314151617
從代碼中可以看出,期望的結(jié)果應(yīng)該是下圖這樣:
log_printf函數(shù)代碼如下:
/*
* 名稱: log_printf
* 功能: 在串口1上打印出日志內(nèi)容
* 輸入: 格式化輸出的字符串
* 輸出: 無(wú)
*/
void log_printf(const char *format ,。。。 )
{
va_list arg;
static char tx_buffer[256]={“”};
//把數(shù)據(jù)處理后放進(jìn)緩沖區(qū)
va_start(arg, format);
vsprintf((char *)tx_buffer, format, arg);
va_end(arg);
//開始發(fā)送數(shù)據(jù)
send_to_router((u8 *)tx_buffer,strlen(tx_buffer));
}
send_to_router函數(shù)代碼如下:
void send_to_router(unsigned char *buffer,unsigned int length)
{
//等待上一次的數(shù)據(jù)發(fā)送完畢
while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1);
/* 關(guān)閉DMA */
__HAL_DMA_DISABLE(&hdma_usart1_tx);
//開始發(fā)送數(shù)據(jù)
HAL_UART_Transmit_DMA(&huart1,buffer,length);
// while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1); /* 放在此處可以保證每次發(fā)送完全,但會(huì)占用時(shí)間 */
}
串口中斷接收處理函數(shù)如下:
/*
* 名稱: router_parse
* 功能: 接收路由器數(shù)據(jù)的解析,在回調(diào)函數(shù)中調(diào)用
* 輸入: 空閑中斷時(shí)串口1接收的數(shù)據(jù)長(zhǎng)度
* 輸出: 無(wú)
*/
void router_parse(uint16_t buffer_len)
{
char *p_start = NULL,*p_end = NULL;
/* 只提取一幀NMEA數(shù)據(jù),$開頭,\n結(jié)尾 */
p_start = strchr(usart1_rx_buffer,‘$’);
if(p_start != NULL)
{
p_end = strchr(p_start,‘\n’);
if(p_end != NULL)
{
memcpy(router_rx_buffer, p_start, (p_end - p_start + 1)); /* 保存數(shù)據(jù) */
router_rx_flag = 1;
}
}
}
分析過(guò)程
以前一直以為是send_to_router函數(shù)中的
//等待上一次的數(shù)據(jù)發(fā)送完畢
while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1);
這一句的問題,即由于某種原因?qū)е翫MA緩存中數(shù)據(jù)未發(fā)送完全,但DMA狀態(tài)卻被釋放了,結(jié)果重新開始了新一輪的發(fā)送,導(dǎo)致上次數(shù)據(jù)的后半部分被覆蓋。但無(wú)論如何調(diào)試,都無(wú)法證實(shí)這個(gè)猜想,DMA外設(shè)沒有出過(guò)任何異常。
今天仔細(xì)觀察了一下,“Getckey”和“Get ok\r\n”和”$Mickey\r\n“,為什是第二次發(fā)送的內(nèi)容的后半部覆蓋了第一次發(fā)送的內(nèi)容,一般不應(yīng)該是前半部分”(美元符號(hào),此處會(huì)排版出錯(cuò))Mic”嗎?問題的原因可能與狀態(tài)位無(wú)關(guān)。于是我再審視了一下send_to_router函數(shù):void send_to_router(unsigned char *buffer,unsigned int length)突然間想到,入?yún)⒅皇且粋€(gè)指針,發(fā)送緩存區(qū)在log_printf函數(shù)中
static char tx_buffer[256]={“”};
整理一下,整個(gè)發(fā)送過(guò)程流程如下:
log_printf(“Get ok\r\n”);時(shí),“Get ok\r\n”被裝進(jìn)了tx_buffer,附帶一個(gè)發(fā)送長(zhǎng)度8字節(jié)。
send_to_router函數(shù)中,HAL_UART_Transmit_DMA(&huart1,buffer,length);開啟了這個(gè)8個(gè)字節(jié)的發(fā)送。
8個(gè)字節(jié)可能只完成了“Get”的發(fā)送, log_printf(“%s”,router_rx_buffer);(即log_printf(“$Mickey\r\n“);)已經(jīng)開始執(zhí)行。
”$Mickey\r\n“被裝進(jìn)tx_buffer,附帶一個(gè)發(fā)送長(zhǎng)度9字節(jié)。
send_to_router函數(shù)中,因?yàn)樯弦淮螖?shù)據(jù)還沒有發(fā)送完全,進(jìn)入DMA狀態(tài)等待循環(huán)。但是DMA發(fā)送指針char *buffer原本指向的那個(gè)地址的內(nèi)容” ok\r\n“已經(jīng)被”ckey\r\n“代替,所以就變成了”Getckey\r“。由于顯示原因,只看到”Getckey“。
解決辦法
把while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1);這一句放到緩存區(qū)tx_buffer裝載步驟之前即可:
/*
* 名稱: log_printf
* 功能: 在串口1上打印出日志內(nèi)容
* 輸入: 格式化輸出的字符串
* 輸出: 無(wú)
*/
void log_printf(const char *format ,。。。 )
{
va_list arg;
static char tx_buffer[256]={“”};
//等待上一次的數(shù)據(jù)發(fā)送完畢
while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1);
//把數(shù)據(jù)處理后放進(jìn)緩沖區(qū)
va_start(arg, format);
vsprintf((char *)tx_buffer, format, arg);
va_end(arg);
//開始發(fā)送數(shù)據(jù)
send_to_router((u8 *)tx_buffer,strlen(tx_buffer));
}
至于send_to_router函數(shù)中的該代碼,保留或刪除都可以。
后言
很久以前就開始使用STM32的DMA串口發(fā)送功能,套路基本上就是曾經(jīng)的博文《iar中使用DMA+printf+uart1》所描述的那樣。后來(lái)開始用STM32CubeMX了,把之前的例程稍微做了一些修改,調(diào)試成功之后,就一直沿用至今。期間,這個(gè)問題困擾了我很久,雖然在寫代碼時(shí)稍微注意一下就可避免其發(fā)生,但做技術(shù)的人都明白:千里之堤,潰于螻蟻,放過(guò)任何一個(gè)小細(xì)節(jié)都可能在將來(lái)引發(fā)重大災(zāi)難。很慶幸今天能夠找到問題的原因。
再回去看來(lái)一遍《iar中使用DMA+printf+uart1》,其實(shí)這個(gè)問題的答案很早就寫在里面了。。。
找個(gè)時(shí)間,我會(huì)專門寫一篇使用DMA串口Normal模式發(fā)送的博文,還是以Cube來(lái)創(chuàng)建工程。屆時(shí),再用一個(gè)例程完整復(fù)現(xiàn)和解決這個(gè)問題。
評(píng)論