Z-STACK之cc2530串口驅動詳解下

         在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的流程大致應該是這樣的,其中有一些細節地方還有待理解!

 

 

 

 

 

 

 

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章