【NUCLEO-L4R5ZI评测】串口中断+DMA通道接收实现接收不定长数据
<div class='showpostmsg'> 本帖最后由 donatello1996 于 2018-1-28 16:33 编辑因为项目需要,要和电脑端的QT上位机实现数据交互,这几天都在忙活评测L4+板子的LPUART1串口接收。STM32接收串口字符串有三种方法,第一种是最简单的轮询接收,在while(1)大循环里面加上一句HAL_UART_Receive();第二种方法是中断接收,可开启IDLE中断,用HAL_UART_Receive_IT函数接收定长字符串;第三种方法则是本帖介绍的方法,使用IDLE中断+串口DMA接收不定长字符,可以获取实际接收长度。这个方法的核心是串口IDLE(空闲)中断功能,这个中断的作用是在串口发送完一帧数据之后开启,什么是一帧数据呢?比如说用串口助手的发送区写上若干内容,点击发送按钮发送一次数据即为一帧,注意IDLE中断和接收中断的概念完全不同,接收中断是每收到一个字符就触发中断一次,如字符串"EEWORLD",不算上结束符'\0'一共会触发七次接收中断,而只触发一次IDLE中断。
接收不定长数据,需要用到STM32串口接收DMA的知识,DMA里面有个寄存器名叫CNDTR,其作用是指示DMA通道中还没被传输(将要被传输)的数据的字节数,对应串口DMA接收就是还没被传输的串口字符数:
但是,这个数量并不是我们要获取的不定长串口数据长度,而是这个长度的互补数据,而总长度X则是我们自己定义的:
了解了原理之后就可以写代码了,在CubeMX里面可以生成相关的初始化代码,但就是这个CubeMX坑了我一次,因为最新版CubeMX中对L4+MCU的DMA通道的选择没有完善,无论是什么外设开启DMA,都可以自由选择DMA通道,这个显然与事实不符(STM32中外设和DMA通道是一对一锁定的,用户只能用官方的分配方案,不能自由修改):
这个CubeMX的不完善之处坑了我三个晚上的时间,由于默认DMA1通道1,因此我还一直以为LPUART1RX通道就是DMA1通道1.后来我在经历了三个晚上的失败之后另辟蹊径,我之前试用过L496板子,而官方则说了L4R5板子和L496板子的大部分外设的差异不是很大,因此我去查看L496MCU的通道分配方案:
然后我将L4R5的LPUART1RX的通道也设置为DMA2通道7,果然成功了。
先来看看初串口和对应DMA的初始化代码:
#define BUFFERSIZE 255
uint8_t rx_buff;
uint8_t recv_end_flag=0,rx_len=0;
GPIO_InitTypeDef GPIO_InitStruct;
UART_HandleTypeDef LPUART1_Handler;
DMA_HandleTypeDef hdma_lpuart1_rx;
void LPUART1_Init(int baud)
{
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_LPUART1_CLK_ENABLE();
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
HAL_PWREx_EnableVddIO2();
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
LPUART1_Handler.Instance = LPUART1;
LPUART1_Handler.Init.BaudRate =baud;
LPUART1_Handler.Init.WordLength = UART_WORDLENGTH_8B;
LPUART1_Handler.Init.StopBits = UART_STOPBITS_1;
LPUART1_Handler.Init.Parity = UART_PARITY_NONE;
LPUART1_Handler.Init.Mode = UART_MODE_TX_RX;
LPUART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
LPUART1_Handler.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
LPUART1_Handler.Init.ClockPrescaler = UART_PRESCALER_DIV1;
LPUART1_Handler.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
LPUART1_Handler.FifoMode = UART_FIFOMODE_DISABLE;
HAL_UART_Init(&LPUART1_Handler);
__HAL_UART_ENABLE_IT(&LPUART1_Handler,UART_IT_IDLE);
HAL_NVIC_SetPriority(LPUART1_IRQn,1,0);
HAL_NVIC_EnableIRQ(LPUART1_IRQn);
HAL_UARTEx_SetTxFifoThreshold(&LPUART1_Handler,UART_TXFIFO_THRESHOLD_1_8);
HAL_UARTEx_SetRxFifoThreshold(&LPUART1_Handler,UART_RXFIFO_THRESHOLD_1_8);
hdma_lpuart1_rx.Instance = DMA2_Channel7;
hdma_lpuart1_rx.Init.Request = DMA_REQUEST_LPUART1_RX;
hdma_lpuart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_lpuart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_lpuart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_lpuart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_lpuart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_lpuart1_rx.Init.Mode =DMA_NORMAL;
hdma_lpuart1_rx.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_lpuart1_rx);
__HAL_LINKDMA(&LPUART1_Handler,hdmarx,hdma_lpuart1_rx);
}
int fputc(int ch,FILE *f)
{
while(!(LPUART1->ISR&(1<<7)));
LPUART1->TDR=ch;
return ch;
}
再来看看中断服务程序和标志函数代码:
void LPUART1_DMA_Get()
{
int i;
if(recv_end_flag==1)
{
printf("接收到的数据【%s】 数据长度=%d",rx_buff,rx_len);
if(rx_buff==0x66)
if(rx_buff==0x01)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_7);
else if(rx_buff==0x02)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);
for(i=0;i<rx_len;i++)
rx_buff=0;
rx_len=0;
recv_end_flag=0;
}
HAL_UART_Receive_DMA(&LPUART1_Handler,(uint8_t*)rx_buff,BUFFERSIZE);
}
void LPUART1_IRQHandler()
{
uint32_t temp;
if(LPUART1==LPUART1_Handler.Instance)
{
if(__HAL_UART_GET_FLAG(&LPUART1_Handler,UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&LPUART1_Handler);
HAL_UART_DMAStop(&LPUART1_Handler);
temp=hdma_lpuart1_rx.Instance->CNDTR;
rx_len=BUFFERSIZE-temp;
recv_end_flag=1;
}
}
}
主函数代码:
int main()
{
HAL_Init();
SystemClock_Config();
Delay_Init();
LED_Init();
LPUART1_Init(115200);
printf("\nEEWORLD论坛 stm32/stm8专区\n");
printf("【NUCLEO-L4R5ZI评测】串口中断+DMA通道接收实现接收不定长数据\n");
printf("donatello1996\n");
//EXTI15_10_IRQHandler_Config();
//TIM3_Init(5000-1,10000-1);
while (1)
{
LPUART1_DMA_Get();
}
}
整个串口中断DMA接收的原理就很清晰了,while(1)大循环一直开启DMA接收(HAL_UART_Receive_DMA()函数),当串口触发空闲中断之后,触发标志位、记录DMA通道未发送数据长度并停止DMA接收,那么DMA通道里面靠前的数据自然就是接收到的串口数据了,并且长度也顺便记录了下来,就达到串口接收不定长数据的效果了。另外还需要注意的是,如果串口助手里加了换行符和结束符(0x0d 0x0a),那么接收到的长度也会+2。
看看效果:
上传工程文件:
此内容由EEWORLD论坛网友donatello1996原创,如需转载或用于商业用途需征得作者同意并注明出处
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页:
[1]