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

        Z-STACK中串口採用DMA和ISR兩種方式,本章主要講解ISR方式的串口驅動。在OASL操作系統輪詢時調用了Hal_ProcessPoll ()函數,在此函數中如果定義了HAL_UART=TRUE,則輪詢串口,看時候有數據要發送或有數據要接收。定位到HalUARTPoll()函數中,如果是採用ISR方式即HAL_UART_ISR爲1或2時,調用ISR串口輪詢函數HalUARTPollISR(),在這個函數中調用了串口的回調函數,這個過程等會兒講。

       先來看看頭文件hal_uart.h,此頭文件中定義了typedef void (*halUARTCBack_t) (uint8 port, uint8 event);串口回調函數的函數指針,定義了串口緩衝區halUARTBufControl_t結構體以及針對串口配置的結構體halUARTCfg_t,halUARTIoctl_t結構體沒用到不用管。其中還有相關的宏定義,這個根據datasheet看。

      接下來看_hal_uart_isr.c文件。HAL_UART_ISR_RX_AVAIL()這個宏定義是返回接收緩衝區中可接收數據的長度大小,HAL_UART_ISR_TX_AVAIL()這個宏定義返回發送緩衝區中空位置的長度大小。下面看看這個結構體


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

  uint8 txBuf[HAL_UART_ISR_TX_MAX];
#if HAL_UART_ISR_TX_MAX < 256
  volatile uint8 txHead;
  uint8 txTail;
#else
  volatile uint16 txHead;
  uint16 txTail;
#endif
  uint8 txMT;

  halUARTCBack_t uartCB;
} uartISRCfg_t;

這個是串口ISR方式的發送和接收緩衝區結構體,裏面具體成員的意義弄懂了,後面幾個串口驅動函數就很好理解了。rxBuf指接收緩衝區,大小有不同的定義,rxHead指示接收緩衝區接收到的數據的首位置或首地址,rxTail指示接收緩衝區接收到的數據的末位置。rxTick這個成員表示串口經過rxtick時間之後開始發送數據。在輪詢串口的時候,即在HalUARTPollISR()函數中會檢查rxTick是否爲0,如果爲0,才調用串口的回調函數進行數據發送,如果不爲0,說明還沒有到發送數據的時間,得繼續等待直到rxTick爲0,這裏對rxTick的計時是採用了cc2530的睡眠定時器,等會兒在後面會講。rxShdw這個參數表示當前睡眠定時器的ST0,即睡眠定時器的count value的低八位。txBuf就是發送緩衝區,需要發送的數據都放在此緩衝區裏面,一旦允許發送中斷,就開始將發送緩衝區裏面的數據發送出去。txHead

、txTail和rxHead、rxTail含義相同,txMT是指發送緩衝區滿或者空的標誌位。uartCB爲串口的回調函數,具體內容由自己定義,在輪詢中被調用。static uartISRCfg_t isrCfg;聲明瞭一個靜態變量isrCfg,此變量只在本源文件中起作用,是針對於ISR方式的串口配置變量。

 

       看看串口初始化函數HalUARTInitISR(),

static void HalUARTInitISR(void)
{
  // Set P2 priority - USART0 over USART1 if both are defined.
  P2DIR &= ~P2DIR_PRIPO;
  P2DIR |= HAL_UART_PRIPO;  //沒看懂這個

#if (HAL_UART_ISR == 1)
  PERCFG &= ~HAL_UART_PERCFG_BIT;    // Set UART0 I/O location to P0.
#else
  PERCFG |= HAL_UART_PERCFG_BIT;     // Set UART1 I/O location to 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.
}

初始化代碼中對P2的操作沒看懂,看了datasheet,串口跟P2一毛錢關係都沒有,而且P2DIR 是方向寄存器,如果誰知道求指教。接下來初始化的過程對照datasheet中串口部分,容易明白。在這裏說一下,我這個板子上P0位置用作串口,P1的串口位置被SPI複用,用來仿真調試了,所以HAL_UART_ISR就爲1,那初始化中的幾個宏定義就知道其值了。

  看一下串口打開函數static void HalUARTOpenISR(halUARTCfg_t *config)

isrCfg.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;
      break;
    case HAL_UART_BR_19200:
      UxGCR = 9;
      break;
    case HAL_UART_BR_38400:
    case HAL_UART_BR_57600:
      UxGCR = 10;
      break;
    default:
      UxGCR = 11;
      break;
  }

  // 8 bits/char; no parity; 1 stop bit; stop bit hi.
  if (config->flowControl)
  {
    UxUCR = UCR_FLOW | UCR_STOP;
    PxSEL |= HAL_UART_Px_RTS | HAL_UART_Px_CTS;
  }
  else
  {
    UxUCR = UCR_STOP;
  }

  UxCSR |= CSR_RE;
  URXxIE = 1;
  UxDBUF = 0;  // Prime the ISR pump.

 將回調函數賦值給isrCfg的uartCB成員,然後根據設置的不同波特率對UxBAUD和UxGCR作相應的設置,最後使能串口接收以及打開串口接收中斷,將UxDBUF清零。

看下串口讀數據函數

static uint16 HalUARTReadISR(uint8 *buf, uint16 len)
{
  uint16 cnt = 0;

  while ((isrCfg.rxHead != isrCfg.rxTail) && (cnt < len))
  {
    *buf++ = isrCfg.rxBuf[isrCfg.rxHead++];
    if (isrCfg.rxHead >= HAL_UART_ISR_RX_MAX)
    {
      isrCfg.rxHead = 0;
    }
    cnt++;
  }

  return cnt;
}

這個函數和串口寫數據函數HalUARTWriteISR是在回調函數中被調用,由用戶自定義操作。此函數只是將接收緩衝區中的數據賦值給buf然後相應rxHead增加len個長度,返回讀的數據長度。這個函數要對照串口接收終端函數理解

 

#if (HAL_UART_ISR == 1)
HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )
#else
HAL_ISR_FUNCTION( halUart1RxIsr, URX1_VECTOR )
#endif
{
  uint8 tmp = UxDBUF;
  isrCfg.rxBuf[isrCfg.rxTail] = tmp;

  // Re-sync the shadow on any 1st byte received.
  if (isrCfg.rxHead == isrCfg.rxTail)
  {
    isrCfg.rxShdw = ST0;
  }

  if (++isrCfg.rxTail >= HAL_UART_ISR_RX_MAX)
  {
    isrCfg.rxTail = 0;
  }

  isrCfg.rxTick = HAL_UART_ISR_IDLE;
}

在接收中斷函數中,將接收到的一字節數據填入rxBuf中,注意在接收數據時是將數據填入rxBuf,然後將rxTail加1,即在緩衝區讀數據時是在緩衝區首位置開始讀,在串口接收數據時是將數據放在rxTail即緩衝區末位置。看最後一句代碼,將HAL_UART_ISR_IDLE即198賦值給rxTick,這個值爲系統輪詢串口時是否需要操作串口的等待時間,其實可以計算一下,採用的是32.768kHz的外部時鐘,用198除以32.768約等於6ms,即每次等待時間爲6ms,如果每次系統經過輪詢之後檢查6ms是否用完,是則調用回調函數。 

if (isrCfg.rxHead == isrCfg.rxTail)
  {
    isrCfg.rxShdw = ST0;
  }

這句代碼的意思是當緩衝區清空的時候,即接收的數據全部被讀出來了之後,重新將rxShdw賦值ST0,以計算下一次輪詢串口需要讀取數據的時間。

接下來看串口寫數據函數

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

  // Accept "all-or-none" on write request.
  if (HAL_UART_ISR_TX_AVAIL() < len)
  {
    return 0;
  }

  for (cnt = 0; cnt < len; cnt++)
  {
    isrCfg.txBuf[isrCfg.txTail] = *buf++;
    isrCfg.txMT = 0;

    if (isrCfg.txTail >= HAL_UART_ISR_TX_MAX-1)
    {
      isrCfg.txTail = 0;
    }
    else
    {
      isrCfg.txTail++;
    }

    // Keep re-enabling ISR as it might be keeping up with this loop due to other ints.
    IEN2 |= UTXxIE; 
  }

  return cnt;
}

 

先檢查發送緩衝區是否有可寫的len長度的位置。然後將buf裏面的數據複製給txBuf,同時將txTail增加,這正好跟read相反,即往緩衝區裏面填充數據是在末尾填充,當發送的時候是在發送緩衝區裏面取數據,即在緩衝區開頭取數據。每次填充一個數據時就開一次發送中斷,這樣時數據及時發送出去。

看下串口發送中斷函數

#if (HAL_UART_ISR == 1)
HAL_ISR_FUNCTION( halUart0TxIsr, UTX0_VECTOR )
#else
HAL_ISR_FUNCTION( halUart1TxIsr, UTX1_VECTOR )
#endif
{
  if (isrCfg.txHead == isrCfg.txTail)
  {
    IEN2 &= ~UTXxIE;
    isrCfg.txMT = 1;
  }
  else
  {
    UTXxIF = 0;
    UxDBUF = isrCfg.txBuf[isrCfg.txHead++];

    if (isrCfg.txHead >= HAL_UART_ISR_TX_MAX)
    {
      isrCfg.txHead = 0;
    }
  }
}

當發送緩衝區中沒有數據要發送的時候,即txHead等於txTail,此時將禁止發送中斷,將txMT標誌位置1,表示發送緩衝區爲空,否則(有數據要發送)將串口發送中斷標誌清零,然後往UxDBUF 寫數據。

最後很重要的一個函數即串口輪詢函數

static void HalUARTPollISR(void)
{
  if (isrCfg.uartCB != NULL)
  {
    uint16 cnt = HAL_UART_ISR_RX_AVAIL();
    uint8 evt = 0;

    if (isrCfg.rxTick)
    {
      // Use the LSB of the sleep timer (ST0 must be read first anyway).
      uint8 decr = ST0 - isrCfg.rxShdw;

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

    if (cnt >= HAL_UART_ISR_RX_MAX-1)
    {
      evt = HAL_UART_RX_FULL;
    }
    else if (cnt >= HAL_UART_ISR_HIGH)
    {
      evt = HAL_UART_RX_ABOUT_FULL;
    }
    else if (cnt && !isrCfg.rxTick)
    {
      evt = HAL_UART_RX_TIMEOUT;
    }

    if (isrCfg.txMT)
    {
      isrCfg.txMT = 0;
      evt |= HAL_UART_TX_EMPTY;
    }

    if (evt)
    {
      isrCfg.uartCB(HAL_UART_ISR-1, evt);
    }
  }
}

這個函數在上面說了是在系統每次循環的時候被調用。如果rxTick不爲0,則

uint8 decr = ST0 - isrCfg.rxShdw;

      if (isrCfg.rxTick > decr)
      {
        isrCfg.rxTick -= decr;
      }
      else
      {
        isrCfg.rxTick = 0;
      }

ST0表示睡眠定時器當前的計數值,而rxShdw記錄的是上次串口接收時候的計數值,這樣decr就表示輪詢了一次之後經過的時間,如果此時間比rxTick大就將rxTick清零,表示時間到了需要接收數據,否則就將rxTick值減去decr,還要繼續等待rxTick-decr這麼長時間。接下來isrCfg.rxShdw = ST0;記錄當前的睡眠定時器的值以便下一次輪詢時候的比較。接下來便是串口事件

if (cnt && !isrCfg.rxTick)
    {
      evt = HAL_UART_RX_TIMEOUT;
    }

看這行代碼,當cnt不爲0且rxTick爲0的時候則標誌串口接收超時事件,

最後

if (evt)
    {
      isrCfg.uartCB(HAL_UART_ISR-1, evt);
    }

如果有串口事件則調用回調函數,對緩衝區中的數據進行處理。

      以上便是Z-STCAK中串口驅動的ISR方式,如果有理解不到位的地方還希望指示。串口在ZigBee協議解決方案的開發過程中有很重要的作用。如果能理清這個串口的工作原理,那麼開發調試起來就會得心應手。

 

 


 

 

 

 

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