最近在做一个智能家居的项目,用到了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
本文出自 “11383655” 博客,请务必保留此出处http://11393655.blog.51cto.com/11383655/1758524
原文地址:http://11393655.blog.51cto.com/11383655/1758524