STM32F429+FreeRTOS队列 串口DMA双缓存数据接收

2 篇文章 0 订阅

前言

最近做项目需要用到数据大量数据处理的应用场景,本来想使用串口空闲中断+DMA传输+FreeRTOS队列的方式接收数据,然而实际使用中发现效果不理想,经常丢包。最后查明原因为在空闲中断频繁关闭/开启DMA接收会影响数据接收效率,所以在查找了相关资料之后,决定使用DMA双缓存的方式接收数据,实际测试结果表明,这种方式大大增加了数据接收效率,现在把这个知识分享给大家。

一、开发环境

芯片:STM32F429

开发软件:keilv5、STM32CubeMX

os:FreeRTOS

二、原理

通过查看官方的《STM32F4xx中文参考手册》,我们得到下面资料:

使用DMA双缓存模式有以下几个特点:

  • 使能双缓存模式会自动使能循环模式。
  • 双缓存模式只支持外设到存储器或者存储器到外设方向,其他方向不允许使用。
  • 双缓存模式适合接收定长数据,如果是不定长数据,建议使用空闲帧处理。 

三、使用DMA双缓存

1、使用STM32CubeMX新建工程,配置串口和DMA。

2、 定义变量

#define UART_BUFF_SIZE  25

#pragma pack(4)
typedef struct
{
    uint16_t len;
    uint8_t data[UART_BUFF_SIZE];
}s_usart_data;
#pragma pack()


QueueHandle_t        queue_mes;  
s_usart_data uart_buf[2];

 3、定义DMA回调函数

//DMA 缓存0 传输结束回调函数
void DMA_M0_RC_Callback(DMA_HandleTypeDef *hdma)
{
    BaseType_t xHigherPriorityTaskWoken; 

    uart_buf[0].len = hdma->Instance->NDTR;
    xQueueSendFromISR(queue_mes,&uart_buf[0],&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

//DMA 缓存1 传输结束回调函数
void DMA_M1_RC_Callback(DMA_HandleTypeDef *hdma)
{
    BaseType_t xHigherPriorityTaskWoken; 

    uart_buf[1].len = hdma->Instance->NDTR;
    xQueueSendFromISR(queue_mes,&uart_buf[1],&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

//DMA 传输错误回调函数
void DMA_Error_Callback(DMA_HandleTypeDef *hdma)
{
    //里面做一些异常处理
}

4、使能DMA传输

//使能DMA
void Enable_Uart(void)
{
		uint32_t u32wk0;
	
	  SET_BIT(huart1.Instance->CR3,USART_CR3_DMAR);
    HAL_DMAEx_MultiBufferStart_IT(&hdma_usart1_rx,
                                  (uint32_t)(&huart1.Instance->DR),
                                  (uint32_t)&uart_buf[0].data[0],
                                  (uint32_t)&uart_buf[1].data[0],
                                   UART_BUFF_SIZE);
		
    //这里是解决DMA在启动时,如果接收到大量数据会出现死机的问题
    u32wk0 = huart1.Instance->SR;  
    u32wk0 = huart1.Instance->DR;
	  UNUSED(u32wk0);
}

5、注册回调函数

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 DMA Init */
    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA2_Stream2;
    hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
	hdma_usart1_rx.XferCpltCallback = DMA_M0_RC_Callback;
    hdma_usart1_rx.XferM1CpltCallback = DMA_M1_RC_Callback;
    hdma_usart1_rx.XferErrorCallback = DMA_Error_Callback;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

6、编写接收代码任务,数据处理可以根据自己需要添加,这里把收到的数据再传输出去。

void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
	BaseType_t ret = pdFALSE;
  /* Infinite loop */
  while(1)
  {
    ret = xQueueReceive(queue_mes,&queue_data,portMAX_DELAY);
	if(ret == pdTRUE)
	{
		HAL_UART_Transmit(&huart1,queue_data.data,queue_data.len,100);
	}
  }
  /* USER CODE END StartDefaultTask */
}

7、主函数创建队列和使能DMA传输

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  queue_mes = xQueueCreate(10,sizeof(s_usart_data));
  Enable_Uart();             
  /* USER CODE END 2 */

  /* Call init function for freertos objects (in freertos.c) */
  MX_FREERTOS_Init();
  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

四、测试

使用串口工具按1ms的频率发送25个字节数据,测试结果如下:

从测试结果来看,没有发生丢包现象,非常稳定,符合预期目标。

后记

使用DMA双缓存+FreeRTOS队列的方式接收定长数据,适合要求实时性非常高的应用场景,DMA双缓存机制保证数据不会丢包,而且采用队列的方式传输,更加高效和安全,防止出现资源冲突的问题。

  • 7
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值