[原创] 【NUCLEO-L4R5ZI评测】串口中断+DMA通道接收实现接收不定长数据

donatello1996   2018-1-28 16:32 楼主
因为项目需要,要和电脑端的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接收就是还没被传输的串口字符数: 4.JPG 但是,这个数量并不是我们要获取的不定长串口数据长度,而是这个长度的互补数据,而总长度X则是我们自己定义的: 5.JPG 了解了原理之后就可以写代码了,在CubeMX里面可以生成相关的初始化代码,但就是这个CubeMX坑了我一次,因为最新版CubeMX中对L4+MCU的DMA通道的选择没有完善,无论是什么外设开启DMA,都可以自由选择DMA通道,这个显然与事实不符(STM32中外设和DMA通道是一对一锁定的,用户只能用官方的分配方案,不能自由修改): 6.jpg 这个CubeMX的不完善之处坑了我三个晚上的时间,由于默认DMA1通道1,因此我还一直以为LPUART1RX通道就是DMA1通道1.后来我在经历了三个晚上的失败之后另辟蹊径,我之前试用过L496板子,而官方则说了L4R5板子和L496板子的大部分外设的差异不是很大,因此我去查看L496MCU的通道分配方案: 7.jpg 然后我将L4R5的LPUART1RX的通道也设置为DMA2通道7,果然成功了。 先来看看初串口和对应DMA的初始化代码:
  1. #define BUFFERSIZE 255
  2. uint8_t rx_buff[BUFFERSIZE];
  3. uint8_t recv_end_flag=0,rx_len=0;
  4. GPIO_InitTypeDef GPIO_InitStruct;
  5. UART_HandleTypeDef LPUART1_Handler;
  6. DMA_HandleTypeDef hdma_lpuart1_rx;
  7. void LPUART1_Init(int baud)
  8. {
  9. __HAL_RCC_GPIOG_CLK_ENABLE();
  10. __HAL_RCC_LPUART1_CLK_ENABLE();
  11. __HAL_RCC_DMAMUX1_CLK_ENABLE();
  12. __HAL_RCC_DMA2_CLK_ENABLE();
  13. HAL_PWREx_EnableVddIO2();
  14. GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8;
  15. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  16. GPIO_InitStruct.Pull = GPIO_PULLUP;
  17. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  18. GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
  19. HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
  20. LPUART1_Handler.Instance = LPUART1;
  21. LPUART1_Handler.Init.BaudRate =baud;
  22. LPUART1_Handler.Init.WordLength = UART_WORDLENGTH_8B;
  23. LPUART1_Handler.Init.StopBits = UART_STOPBITS_1;
  24. LPUART1_Handler.Init.Parity = UART_PARITY_NONE;
  25. LPUART1_Handler.Init.Mode = UART_MODE_TX_RX;
  26. LPUART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  27. LPUART1_Handler.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  28. LPUART1_Handler.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  29. LPUART1_Handler.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  30. LPUART1_Handler.FifoMode = UART_FIFOMODE_DISABLE;
  31. HAL_UART_Init(&LPUART1_Handler);
  32. __HAL_UART_ENABLE_IT(&LPUART1_Handler,UART_IT_IDLE);
  33. HAL_NVIC_SetPriority(LPUART1_IRQn,1,0);
  34. HAL_NVIC_EnableIRQ(LPUART1_IRQn);
  35. HAL_UARTEx_SetTxFifoThreshold(&LPUART1_Handler,UART_TXFIFO_THRESHOLD_1_8);
  36. HAL_UARTEx_SetRxFifoThreshold(&LPUART1_Handler,UART_RXFIFO_THRESHOLD_1_8);
  37. hdma_lpuart1_rx.Instance = DMA2_Channel7;
  38. hdma_lpuart1_rx.Init.Request = DMA_REQUEST_LPUART1_RX;
  39. hdma_lpuart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  40. hdma_lpuart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  41. hdma_lpuart1_rx.Init.MemInc = DMA_MINC_ENABLE;
  42. hdma_lpuart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  43. hdma_lpuart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  44. hdma_lpuart1_rx.Init.Mode =DMA_NORMAL;
  45. hdma_lpuart1_rx.Init.Priority = DMA_PRIORITY_LOW;
  46. HAL_DMA_Init(&hdma_lpuart1_rx);
  47. __HAL_LINKDMA(&LPUART1_Handler,hdmarx,hdma_lpuart1_rx);
  48. }
  49. int fputc(int ch,FILE *f)
  50. {
  51. while(!(LPUART1->ISR&(1<<7)));
  52. LPUART1->TDR=ch;
  53. return ch;
  54. }
再来看看中断服务程序和标志函数代码:
  1. void LPUART1_DMA_Get()
  2. {
  3. int i;
  4. if(recv_end_flag==1)
  5. {
  6. printf("接收到的数据【%s】 数据长度=%d",rx_buff,rx_len);
  7. if(rx_buff[0]==0x66)
  8. if(rx_buff[1]==0x01)
  9. HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_7);
  10. else if(rx_buff[1]==0x02)
  11. HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);
  12. for(i=0;i<rx_len;i++)
  13. rx_buff[i]=0;
  14. rx_len=0;
  15. recv_end_flag=0;
  16. }
  17. HAL_UART_Receive_DMA(&LPUART1_Handler,(uint8_t*)rx_buff,BUFFERSIZE);
  18. }
  19. void LPUART1_IRQHandler()
  20. {
  21. uint32_t temp;
  22. if(LPUART1==LPUART1_Handler.Instance)
  23. {
  24. if(__HAL_UART_GET_FLAG(&LPUART1_Handler,UART_FLAG_IDLE))
  25. {
  26. __HAL_UART_CLEAR_IDLEFLAG(&LPUART1_Handler);
  27. HAL_UART_DMAStop(&LPUART1_Handler);
  28. temp=hdma_lpuart1_rx.Instance->CNDTR;
  29. rx_len=BUFFERSIZE-temp;
  30. recv_end_flag=1;
  31. }
  32. }
  33. }
主函数代码:
  1. int main()
  2. {
  3. HAL_Init();
  4. SystemClock_Config();
  5. Delay_Init();
  6. LED_Init();
  7. LPUART1_Init(115200);
  8. printf("\nEEWORLD论坛 stm32/stm8专区\n");
  9. printf("【NUCLEO-L4R5ZI评测】串口中断+DMA通道接收实现接收不定长数据\n");
  10. printf("donatello1996\n");
  11. //EXTI15_10_IRQHandler_Config();
  12. //TIM3_Init(5000-1,10000-1);
  13. while (1)
  14. {
  15. LPUART1_DMA_Get();
  16. }
  17. }
整个串口中断DMA接收的原理就很清晰了,while(1)大循环一直开启DMA接收(HAL_UART_Receive_DMA()函数),当串口触发空闲中断之后,触发标志位、记录DMA通道未发送数据长度并停止DMA接收,那么DMA通道里面靠前的数据自然就是接收到的串口数据了,并且长度也顺便记录了下来,就达到串口接收不定长数据的效果了。另外还需要注意的是,如果串口助手里加了换行符和结束符(0x0d 0x0a),那么接收到的长度也会+2。 看看效果: 2.JPG 3.JPG 上传工程文件:
工程文件.zip (1.64 MB)
(下载次数: 76, 2018-1-28 16:32 上传)
此内容由EEWORLD论坛网友donatello1996原创,如需转载或用于商业用途需征得作者同意并注明出处 本帖最后由 donatello1996 于 2018-1-28 16:33 编辑

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复