串口DMA的使用

一.什么是DMA

DMA方式,Direct Memory Access,也称为成组数据传送方式,有时也称为直接内存操作。DMA方式在数据传送过程中,没有保存现场、恢复现场之类的工作。由于CPU根本不参加传送操作,因此就省去了CPU取指令、取数、送数等操作。内存地址修改、传送字 个数的计数等等,也不是由软件实现,而是用硬件线路直接实现的。所以DMA方式能满足高速I/O设备的要求,也有利于CPU效率的发挥。

二.使用串口DMA的操作流程

2.1一点题外话

本文以stm32的芯片为例进行说明,当然其他国产的芯片只要内核和st的芯片一样,使用流程应该大同小异。很早以前使用st的芯片用的都是标准库,使用外设的时候都需要自己按照相应的流程写初始化配置参数的代码。后面st推出了HAL库以及LL库并且有专门的图形配置生成代码的工具cubemx,使用起来非常方便,而且也不会出现某些参数配置遗漏导致外设使用不起来的情况。之前我写的文章也推荐过大家使用,操作流程还是很简单的。

使用cubemx是方便,但当你由新手变成老手之后,就会发现,cubemx生成的代码比较“啰嗦”。当然我现在依然还在用cubemx生成代码,但是,我主要用来查看生成的外设配置部分。比如我要使用串口,我就会用它生成串口配置代码,然后将该部分配置代码复制到工程中去。有时候,不会完全复制,有些地方操作某个寄存器配置就可以搞定的,而HAL库要调用一个接口函数,有些地方对执行速度要求高的,直接自己用寄存器配置,而HAL库调用接口函数时里面有各种判断,比较耗时,等等。

工具是方便我们做事的,但是我们也不能被工具完全“绑架”了。明白我的意思吧。

2.2配置串口DMA

首先还是要配置基本的串口参数,然后将普通的io口引脚配置成串口的复用功能,再配置DMA参数,最后将串口链接到DMA。

下面附上相应的源码

UART_HandleTypeDef huart2;            //串口的句柄
DMA_HandleTypeDef hdma_usart2_rx; //串口DMA接收数据的句柄
DMA_HandleTypeDef hdma_usart2_tx; //串口DMA发送数据的句柄

//串口功能参数配置
void MX_USART2_UART_Init(void)
{
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
    //DMA参数配置
    my_usart_dma_recv(usart_dma_recv_buf,sizeof(usart_dma_recv_buf));
}
//串口io初始化配置,并开启串口中断,设置其中断分组
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART2)
  {
  /* USER CODE BEGIN USART2_MspInit 0 */

  /* USER CODE END USART2_MspInit 0 */
    /* USART2 clock enable */
    __HAL_RCC_USART2_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART2 GPIO Configuration    
    PA2     ------> USART2_TX
    PA15 (JTDI)     ------> USART2_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    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_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF3_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART2 interrupt Init */
    //HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
    //HAL_NVIC_EnableIRQ(USART2_IRQn);
  /* USER CODE BEGIN USART2_MspInit 1 */

  /* USER CODE END USART2_MspInit 1 */
  }
}
//清除串口的io配置
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART2)
  {
  /* USER CODE BEGIN USART2_MspDeInit 0 */

  /* USER CODE END USART2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART2_CLK_DISABLE();
  
    /**USART2 GPIO Configuration    
    PA2     ------> USART2_TX
    PA15 (JTDI)     ------> USART2_RX 
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2|GPIO_PIN_15);

    /* USART2 DMA DeInit */
    HAL_DMA_DeInit(uartHandle->hdmarx);
      HAL_DMA_DeInit(uartHandle->hdmatx);
    /* USART2 interrupt Deinit */
   // HAL_NVIC_DisableIRQ(USART2_IRQn);
  /* USER CODE BEGIN USART2_MspDeInit 1 */

  /* USER CODE END USART2_MspDeInit 1 */
  }
}
//串口接收DMA参数配置
//寄存器地址mar 传输数量 ndtr
void my_usart_dma_recv(uint32_t mar,uint16_t ndtr)
{
        /* DMA controller clock enable */
         __HAL_RCC_DMA1_CLK_ENABLE();

        /* USART2 DMA Init */
    /* USART2_RX Init */
    hdma_usart2_rx.Instance = DMA1_Channel6;
    hdma_usart2_rx.Init.Request = DMA_REQUEST_2;
    hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_rx.Init.Mode = DMA_NORMAL;
    hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_usart2_rx.Instance->CNDTR=ndtr;
      hdma_usart2_rx.Instance->CMAR = (uint32_t)mar;
        hdma_usart2_rx.Instance->CPAR = (uint32_t)(&huart2.Instance->RDR);
      //这个寄存器的配置不能遗漏
        UART2_Handler.Instance->CR3 |= USART_CR3_DMAR;//使能串口dma接收
    if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
        /* USART2_TX Init */
    hdma_usart2_tx.Instance = DMA1_Channel7;
    hdma_usart2_tx.Init.Request = DMA_REQUEST_2;
    hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_tx.Init.Mode = DMA_NORMAL;
    hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx);

      __HAL_DMA_DISABLE(&hdma_usart2_rx);//先关闭DMA
        __HAL_DMA_DISABLE(&hdma_usart2_tx);//先关闭DMA
      /* DMA interrupt init */
    /* DMA1_Channel6_IRQn interrupt configuration */
      __HAL_DMA_ENABLE_IT(&hdma_usart2_rx,DMA_IT_TC);//使能dma接收完成中断
      __HAL_DMA_CLEAR_FLAG(&hdma_usart2_rx,DMA_FLAG_TC6);
        HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
        __HAL_DMA_ENABLE(&hdma_usart2_rx);//启动DMA
}
//串口dma中断函数
void DMA1_Channel6_IRQHandler(void)
{
  //HAL_DMA_IRQHandler(&hdma_usart2_rx);
  if(__HAL_DMA_GET_FLAG(&hdma_usart2_rx,DMA_FLAG_TC3)){//串口dma接收完成中断
    __HAL_DMA_CLEAR_FLAG(&hdma_usart2_rx,DMA_FLAG_TC3);
  }
}
//DMA发送函数
void myHAL_UART_Transmit_DMA(uint8_t buf[], uint8_t len)
{
        //这个寄存器的配置不能遗漏
      UART2_Handler.Instance->CR3 |= USART_CR3_DMAT;//使能串口dma发送
        __HAL_DMA_DISABLE(&hdma_usart2_tx);//先关闭DMA
        hdma_usart2_tx.Instance->CNDTR=len;
      hdma_usart2_tx.Instance->CMAR = (uint32_t)buf;
        hdma_usart2_tx.Instance->CPAR = (uint32_t)(&UART2_Handler.Instance->TDR);
        __HAL_DMA_ENABLE(&hdma_usart2_tx);//启动DMA
    
    while(!__HAL_DMA_GET_FLAG(&hdma_usart2_tx,DMA_FLAG_TC7)){
        
    }
    __HAL_DMA_CLEAR_FLAG(&hdma_usart2_tx, DMA_FLAG_TC7 );
}

//获取DMA接收数据长度
__HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
这个宏非常实用,可以实时监测当前接收到的数据量
数据量大小 = 初始配置时传输设置的数据量大小 - __HAL_DMA_GET_COUNTER(__HANDLE__);

配合DMA通道表格查看

其实使用多了外设DMA之后,你会发现,所有的DMA配置基本流程都是差不多的,记住串口这一个配置流程,推演到其他外设,比如,ADC,PWM等等。时刻牢记掌握方法和规律,举一反三的能力很重要,能够为我们省很多事,也能够大大提高工作效率。

知道如何使用串口DMA之后,就可以实现所谓的不定长数据接收,配合串口本身的空闲中断,等等,通过各种组合可以玩出很多花样,你甚至可以不使用串口中断,DMA中断。通过__HAL_DMA_GET_COUNTER(__HANDLE__)去实时用扫描的方式查看接收数据的状态,进行解码数据,然后重新赋初值即可。当然,为什么会有这样的需求呢,这种需求很少,就是尽量不希望有任何中断打断主任务的操作。只有主任务自己退出时,才能去处理其他事情。我做过一个项目就有这种要求。所以才会有上面的想法。

三.总结

干嵌入式就需要多实践,多操作,多调试。纸上得来终觉浅,绝知此事要躬行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值