[原创]
【NUCLEO-L4R5ZI评测】串口中断+DMA通道接收实现接收不定长数据
因为项目需要,要和电脑端的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[BUFFERSIZE];
- 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[0]==0x66)
- if(rx_buff[1]==0x01)
- HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_7);
- else if(rx_buff[1]==0x02)
- HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);
- for(i=0;i<rx_len;i++)
- rx_buff[i]=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。
看看效果:
上传工程文件:
工程文件.zip
(1.64 MB)
(下载次数: 76, 2018-1-28 16:32 上传)
此内容由EEWORLD论坛网友donatello1996原创,如需转载或用于商业用途需征得作者同意并注明出处
本帖最后由 donatello1996 于 2018-1-28 16:33 编辑
暂无评论,赶紧抢沙发吧