最近在做一个智能家居的项目,用到了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为例介绍:

typedefstruct{uint16rxBuf[HAL_UART_DMA_RX_MAX];#ifHAL_UART_DMA_RX_MAX<256uint8rxHead;uint8rxTail;#elseuint16rxHead;uint16rxTail;#endifuint8rxTick;uint8rxShdw;uint8txBuf[2][HAL_UART_DMA_TX_MAX];#ifHAL_UART_DMA_TX_MAX<256uint8txIdx[2];#elseuint16txIdx[2];#endifvolatileuint8txSel;uint8txMT;uint8txTick;//1-charactertimein32kHzticksaccordingtobaudrate,//tobeusedincalculatingtimelapsesinceDMAISR//toallowdelaymarginbeforestartfiringDMA,sothat//DMAdoesnotoverwriteUARTDBUFofpreviouspacketvolatileuint8txShdw;//SleepTimerLSBshadow.volatileuint8txShdwValid;//TXshadowvalueisvaliduint8txDMAPending;//UARTTXDMAispendinghalUARTCBack_tuartCB;}uartDMACfg_t;

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


1、写操作

staticuint16HalUARTWriteDMA(uint8*buf,uint16len){uint16cnt;halIntState_this;uint8txIdx,txSel;//Enforceallornone.if((len+dmaCfg.txIdx[dmaCfg.txSel])>HAL_UART_DMA_TX_MAX){return0;}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){//TXDMAisexpectedtobefireddmaCfg.txDMAPending=TRUE;}HAL_EXIT_CRITICAL_SECTION(his);returncnt;}

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

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


2、读操作

staticuint16HalUARTReadDMA(uint8*buf,uint16len){uint16cnt;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-enabletheflowonanyread.returncnt;}

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


3、poll操作

staticvoidHalUARTPollDMA(void){uint16cnt=0;uint8evt=0;if(HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead)){uint16tail=findTail();//IftheDMAhastransferredinmoreRxbytes,resettheRxidletimer.if(dmaCfg.rxTail!=tail){dmaCfg.rxTail=tail;//Re-synctheshadowonany1stbyte(s)received.if(dmaCfg.rxTick==0){dmaCfg.rxShdw=ST0;}dmaCfg.rxTick=HAL_UART_DMA_IDLE;}elseif(dmaCfg.rxTick){//UsetheLSBofthesleeptimer(ST0mustbereadfirstanyway).uint8decr=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;}elseif(cnt>=HAL_UART_DMA_HIGH){evt=HAL_UART_RX_ABOUT_FULL;PxOUT|=HAL_UART_Px_RTS;}elseif(cnt&&!dmaCfg.rxTick){evt=HAL_UART_RX_TIMEOUT;}if(dmaCfg.txMT){dmaCfg.txMT=FALSE;evt|=HAL_UART_TX_EMPTY;}if(dmaCfg.txShdwValid){uint8decr=ST0;decr-=dmaCfg.txShdw;if(decr>dmaCfg.txTick){//NoprotectionfortxShdwValidisrequired//becausewhiletheshadowwasvalid,DMAISRcannotbetriggered//tocauseconcurrentaccesstothisvariable.dmaCfg.txShdwValid=FALSE;}}if(dmaCfg.txDMAPending&&!dmaCfg.txShdwValid){//UARTTXDMAisexpectedtobefiredandenoughtimehaslapsedsincelastDMAISR//toknowthatDBUFcanbeoverwrittenhalDMADesc_t*ch=HAL_DMA_GET_DESC1234(HAL_DMA_CH_TX);halIntState_tintState;//CleartheDMApendingflagdmaCfg.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个值:

#defineHAL_UART_DMA_FULL(HAL_UART_DMA_RX_MAX-16)#defineHAL_UART_DMA_HIGH(HAL_UART_DMA_RX_MAX/2-16)#defineHAL_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