分享

Z-Stack串口DMA方式

 堆泄露栈溢出 2014-12-26

         在http://blog.csdn.net/crystal736/article/details/8541443中已经讲了Z-STACK中串口驱动的ISR方式,本文介绍串口驱动的另一种方式DMA,实际上Z-STACK中就是采用的这种方式,看hal_board_cfg.h文件中如下代码

 #if HAL_UART

// Always prefer to use DMA over ISR.
#if HAL_DMA
#ifndef HAL_UART_DMA
#if (defined ZAPP_P1) || (defined ZTOOL_P1)
#define HAL_UART_DMA  1
#elif (defined ZAPP_P2) || (defined ZTOOL_P2)
#define HAL_UART_DMA  2
#else
#define HAL_UART_DMA  1
#endif
#endif
#define HAL_UART_ISR  0
#else
#ifndef HAL_UART_ISR
#if (defined ZAPP_P1) || (defined ZTOOL_P1)
#define HAL_UART_ISR  1
#elif (defined ZAPP_P2) || (defined ZTOOL_P2)
#define HAL_UART_ISR  2
#else
#define HAL_UART_ISR  1
#endif
#endif
#define HAL_UART_DMA  0
#endif


// Used to set P2 priority - USART0 over USART1 if both are defined.
#if ((HAL_UART_DMA == 1) || (HAL_UART_ISR == 1))
#define HAL_UART_PRIPO             0x00
#else
#define HAL_UART_PRIPO             0x40
#endif


#else
#define HAL_UART_DMA  0
#define HAL_UART_ISR  0
#endif


可以看出z-stack中串口驱动使用的是DMA方式。上篇文章讲了DMA具体工作方式及原理,这里就说说串口是如何使用DMA的。先看驱动源文件_hal_uart_dma.c

先看一下串口DMA方式中很重要的一个结构体uartDMACfg_t

typedef struct
{
  uint16 rxBuf[HAL_UART_DMA_RX_MAX];
#if HAL_UART_DMA_RX_MAX < 256
  uint8 rxHead;
  uint8 rxTail;
#else
  uint16 rxHead;
  uint16 rxTail;
#endif
  uint8 rxTick;
  uint8 rxShdw;


  uint8 txBuf[2][HAL_UART_DMA_TX_MAX];
#if HAL_UART_DMA_TX_MAX < 256
  uint8 txIdx[2];
#else
  uint16 txIdx[2];
#endif
  volatile uint8 txSel;
  uint8 txMT;
  uint8 txTick;           // 1-character time in 32kHz ticks according to baud rate,
                          // to be used in calculating time lapse since DMA ISR
                          // to allow delay margin before start firing DMA, so that
                          // DMA does not overwrite UART DBUF of previous packet
  
  volatile uint8 txShdw;  // Sleep Timer LSB shadow.
  volatile uint8 txShdwValid; // TX shadow value is valid
  uint8 txDMAPending;     // UART TX DMA is pending


  halUARTCBack_t uartCB;
} uartDMACfg_t;

跟uartISRCfg_t有很多相同之处,如rxTick、rxShdw。这里txBuf定义成二维数组,以增加缓冲区长度容纳更多的数据,其他的成员在讲具体函数时会提到。先看DMA串口驱动的初始化函数

static void HalUARTInitDMA(void)
{
  halDMADesc_t *ch;


  P2DIR &= ~P2DIR_PRIPO;
  P2DIR |= HAL_UART_PRIPO;


#if (HAL_UART_DMA == 1)
  PERCFG &= ~HAL_UART_PERCFG_BIT;    // Set UART0 I/O to Alt. 1 location on P0.
#else
  PERCFG |= HAL_UART_PERCFG_BIT;     // Set UART1 I/O to Alt. 2 location on P1.
#endif
  PxSEL  |= UxRX_TX;                 // Enable Tx and Rx peripheral functions on pins.
  ADCCFG &= ~UxRX_TX;                // Make sure ADC doesnt use this.
  UxCSR = CSR_MODE;                  // Mode is UART Mode.
  UxUCR = UCR_FLUSH;                 // Flush it.


  // Setup Tx by DMA.
  ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );


  // The start address of the destination.
  HAL_DMA_SET_DEST( ch, DMA_UDBUF );


  // Using the length field to determine how many bytes to transfer.
  HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );


  // One byte is transferred each time.
  HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );


  // The bytes are transferred 1-by-1 on Tx Complete trigger.
  HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
  HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );


  // The source address is incremented by 1 byte after each transfer.
  HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );


  // The destination address is constant - the Tx Data Buffer.
  HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );


  // The DMA Tx done is serviced by ISR in order to maintain full thruput.
  HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_ENABLE );


  // Xfer all 8 bits of a byte xfer.
  HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );


  // DMA has highest priority for memory access.
  HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );


  // Setup Rx by DMA.
  ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );


  // The start address of the source.
  HAL_DMA_SET_SOURCE( ch, DMA_UDBUF );


  // Using the length field to determine how many bytes to transfer.
  HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );


  /* The trick is to cfg DMA to xfer 2 bytes for every 1 byte of Rx.
   * The byte after the Rx Data Buffer is the Baud Cfg Register,
   * which always has a known value. So init Rx buffer to inverse of that
   * known value. DMA word xfer will flip the bytes, so every valid Rx byte
   * in the Rx buffer will be preceded by a DMA_PAD char equal to the
   * Baud Cfg Register value.
   */
  HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );


  // The bytes are transferred 1-by-1 on Rx Complete trigger.
  HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE_REPEATED );
  HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_RX );


  // The source address is constant - the Rx Data Buffer.
  HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );


  // The destination address is incremented by 1 word after each transfer.
  HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_1 );
  HAL_DMA_SET_DEST( ch, dmaCfg.rxBuf );
  HAL_DMA_SET_LEN( ch, HAL_UART_DMA_RX_MAX );


  // The DMA is to be polled and shall not issue an IRQ upon completion.
  HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );


  // Xfer all 8 bits of a byte xfer.
  HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );


  // DMA has highest priority for memory access.
  HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );

}

在开头的这两句 P2DIR &= ~P2DIR_PRIPO; P2DIR |= HAL_UART_PRIPO; 在串口驱动详解上中没弄明白,这次看了下datasheet,发现P2DIR的6、7位是Port 0 peripheral priority control. These bits determine the order of priority in the case   when PERCFG assigns several peripherals to the same pins.即控制P0口外设功能的优先级。将P2DIR高两位设为00,则1st priority: USART ,02nd priority: USART 1,3rd priority: Timer 1,P0外设功能有USART0/1,TIMER1。

然后设定UART的位置为P0或P1,使能外设功能,设定USART的mode为UART,清除串口内容。

然后就是设置TX和RX的DMA配置描述符,其通道号分别为4、3。这个设置过程跟FLASH控制器的DMA配置描述符的设置过程相似,只是设置参数不同。从代码中看出RX和TX的DMA配置描述符的不同,TX采用逐个字节的传输,而RX采用逐个字(即两个字节)地传输,TX的触发采用HAL_DMA_TMODE_SINGLE,即在DMA触发之后开始传输,传输完了就结束DMA传输,RX的触发采用HAL_DMA_TMODE_SINGLE_REPEATED,即在DMA触发之后开始传输,传输完之后rearm RX的DMA,则DMA重新开始传输下一个数据。另外就是TX使能了DMA中断,RX禁用了DMA中断。


再看一下HalUARTOpenDMA做了哪些工作

static void HalUARTOpenDMA(halUARTCfg_t *config)
{
  dmaCfg.uartCB = config->callBackFunc;
  // Only supporting subset of baudrate for code size - other is possible.
  HAL_UART_ASSERT((config->baudRate == HAL_UART_BR_9600) ||
                  (config->baudRate == HAL_UART_BR_19200) ||
                  (config->baudRate == HAL_UART_BR_38400) ||
                  (config->baudRate == HAL_UART_BR_57600) ||
                  (config->baudRate == HAL_UART_BR_115200));
  
  if (config->baudRate == HAL_UART_BR_57600 ||
      config->baudRate == HAL_UART_BR_115200)
  {
    UxBAUD = 216;
  }
  else
  {
    UxBAUD = 59;
  }
  
  switch (config->baudRate)
  {
    case HAL_UART_BR_9600:
      UxGCR = 8;
      dmaCfg.txTick = 35; // (32768Hz / (9600bps / 10 bits))
                          // 10 bits include start and stop bits.
      break;
    case HAL_UART_BR_19200:
      UxGCR = 9;
      dmaCfg.txTick = 18;
      break;
    case HAL_UART_BR_38400:
      UxGCR = 10;
      dmaCfg.txTick = 9;
      break;
    case HAL_UART_BR_57600:
      UxGCR = 10;
      dmaCfg.txTick = 6;
      break;
    default:
      // HAL_UART_BR_115200
      UxGCR = 11;
      dmaCfg.txTick = 3;
      break;
  }


  // 8 bits/char; no parity; 1 stop bit; stop bit hi.
  if (config->flowControl)
  {
    UxUCR = UCR_FLOW | UCR_STOP;
    PxSEL |= HAL_UART_Px_CTS;
    // DMA Rx is always on (self-resetting). So flow must be controlled by the S/W polling the Rx
    // buffer level. Start by allowing flow.
    PxOUT &= ~HAL_UART_Px_RTS;
    PxDIR |=  HAL_UART_Px_RTS;
  }
  else
  {
    UxUCR = UCR_STOP;
  }


  dmaCfg.rxBuf[0] = *(volatile uint8 *)DMA_UDBUF;  // Clear the DMA Rx trigger.
  HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_RX);
  HAL_DMA_ARM_CH(HAL_DMA_CH_RX);
  osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);


  UxCSR |= CSR_RE;
  UxDBUF = 0;  // Prime the DMA-ISR pump.
  
  // Initialize that TX DMA is not pending
  dmaCfg.txDMAPending = FALSE;
  dmaCfg.txShdwValid = FALSE;
}

首先根据配置设定UART的波特率及txTick,如果采用流控制,则要设置UxUCR、PxSEL、PxOUT、PxDIR。一般都没有采用硬件流控制。

dmaCfg.rxBuf[0] = *(volatile uint8 *)DMA_UDBUF;这行代码读取uart的缓冲寄存器,其作用是清除Rx的DMA触发。

然后启动Rx的DMA传输,注意到初始化函数中有这段注释:

/* The trick is to cfg DMA to xfer 2 bytes for every 1 byte of Rx.
   * The byte after the Rx Data Buffer is the Baud Cfg Register,
   * which always has a known value. So init Rx buffer to inverse of that
   * known value. DMA word xfer will flip the bytes, so every valid Rx byte
   * in the Rx buffer will be preceded by a DMA_PAD char equal to the
   * Baud Cfg Register value.
   */

所以将DMA_PAD ^ 0xFF填充整个rxBuf,即osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);

接着使能串口接收,将UxDBUF置0,Prime the DMA-ISR pump,这一句不知道具体作用是什么。

最后将dmaCfg的txDMAPending和txShdwValid置为FALSE。

看看HalUARTReadDMA这个函数,它从UART中读取一些数据到Buf中,

static uint16 HalUARTReadDMA(uint8 *buf, uint16 len)
{
  uint16 cnt;


  for (cnt = 0; cnt < len; cnt++)
  {
    if (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
    {
      break;
    }
    *buf++ = HAL_UART_DMA_GET_RX_BYTE(dmaCfg.rxHead);
    HAL_UART_DMA_CLR_RX_BYTE(dmaCfg.rxHead);
    if (++(dmaCfg.rxHead) >= HAL_UART_DMA_RX_MAX)
    {
      dmaCfg.rxHead = 0;
    }
  }
  PxOUT &= ~HAL_UART_Px_RTS;  // Re-enable the flow on any read.
  return cnt;
}

这个函数里面主要理解几个宏定义。读取数据的时候是从rxHead位置开始读取,每读一个数据rxHead就加1。串口在每次接收到一个数据时,是将其放在rxBuf每个16位字的前八位,后八位为DMA_PAD,所以当该数据的高八位等于DMA_PAD时表明rxBuf中有数据了。

#define HAL_UART_DMA_NEW_RX_BYTE(IDX)  (DMA_PAD == HI_UINT16(dmaCfg.rxBuf[(IDX)]))

根据Rx的DMA配置知道,串口每次接收一个数据完成时便触发DMA传输,此时将U0DBUF中的8位数据传送到rxBuf中

,rxBuf中的数据位置是逐字增加的,假如串口接收到10个数据,则rxBuf中前10个位置便是这10个数据,只是每个数据后面有填充位为DMA_PAD 。这里有一个不明白的地方:在初始化的时候rxBuf中填充的是DMA_PAD^0xFF,为什么将HI_UINT16(dmaCfg.rxBuf[(IDX)与DMA_PAD比较,且两者相同时就说明rxBuf中有数据了。然后就是每次串口接收数据将其发送到rxBuf中某个内存的低地址还是高地址,this is a problem!从宏定义上来看,串口中的数据和DMA_PAD^0xFF的值组成16位数,且串口中的数据位低八位,DMA_PAD^0xFF为高八位。在数据后面加一个PAD位有什么作用呢?Thinking...

    以上问题等想通了再tell you。

    Come back!HalUARTReadDMA函数返回读取到数据的长度。

   接下来看看这个函数HalUARTWriteDMA,其功能是将buf中的数据写进txBuf,写的数据长度为len

static uint16 HalUARTWriteDMA(uint8 *buf, uint16 len)
{
  uint16 cnt;
  halIntState_t his;
  uint8 txIdx, txSel;


  // Enforce all or none.
  if ((len + dmaCfg.txIdx[dmaCfg.txSel]) > HAL_UART_DMA_TX_MAX)
  {
    return 0;
  }


  HAL_ENTER_CRITICAL_SECTION(his);
  txSel = dmaCfg.txSel;
  txIdx = dmaCfg.txIdx[txSel];
  HAL_EXIT_CRITICAL_SECTION(his);


  for (cnt = 0; cnt < len; cnt++)
  {
    dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
  }


  HAL_ENTER_CRITICAL_SECTION(his);
  if (txSel != dmaCfg.txSel)
  {
    HAL_EXIT_CRITICAL_SECTION(his);
    txSel = dmaCfg.txSel;
    txIdx = dmaCfg.txIdx[txSel];


    for (cnt = 0; cnt < len; cnt++)
    {
      dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
    }
    HAL_ENTER_CRITICAL_SECTION(his);
  }


  dmaCfg.txIdx[txSel] = txIdx;


  if (dmaCfg.txIdx[(txSel ^ 1)] == 0)
  {
    // TX DMA is expected to be fired
    dmaCfg.txDMAPending = TRUE;
  }
  HAL_EXIT_CRITICAL_SECTION(his);
  return cnt;
}

下面分析一下函数的具体代码实现。先说一下uartDMACfg_t中txSel是指txBuf二维数组中的哪一维,0或者1,例如txBuf中第一维数据满了,第二维数据未满,则其为1,否则为0;txIdx[2]指示txBuf中某一维数组的数据的具体位置(应该是指示txBuf中数据的末位置),例如txBuf中第一维数据满了,则txIdx[0]则为HAL_UART_DMA_TX_MAX,txIdx[1]为第二维数组中数据的长度,否则txIdx[0]为当前数据的长度,txIdx[1]为0;

if ((len + dmaCfg.txIdx[dmaCfg.txSel]) > HAL_UART_DMA_TX_MAX) { return 0; }

如果要写入的数据加上rxBuf中的数据长度大于HAL_UART_DMA_TX_MAX,则直接返回0,此时不能写入数据

  txSel = dmaCfg.txSel;
  txIdx = dmaCfg.txIdx[txSel];
局部变量txSel和txIdx分别暂存dmaCfg中的txSel和txIdx。

for (cnt = 0; cnt < len; cnt++)
  {
    dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
  }

将buf中的数据复制到txBuf中。if (txSel != dmaCfg.txSel)后面的一段代码没看懂,既然前面将dmaCfg.txSel赋值给了txSel了,那么他们必然相等,不用比较,不过还有一种可能就是在这个之前,发生了DMA中断,但是在DMA中断程序中也没看到dmaCfg.txSel的变更,这个令人相当费解。Let's pass it!

if (dmaCfg.txIdx[(txSel ^ 1)] == 0)
  {
    // TX DMA is expected to be fired
    dmaCfg.txDMAPending = TRUE;
  } 

接下来判断发送的另一个缓冲区是否为空(其实将发送缓冲区看做是二维数组,也可以看成是两个长度相同的一维数组,即两个缓冲区)。如果另一个缓冲区没有数据,则将txDMAPending 置为TRUE,在下一次调用串口轮询函数HalUARTPollDMA时,将txBuf中的数据发送出去。

接下来看看HalUARTPollDMA这个非常重要的函数。

static void HalUARTPollDMA(void)
{
  uint16 cnt = 0;
  uint8 evt = 0;

  if (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
  {
    uint16 tail = findTail();

    // If the DMA has transferred in more Rx bytes, reset the Rx idle timer.
    if (dmaCfg.rxTail != tail)
    {
      dmaCfg.rxTail = tail;

      // Re-sync the shadow on any 1st byte(s) received.
      if (dmaCfg.rxTick == 0)
      {
        dmaCfg.rxShdw = ST0;
      }
      dmaCfg.rxTick = HAL_UART_DMA_IDLE;
    }
    else if (dmaCfg.rxTick)
    {
      // Use the LSB of the sleep timer (ST0 must be read first anyway).
      uint8 decr = ST0 - dmaCfg.rxShdw;

      if (dmaCfg.rxTick > decr)
      {
        dmaCfg.rxTick -= decr;
        dmaCfg.rxShdw = ST0;
      }
      else
      {
        dmaCfg.rxTick = 0;
      }
    }
    cnt = HalUARTRxAvailDMA();
  }
  else
  {
    dmaCfg.rxTick = 0;
  }

  if (cnt >= HAL_UART_DMA_FULL)
  {
    evt = HAL_UART_RX_FULL;
  }
  else if (cnt >= HAL_UART_DMA_HIGH)
  {
    evt = HAL_UART_RX_ABOUT_FULL;
    PxOUT |= HAL_UART_Px_RTS;
  }
  else if (cnt && !dmaCfg.rxTick)
  {
    evt = HAL_UART_RX_TIMEOUT;
  }

  if (dmaCfg.txMT)
  {
    dmaCfg.txMT = FALSE;
    evt |= HAL_UART_TX_EMPTY;
  }

  if (dmaCfg.txShdwValid)
  {
    uint8 decr = ST0;
    decr -= dmaCfg.txShdw;
    if (decr > dmaCfg.txTick)
    {
      // No protection for txShdwValid is required
      // because while the shadow was valid, DMA ISR cannot be triggered
      // to cause concurrent access to this variable.
      dmaCfg.txShdwValid = FALSE;
    }
  }
 
  if (dmaCfg.txDMAPending && !dmaCfg.txShdwValid)
  {
    // UART TX DMA is expected to be fired and enough time has lapsed since last DMA ISR
    // to know that DBUF can be overwritten
    halDMADesc_t *ch = HAL_DMA_GET_DESC1234(HAL_DMA_CH_TX);
    halIntState_t intState;

    // Clear the DMA pending flag
    dmaCfg.txDMAPending = FALSE;
   
    HAL_DMA_SET_SOURCE(ch, dmaCfg.txBuf[dmaCfg.txSel]);
    HAL_DMA_SET_LEN(ch, dmaCfg.txIdx[dmaCfg.txSel]);
    dmaCfg.txSel ^= 1;
    HAL_ENTER_CRITICAL_SECTION(intState);
    HAL_DMA_ARM_CH(HAL_DMA_CH_TX);
    do
    {
      asm("NOP");
    } while (!HAL_DMA_CH_ARMED(HAL_DMA_CH_TX));
    HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);
    HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX);
    HAL_EXIT_CRITICAL_SECTION(intState);
  }

  if (evt && (dmaCfg.uartCB != NULL))
  {
    dmaCfg.uartCB(HAL_UART_DMA-1, evt);
  }
}

 

这个poll函数即轮询发送缓冲区也轮询接收缓冲区。第一个if语句里面判断接收缓冲区中是否有数据,如果有则tail = findTail(),找到缓冲区数据的末位置,如果dmaCfg.rxTail != tail,则还有数据要传输,分别设置dmaCfg.rxShdw和dmaCfg.rxTick,这两个成员跟串口驱动ISR方式中isrCfg的这两个成员意义相同,这里不再赘余。如果没有数据需要接收了,则判断dmaCfg.rxTick是否为0,如果不为0,则将其置为减去上次轮询的时候到本次程序跑的时间的值,decr为程序跑的时间,如果rxTick大于decr,则说明还没有到进行数据传输的时间,如果小于则时间到了将rxTick置为0,在下一次轮询的时候,则可以调用回调函数读取接收缓冲区的数据了。该代码块最后调用

HalUARTRxAvailDMA()计算出接收缓冲区的有效数据的长度。

 如果HAL_UART_DMA_NEW_RX_BYTE判断的结果是缓冲区没有数据,则仅将txTick置为0。

第二个if语句若cnt >= HAL_UART_DMA_FULL,则置串口事件HAL_UART_RX_FULL表示缓冲区已满。

接下来如果cnt不等于0,且dmaCfg.rxTick为0,则置串口事件HAL_UART_RX_TIMEOUT表示到了读取缓冲区数据的事件,赶紧调用串口回调函数读取吧!

 if (dmaCfg.txMT)
  {
    dmaCfg.txMT = FALSE;
    evt |= HAL_UART_TX_EMPTY;
  }

txMT标志发送缓冲区是否为空,为TRUE为空,置为HAL_UART_TX_EMPTY事件。接下来两个if语句至关重要。

if (dmaCfg.txShdwValid)
  {
    uint8 decr = ST0;
    decr -= dmaCfg.txShdw;
    if (decr > dmaCfg.txTick)
    {
      // No protection for txShdwValid is required
      // because while the shadow was valid, DMA ISR cannot be triggered
      // to cause concurrent access to this variable.
      dmaCfg.txShdwValid = FALSE;
    }
  }

txShdValid这个成员变量是对发送缓冲区有效的,即如果为true,则发送数据只有经过txTick时间之后才能进行数据传输,否则则说明txTick时间已到,这个txTick是串口初始化的时候就已经确定的,是个定值,跟rxTick不一样。所以就定义了txShdwValid这个成员变量。

第二个if语句就先判断txDMAPending和txShdwValid,如果前者为TRUE,后者为FALSE,则UART TX DMA is expected to be fired and enough time has lapsed since last DMA ISR to know that DBUF can be overwritten;真正发送数据到UxDBUF中 是在这里面进行的!因为这里重新配置了TX的DMA配置描述符,有一句代码HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX);手动开启DMA传输,则数据直接从dmaCfg.txBuf[dmaCfg.txSel]传输到UxDBUF中,dmaCfg.txSel ^= 1;这一句代码有一点不解,意思是将txSel取为另一个缓冲区,但是这是为何呢!

 

最后一个if语句是evt不为空且回调函数不为空时,调用串口的回调函数!

最后看一下这个TX的DMA中断程序

void HalUARTIsrDMA(void)
{
  HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);

  // Indicate that the other buffer is free now.
  dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0;
  dmaCfg.txMT = TRUE;
 
  // Set TX shadow
  dmaCfg.txShdw = ST0;
  dmaCfg.txShdwValid = TRUE;

  // If there is more Tx data ready to go, re-start the DMA immediately on it.
  if (dmaCfg.txIdx[dmaCfg.txSel])
  {
    // UART TX DMA is expected to be fired
    dmaCfg.txDMAPending = TRUE;
  }
}

这个中断函数在什么时候执行呢?根据datasheet,每当Tx complete时便发生DMA中断。这个Tx complete是指每次从UxDBUF中发送一个字节的数据出去完成时。

dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0;
  dmaCfg.txMT = TRUE;

这两句代码有点不解,按注释意思是中断发生时另一个缓冲区为空,执行这两句代码。。。??

然后执行dmaCfg.txShdw = ST0; dmaCfg.txShdwValid = TRUE; 这是在下一次进行DMA传输时需要设置的。

if (dmaCfg.txIdx[dmaCfg.txSel])
  {
    // UART TX DMA is expected to be fired
    dmaCfg.txDMAPending = TRUE;
  }

最后如果本缓冲区中还有数据,则txDMAPending置为TRUE,以便将剩余的数据进行DMA传输给串口。

 

以上是大致串口DMA驱动的代码详解分析,其中有三个问题没有弄明白,一个是Tx时,两个缓冲区是如何进行切换使用的?一个就是DMA_PAD的问题!还有一个就是if (txSel != dmaCfg.txSel)这个地方!

接下来来简单分析一下串口DMA驱动的流程!

在使用串口时,对于Rx,即从串口读取一定字符串的过程;系统在串口数据到来之前调用HalUARTPollDMA函数轮询串口中是否有数据。这里说一下,当UxDBUF中有数据时,直接利用DMA传输,一一将UxDBUF的数据发送到了rxBuf中了,而HalUARTPollDMA轮询时候只是检查rxBuf中时候有新的数据,就是宏HAL_UART_DMA_NEW_RX_BYTE的作用。当检查到rxBuf中有数据时,则会经过很小的一段时间,这个时间可以通过HAL_UART_DMA_IDLE计算出来,此值为198,乘以32KHz的时钟频率即是!程序中会判断是否经过了这么长时间,如果是,则会置相应串口事件,从而调用串口回调函数,在这个回调函数里面,通常我们会调用HalUARTReadDMA函数将缓冲区中的数据读取出来。

对于Tx,即向串口中发送一定字符串的过程,这个过程可能略显复杂。但是和Rx有相似之处。通常在我们的Z-STACK的应用程序中调用HalUARTWriteDMA向发送缓冲区中写字符串,这个是在HalUARTPollDMA轮询之前进行的,即如果没有调用HalUARTWriteDMA,则HalUARTPollDMA将不会检测到有数据要发送到串口!当txBuf中有数据了,那么接下来发生什么呢?在HalUARTPollDMA函数中将强制启动DMA传输,将txBuf中的数据发送打UxDBUF中去,这个DMA传输是一个字节一个字节的传输,当一个字节传输完成时,串口将UxDBUF中数据发送出去,然后发生DMA 中断,在中断函数里面,判断是否还有数据要发送,如果有,则当系统轮询调用HalUARTPollDMA这个函数进行剩余数据的DMA传输,Tx的流程大致应该是这样的,其中有一些细节地方还有待理解!

 

 

 

 

 

 

 

   

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多