Z-Stack串口通信使用心得-创新互联

最近在做一个智能家居的项目,用到了TI的CC2530芯片以及对应的zstack协议栈,其中串口通信部分使用的最多,下面就分享一下Z-Stack对串口封装的使用心得。

成都创新互联公司的客户来自各行各业,为了共同目标,我们在工作上密切配合,从创业型小企业到企事业单位,感谢他们对我们的要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。专业领域包括网站设计、成都做网站、电商网站开发、微信营销、系统平台开发。

Z-Stack中对串口操作的封装主要在hal_uart.h,hal_uart.c中, 支持DMA和ISR两种处理方式, 真正的实现则都封装在_hal_uart_dma.c 和_hal_uart_isr.c中,  但系统只推荐使用DMA方式, 可以通过修改宏定义来改为ISR的方式,宏定义在hal_board_cfg.h中。

Z-Stack对串口操作的封装使用了缓冲区的方式, 读写都是直接操作缓冲区, 不管是DMA方式还是ISR方式都是如此,下面以DMA为例介绍:

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;

uartDMACfg_t结构体定力了相关的数据结构, 其中rxBuf和txBuf分别对应读写缓冲区

1、写操作

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;
}

从这段代码可以明显看出, Z-Stack对串口的写如果缓冲区剩余空间少于用户写入长度, 会直接返回0

也就是注释里的enforce all or none,  由于DMA方式使用的是双缓冲区,这个函数里也对缓冲区切换的情况做了保护。

2、读操作

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;
}

这个函数很简单,就是直接从rxBuf里读取数据到用户缓冲区中, 需要注意的是,如果读到缓冲区的末尾,会自动调整游标到缓冲区头, 可能造成读到的数据并非真实接受到的数据, 所以在调用这个函数的时候,最好读取数据不要超过HAL_UART_DMA_RX_MAX

3、poll操作

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);
  }
}

HalUARTPollDMA函数是整个串口操作的核心, 该函数会被系统大循环定时的调用,在这个函数里

会判断读写缓冲区的状态, 进而触发回调函数halUARTCfg_t.callBackFunc。在触发会调函数的时候,会传给回调函数几个事件,而这些事件涉及到以下4个值:

#define HAL_UART_DMA_FULL         (HAL_UART_DMA_RX_MAX - 16)
#define HAL_UART_DMA_HIGH         (HAL_UART_DMA_RX_MAX / 2 - 16)
#define HAL_UART_DMA_IDLE         (6 * HAL_UART_MSECS_TO_TICKS)
dmaCfg.txMT

当缓冲区数据长度大于等于HAL_UART_DMA_FULL 时, 触发HAL_UART_RX_FULL事件

当缓冲区数据长度大于等于HAL_UART_DMA_HIGH 时, 触发HAL_UART_RX_ABOUT_FULL事件

当缓冲区数据长度小于HAL_UART_DMA_FULL且等待时间达到HAL_UART_DMA_IDLE 时, 触发HAL_UART_TIMEOUT事件

当dmaCfg.txMT为真时,表明写缓冲区数据已经全部写入串口,触发HAL_UART_TX_EMPTY事件

所以用Z-Stack的hal_uart库对串口进行操作时, 推荐的做法是在回调函数里根据事件来判断是否需要读取数据,而写操作可以放到程序的任何位置,包括回调函数里, 写入数据的时候要判断一下返回值, 看数据是否真正写入到缓冲区中。

HalUARTPollDMA的调用频率大概是间隔200ms, 参考

http://www.360doc.com/content/11/1022/09/7906690_158136472.shtml

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


本文标题:Z-Stack串口通信使用心得-创新互联
文章地址:http://hbruida.cn/article/dpohpc.html