串口DMA发送--双BUFF和DMA完成中断--适用于低波特率

本文详细介绍了串口DMA发送的配置过程,涉及DMA初始化、内存与外设传输、数据缓冲区管理、DMA计数器设置以及中断服务函数的处理。重点展示了如何利用DMA提高串口数据传输效率和通道控制技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、DMA的配置

这里的一些配置介绍在前两篇介绍串口 DMA发送的时候写过了,可以去看,配置是一样的,这里不多介绍;

static INT  __dmaTxConfig (UART_SIOCHAN  *pUartSioChan)
{
    UINT32  uiCtlReVal;
    UINT32  uiDmaM0Addr;
    UINT32  uiDmaM1Addr;
    UINT32  iDmaTxChan;
    UINT32  uiBase;
    UINT32  uiDmaX       = pUartSioChan->SIOCHAN_pDmaBase;
    UINT32  uiDmaTxPeri  = pUartSioChan->SIOCHAN_iDmaTxPeri;

    uiBase      = pUartSioChan->SIOCHAN_pBase;
    iDmaTxChan  = pUartSioChan->SIOCHAN_iDmaTxChan;

    rcu_periph_clock_enable(RCU_DMA1);
    dma_single_data_parameter_struct  DmaInitStruct;

    dma_deinit(uiDmaX, iDmaTxChan);
    DmaInitStruct.direction    = DMA_MEMORY_TO_PERIPH;
    DmaInitStruct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;            /*  内存自增                    */
    DmaInitStruct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;          /*  存储和外设的传输宽度        */
    DmaInitStruct.number       = DMA_TXBUFF_SIZE;
    DmaInitStruct.periph_addr  = (UINT32)(uiBase + 0x4);
    DmaInitStruct.periph_inc   = DMA_MEMORY_INCREASE_DISABLE;           /*  外设地址固定                */
    DmaInitStruct.priority     = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(uiDmaX, iDmaTxChan, DmaInitStruct);
    dma_channel_subperipheral_select(uiDmaX, iDmaTxChan, uiDmaTxPeri); /*  外设选择                    */
    dma_flow_controller_config(uiDmaX, iDmaTxChan, DMA_FLOW_CONTROLLER_DMA);

    uiDmaM0Addr = (UINT32)_G_ucTxbuffer1;
    DMA_CHM0ADDR(uiDmaX, iDmaTxChan) = uiDmaM0Addr;                     /*  设置存储器0基地址           */
    uiDmaM1Addr = (UINT32)_G_ucTxbuffer2;
    DMA_CHM1ADDR(uiDmaX, iDmaTxChan) = uiDmaM1Addr;                     /*  设置存储器1基地址           */

    uiCtlReVal = DMA_CHCTL(uiDmaX, iDmaTxChan);
    uiCtlReVal &= ~(1 << 18);                                           /*  不设置存储切换模式          */
    uiCtlReVal &= ~(1 << 19);                                           /*  首先设置存储器0作为存储区域 */
    DMA_CHCTL(uiDmaX, iDmaTxChan) = uiCtlReVal;

    dma_channel_disable(uiDmaX, iDmaTxChan);                            /* disable DMA channel          */

    return  (ERROR_NONE);
}

二、串口发送数据

应用程序调用write函数去主动发送数据时,在驱动中首先会调用txStartUp()函数,此函数是开始通过串口发送数据,使用了DMA传输,首先这个数据就会被转移到DMA BUFF中;等开启DMA 传输,DMA会主动搬运数据到外设,而不经过CPU;

从TTY设备中获取数据放到DMA BUFF,代码如下:
DMA_TXBUFF_SIZE 表示DMA BUFF最多可以放置的字节数;

     while(usart_flag_get(uiBase, USART_FLAG_TC) == RESET);
         {
         uiBuffUsed = dma_using_memory_get(uiDmaX, iDmaTxChan);         /*  双BUFF时判断                */
         while (uiCount < DMA_TXBUFF_SIZE) {
               if (GETTXCHAR(&cChar)) {                                 /*  没有数据可以取出            */
                   break;
               }
               if (uiBuffUsed == DMA_MEMORY_0) {             /*  确定使用的是哪一个BUFF   */
                   _G_ucTxbuffer1[uiCount] = cChar;
                   uiBuffNext = DMA_MEMORY_1;

               } else {
                   _G_ucTxbuffer2[uiCount] = cChar;
                   uiBuffNext = DMA_MEMORY_0;
               }
               ++uiCount;
         }

上面将数据放置到DMA BUFF中结束或者DMA BUFF满了,需要配置DMA 计数器的值和要配置用于DMA传输的BUFF(因为这里是双BUFF):

 dma_interrupt_disable(uiDmaX, iDmaTxChan, DMA_CHXCTL_FTFIE);        /* full transfer finish interrupt enable*/
 dma_channel_disable(uiDmaX, iDmaTxChan);                           /*  通道失能                    */
  _G_TxNextDate = uiCount;
  DMA_CHCNT(uiDmaX, iDmaTxChan) = _G_TxNextDate;//dma要发送的数据多少
  dmaSwitchBuffer(uiDmaX, iDmaTxChan,_G_iTxNextBuff);
  _G_iTxNextBuff = !(_G_iTxNextBuff);

为了提高效率,如果填充完第一个DMA BUFF之后,仍然有数据需要传输,可以继续往第二个DMA BUFF中填充数据;

uiBuffNext 是使用的哪一个DMA BUFF标志;
uiCount 记录了传输到DMA BUFF中的数据大小

       if (uiCount == DMA_TXBUFF_SIZE) {           /*  上一个BUFF被填满          */
             uiCount = 0;

              while (uiCount < DMA_TXBUFF_SIZE) {
                _PrintFormat("func= %s, line = %d\r\n", __func__, __LINE__);
                   if (GETTXCHAR(&cChar)) {
                       break;
                   }
                   if (uiBuffNext == DMA_MEMORY_0) {
                       _PrintFormat("1func= %s, line = %d\r\n", __func__, __LINE__);
                       _G_ucTxbuffer1[uiCount] = cChar;

                   } else {
                       _G_ucTxbuffer2[uiCount] = cChar;
                   }
                   ++uiCount;
             }
             _G_TxNextDate = uiCount;
         }

下面就是开启DMA传输,完成中断使能,应该先使能完成中断,再开启传输;

      dma_interrupt_enable(uiDmaX, iDmaTxChan, DMA_CHXCTL_FTFIE);        /* full transfer finish interrupt enable*/
      dma_channel_enable(uiDmaX, iDmaTxChan);

DMA传输完成:指的是第一个DMA BUFF传输完成,传输完成指的是DMA CNT的值减到0,每通过DMA 传输一个字节,那么CNT就会减一;

三、完成中断的服务函数
第一个BUFF传输完成进入传输完成中断服务函数,在这个函数里面需要开启第二个DAM BUFF的传输;
并完成修改下一个使用DMA BUFF的标志;

    if((RESET != dma_interrupt_flag_get(uiDmaX, iDmaTxChan, DMA_INTF_FTFIF))) {
        UINT32 cnt = REG32(uiDmaX + (0x14 + 0x18 *iDmaTxChan));
        _PrintFormat("func= %s, line = %d,cnt = %d\r\n", __func__, __LINE__, cnt);
        dma_interrupt_flag_clear(uiDmaX, iDmaTxChan, DMA_INTC_FTFIFC);

        if (_G_TxNextDate == 0) {
            dma_channel_disable(uiDmaX, iDmaTxChan);
        }
         uiCount = 0;
         uiBuffUsed = dma_using_memory_get(uiDmaX, iDmaTxChan);          /*  传输完成自动切换            */
        _PrintFormat("func= %s, line = %d, uiBuffUsed = %d\r\n", __func__, __LINE__, uiBuffUsed);
      if (_G_TxNextDate != 0) {
        dma_channel_disable(uiDmaX, iDmaTxChan);                        /*  通道失能传输数据            */
        DMA_CHCNT(uiDmaX, iDmaTxChan) = _G_TxNextDate;                  /*  设置传输长度                */
        dmaSwitchBuffer(uiDmaX, iDmaTxChan,_G_iTxNextBuff);
        _G_iTxNextBuff = !(_G_iTxNextBuff);
        _PrintFormat("func= %s, line = %d,_G_TxNextDate = %d\r\n", __func__, __LINE__, _G_TxNextDate);
         dma_channel_enable(uiDmaX, iDmaTxChan);                         /*  中断开始传输数据            */
        uiBuffUsed = dma_using_memory_get(uiDmaX, iDmaTxChan);          /*  传输完成自动切换            */
       }
      if (_G_TxNextDate == DMA_TXBUFF_SIZE) {
          if (uiBuffUsed == DMA_MEMORY_0) {
              uiBuffSelect = DMA_MEMORY_1;
          } else {
              uiBuffSelect = DMA_MEMORY_0;
          }

          while (uiCount < DMA_TXBUFF_SIZE) {
                if (GETTXCHAR(&cTx)) {
                    _PrintFormat("func= %s, line = %d\r\n", __func__, __LINE__);
                    break;
                }
                if (uiBuffSelect == DMA_MEMORY_0) {
                     _G_ucTxbuffer1[uiCount] = cTx;
                } else {
                     _G_ucTxbuffer2[uiCount] = cTx;
                }
                ++uiCount;
          }

          _G_TxNextDate = uiCount;
      } else {
          _G_TxNextDate = 0;
          dma_interrupt_flag_clear(uiDmaX, iDmaTxChan, DMA_INTC_FTFIFC);
          _PrintFormat("func= %s, line = %d, _G_TxNextDate = %d\r\n", __func__, __LINE__, _G_TxNextDate);
      }
    }
### STM32 HAL库中DMA中断的使用教程 #### 配置DMA控制器 对于大容量的STM32芯片有两个DMA控制器,DMA1拥有7个通道而DMA2则有5个通道。每个通道都可被配置用于不同外设的数据传输操作[^1]。 #### 初始化UART并启用DMA模式 为了利用DMA进行高效的数据传输,在主程序里可以通过条件编译指令`#ifdef UART1_DMA`来决定是否采用DMA方式进行串口发送接收。具体来说,通过调用`HAL_UART_Transmit_DMA()` `HAL_UART_Receive_DMA()` 函数设置好要传送的数据缓冲区及其大小之后启动DMA传输过程[^2]。 ```c #ifdef UART1_DMA HAL_UART_Transmit_DMA(&huart1, TdataDMA, sizeof(TdataDMA)); HAL_UART_Receive_DMA(&huart1, Rdata, 1); #endif ``` #### 实现DMA中断处理机制 当DMA完成了一次完整的数据块转移后会触发相应的中断事件。此时可以在自定义的回调函数内对接收到的新鲜数据做进一步处理或是准备下一轮的数据交换工作。例如下面这段代码展示了如何在一个名为`receives_uaru_7` 的函数中重置状态变量RXU7,并再次激活DMA接收流程;而在另一个专门针对DMA结束后的响应函数`HAL_UARTEx_RxEventCallback` 中,则负责关闭当前活动中的DMA流并将接收到的信息传递给应用程序逻辑层去解析[^4]。 ```c void receives_uaru_7(void) { RXU7 = 0; HAL_UARTEx_ReceiveToIdle_DMA(&huart7, Buff_U7, sizeof_U7); } void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart->Instance == UART7 && Size <= sizeof_U7) { HAL_UART_DMAStop(huart); Buff_LEN_U7 = Size; RXU7 = 1; } } ``` #### 测试与验证 一旦上述所有步骤都被正确执行完毕,就可以烧录固件至目标板卡并通过连接PC端上的终端软件来进行实际测试了。确保打开正确的波特率其他参数匹配选项,按下复位按钮让设备进入正常运行状态,观察预期的行为表现以确认一切运作良好[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值