Z-STACK之cc2530串口驱动详解上

来源:互联网 发布:莞城金域名苑 编辑:程序博客网 时间:2024/05/30 20:07

        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协议解决方案的开发过程中有很重要的作用。如果能理清这个串口的工作原理,那么开发调试起来就会得心应手。