zstack串口详解.docx
- 文档编号:4865825
- 上传时间:2022-12-11
- 格式:DOCX
- 页数:28
- 大小:38.62KB
zstack串口详解.docx
《zstack串口详解.docx》由会员分享,可在线阅读,更多相关《zstack串口详解.docx(28页珍藏版)》请在冰豆网上搜索。
zstack串口详解
好久没写了,今天正式回归,这次要用到串口了,一到用的时候我就发现自己傻逼了,很多东西在脑海里乱七八糟的,有很多简单的问题还没搞清楚,管它呢,先把这些幼稚的问题搞清楚再说。
(1)在z-stack中应用串口有几种方式?
哪位大侠有答案。
(2)关于中断和DMA方式有何区别?
(3)我想用中断方式,该怎么用?
?
这些问题很幼稚,但我还不懂,杯具了。
看了网上的资料,转载如下(抄袭的滋味真不好受啊):
串口接收发送数据有两种方式,一种是中断的模式,另一种是DMA方式,这里主要以中断的方式,来看一下使用串口来发送,接收数据的整个流程。
这里以SerialApp例程为例子。
在mian函数中的调用HalDriverInit();函数,在函数中初始化串口,主要是配置管脚和DMA通道
voidHalDriverInit(void)
{
...................................
#if(definedHAL_UART)&&(HAL_UART==TRUE)
HalUARTInit();
#endif
....................................
}
从程序中可以看出要想使用协议栈中串口,初始化串口必须定义HAL_UART和HAL_UART TRUE在hal_board_cfg.h文件中。
#ifndefHAL_UART
#if(definedZAPP_P1)||(definedZAPP_P2)||(definedZTOOL_P1)||(definedZTOOL_P2)
#defineHAL_UARTTRUE
#else
#defineHAL_UARTFALSE
#endif
#endif
然后在osal_start_system()开始系统后,会调用Hal_ProcessPoll()来读取时间和串口。
在CC2430的数据手册中有这样一段话。
DatareceptionontheUARTisinitiatedwhena1iswrittentothe UxCSR.REbit
TheUARTwillthensearchforavalidstartbitontheRXDxinputpinandsetthe
UxCSR.ACTIVEbithigh.Whenavalidstartbithasbeendetectedthereceived byteisshiftedintothereceiveregister.The UxCSR.RX_BYTEbitissetandareceiveinterruptisgeneratedwhentheoperationhascompleted.ThereceiveddatabyteisavailablethroughtheUxBUFregister.WhenUxBUFisread, UxCSR.RX_BYTEisclearedbyhardware.
当有数据接收时,UxCSR.RE位将被置1,然后,UART将在RXDx的输入引脚上查找一个有效的开始位,当找到这个开始位时,将设置UxCSR.ACTIVE位为高电平。
当一个有效的开始位被查找到,收到的字节将被移动到接收寄存器中。
然后,UxCSR.RX_BYTE位设为1.并且,当这个接收操作完成后接收中断会被产生。
接收到的数据可以通过操作UxBUF寄存器,当UxBUF寄存器的数据被读出后,UxCSR.RX_BYTE位被硬件清除。
串口发生中断首先调用中断的处理函数,这个是接收的中断函数。
#ifHAL_UART_0_ENABLE
HAL_ISR_FUNCTION(halUart0RxIsr,URX0_VECTOR)
{
cfg0->rxBuf[cfg0->rxHead]=U0DBUF;
if(cfg0->rxHead==cfg0->rxMax)
{
cfg0->rxHead=0;
}
else
{
cfg0->rxHead++;
}
}
#endif
该中断函数主要是把U0DBUF寄存器,也就是接收到数据的寄存器,把数据读取来放到UART的结构体中的,cfg0->rxBuf[],中,这个数组的内存分配是在HalUARTOpen()函数中。
SerialApp.c中有下面的定义
#if!
defined(SERIAL_APP_RX_MAX)
#if(defined(HAL_UART_DMA))&&HAL_UART_DMA
#defineSERIAL_APP_RX_MAX 128
#else
#defineSERIAL_APP_RX_MAX 64
#endif
#endif
SerialApp_Init()函数中有下面的赋值,
uartConfig.rx.maxBufSize =SERIAL_APP_RX_MAX;
HalUARTOpen()函数中有下面的赋值:
所以其cfg->rxMax=128,
cfg->rxMax=config->rx.maxBufSize;
其中rxHead这个参数始终指向像一个参数被存放到rxBuf的位置。
因为硬件串口缓存器U0DBUF只能存放一个字节,如果不及时把这个接收到的转移出去,那么就会被下一个到来的字节覆盖掉,所以rxHead变量就指向了这个存放的地址,当然是基于定义的rxBuf存储空间。
而if(cfg0->rxHead==cfg0->rxMax)这一句判断也说明的很清楚,一旦这个计数达到了定义的最大接收数量,也就是说已经把rxBuf存储空间占满了,那么就不能在继续存放了。
中断函数执行完后,就应该跳到发生中断时执行的地方,这时程序继续执行,然后在osal_start_system()开始系统后,会循环调用Hal_ProcessPoll()来读取时间和串口,
voidHal_ProcessPoll()
{
HalTimerTick();
#if(definedHAL_UART)&&(HAL_UART==TRUE)
HalUARTPoll();
#endif
}
下面是HalUARTPoll();函数的源代码,在这里有对接收到的数据进行处理的程序。
voidHalUARTPoll(void)
{
#if(HAL_UART_0_ENABLE|HAL_UART_1_ENABLE)
staticuint8tickShdw;
uartCfg_t*cfg;
uint8tick;
#ifHAL_UART_0_ENABLE
//当发生串口接收中断时cfg0就会改变,如果串口没有数据输入cfg0为空,当接收到数据时cfg0将在串口中断服务程序中被改变
if(cfg0)
{
cfg=cfg0;
}
#endif
#ifHAL_UART_1_ENABLE
if(cfg1)
{
cfg=cfg1;
}
#endif
//UsetheLSBofthesleeptimer(ST0mustbereadfirstanyway).
//系统上电后,睡眠定时器就会自动启动做自增计数ST0即睡眠定时器启动到现在计算值的最低8位
tick=ST0-tickShdw;
tickShdw=ST0;
//下面是一个无限循环
do
{
//------------发送超时时间
if(cfg->txTick>tick)
{
cfg->txTick-=tick;
}
else
{
cfg->txTick=0;
}
//---------------------接收超时时间
if(cfg->rxTick>tick)
{
cfg->rxTick-=tick;
}
else
{
cfg->rxTick=0;
}
//是使用DMA方式还是使用中断方式
#ifHAL_UART_ISR
#ifHAL_UART_DMA
if(cfg->flag&UART_CFG_DMA)
{
pollDMA(cfg);
}
else//中断方式
#endif
{
pollISR(cfg);
}
#elifHAL_UART_DMA
pollDMA(cfg);
#endif
if(cfg->rxHead!
=cfg->rxTail)//不相等表示有数据
{
uint8evt;
if(cfg->rxHead>=(cfg->rxMax-SAFE_RX_MIN))
{
//已保存的数据已经超过了安全界限,发送接收满事件
evt=HAL_UART_RX_FULL;
}
elseif(cfg->rxHigh&&(cfg->rxHead>=cfg->rxHigh))
{
//rxBuf[]接收到预设值(默认80字节),则触发事件,为什么是80,在上一篇转载的文章中有介绍,这里重点关注执行的流程。
evt=HAL_UART_RX_ABOUT_FULL;
}
elseif(cfg->rxTick==0)
{
//超时事件
evt=HAL_UART_RX_TIMEOUT;
}
else
{
evt=0;
}
//如果发生事件,并且配置了回调函数则调用回调函数
if(evt&&cfg->rxCB)
{
//(cfg->flag&UART_CFG_U1F)!
=0)判读是那个串口,如果是串口1则为1,否则为0
cfg->rxCB(((cfg->flag&UART_CFG_U1F)!
=0),evt);
}
}
#ifHAL_UART_0_ENABLE
if(cfg==cfg0)
{
#ifHAL_UART_1_ENABLE
if(cfg1)
{
cfg=cfg1;
}
else
#endif
break;
}
else
#endif
break;
}while(TRUE);
#else
return;
#endif
}
说明:
(1)下面我们看一下pollISR()函数
staticvoidpollISR(uartCfg_t*cfg)
{
//计算rxBuf[]中还有多少数据没有读出(以字节为单位)
uint8cnt=UART_RX_AVAIL(cfg);
//如果串口没有接收到数据,也就是说没有发生过串口接收中断,那么cfg应为是为空的,则cnt=0如果发生了串口中断,则cnt计算出串口缓存中还有多少数据没有读出,这个缓存并不是硬件寄存器的缓存,而是程序中开辟一段空间
if(!
(cfg->flag&UART_CFG_RXF))
{
//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡眠定时器来控制),而且需要把已经读出的数据数目减去!
//Ifanythingreceived,resettheRxidletimer.
if(cfg->rxCnt!
=cnt)
{
cfg->rxTick=HAL_UART_RX_IDLE;
cfg->rxCnt=cnt;
}
if(cfg->rxCnt>=(cfg->rxMax-SAFE_RX_MIN))
{
RX_STOP_FLOW(cfg);
}
}
}
#endif
pollISR()函数主要作用就是设置rxTick和rxCn,
//关于安全界限,在程序中有下面一段:
//如果声明了流控制,为保证数据的正确接收需要在RX缓存区中预留出足够的空间。
CC2430可以使用的最大串口波特率为115.2k。
这个安全界限的数字跟使用的波特率还有串口tick有关。
具体参考《Z-STACK问题之串口结构uartCfg_t乱说》文章。
可以看到,在初始化时rxHead=rxTail=0,如果发生接收中断,在中断服务函数中把U0DBUF寄存器中的数据传送到rxbuf[]中,这时rxHead和rxTail的值不在相等,其中,rxHead是rxBuf[]接收到数据的个数,rxTail是rxBuf[]移出的数据个数,再根据两者的差值,判断具体的事件evt。
然后,根据evt和设置的回调函数,通过cfg->rxCB调用相应的回调函数。
代码也是体显在下面一句。
cfg->rxCB(((cfg->flag&UART_CFG_U1F)!
=0),evt);
第一个参数主要是判断,是UART1还是UART0.第二个参数是触发的事件类型,那个个回调函数,具体是指向函数呢?
首先,我们在voidSerialApp_Init(uint8task_id)初始化函数中,对串口进行了配置,其中下面两句中有关于回调函数的。
#else
uartConfig.callBackFunc =rxCB;
#endif
HalUARTOpen(SERIAL_APP_PORT,&uartConfig);
其中,在HalUARTOpen()函数中,有下面的一条语句,
uint8HalUARTOpen(uint8port,halUARTCfg_t*config)
{
...................
cfg->rxCB=config->callBackFunc;
...................
}
也就是调用下面的rxCB函数。
程序中定义了两个串口接收缓冲区:
otaBuf上otaBuf2.当otaBuf中无数据时,处于空闲状态时,由otaBuf接收串口数据;当otaBuf中保留有数据时,下等待接收节点发送接收数据响应或由于某些正在重新给接收节点发送数据时,可通过otaBuf2接收数据,当otaBuf和otaBuf2都没有处于空闲状态时,说明数据没有及时发送给接收节点,发生了数据累积,缓冲区被占用,需要进行流量控制,所以直接退出接收回调函数,暂不接收数据。
staticvoidrxCB(uint8port,uint8event)
{
uint8*buf,len;
if(otaBuf2)//缓冲区被占用
{
return;
}
if(!
(buf=osal_mem_alloc(SERIAL_APP_RX_CNT)))
{
return;
}
len=HalUARTRead(port,buf+1,SERIAL_APP_RX_CNT-1);
if(!
len) //Lengthisnotexpectedtoeverbezero.
{
osal_mem_free(buf);
return;
}
if(otaBuf)//otaBuf正在被占用
{
otaBuf2=buf;//otaBuf2接收数据
otaLen2=len;
}
else
{
otaBuf=buf;//otaBuf接收数据
otaLen=len;
osal_set_event(SerialApp_TaskID,SERIALAPP_MSG_SEND_EVT);
}
}
#endif
在事件处理函数中,有下面的判断。
UINT16SerialApp_ProcessEvent(uint8task_id,UINT16events)
{
........................
if(events&SERIALAPP_MSG_SEND_EVT)
{
SerialApp_SendData(otaBuf,otaLen);//
return(events^SERIALAPP_MSG_SEND_EVT);
}
.........................
}
下面是SerialApp_SendData()函数的源代码,调用AF_DataRequest(),通过OTA发送数据。
由于在数据包之前增加了序列号SerialApp_SeqTx,多次重发的数据不会被接收节点重复发送到串口。
staticvoidSerialApp_SendData(uint8*buf,uint8len)
{
afStatus_tstat;
//Pre-pendsequencenumbertothestartoftheRxbuffer.
*buf=++SerialApp_SeqTx;
otaBuf=buf;
otaLen=len+1;
stat=AF_DataRequest(&SerialApp_DstAddr,
(endPointDesc_t*)&SerialApp_epDesc,
SERIALAPP_CLUSTERID1,
otaLen,otaBuf,
&SerialApp_MsgID,0,AF_DEFAULT_RADIUS);
if((stat==afStatus_SUCCESS)||(stat==afStatus_MEM_FAIL))
{
//在设定的时间内没有发送成功,则重新发送。
osal_start_timerEx(SerialApp_TaskID,SERIALAPP_MSG_RTRY_EVT,
SERIALAPP_MSG_RTRY_TIMEOUT);
rtryCnt=SERIALAPP_MAX_RETRIES;
}
else
{
FREE_OTABUF();//重发的次数
}
}
voidSerialApp_ProcessMSGCmd(afIncomingMSGPacket_t*pkt)
{
uint8stat;
uint8seqnb;
uint8delay;
switch(pkt->clusterId)
{
//Amessagewithaserialdatablocktobetransmittedontheserialport.
//接收节点收到的接收数据命令,
caseSERIALAPP_CLUSTERID1:
seqnb=pkt->cmd.Data[0];
//Keepmessageifnotarepeatpacket
if((seqnb>SerialApp_SeqRx)|| //Normal
((seqnb<0x80)&&(SerialApp_SeqRx>0x80)))//Wrap-around
{
//Transmitthedataontheserialport.接收到的发送到串口
if(HalUARTWrite(SERIAL_APP_PORT,pkt->cmd.Data+1,
(pkt->cmd.DataLength-1)))
{
//Savefornextincomingmessage
SerialApp_SeqRx=seqnb;
stat=OTA_SUCCESS;
}
else
{
stat=OTA_SER_BUSY;
}
}
else
{
stat=OTA_DUP_MSG;
}
//SelectapproproiateOTAflow-controldelay.
delay=(stat==OTA_SER_BUSY)?
SERIALAPP_NAK_DELAY:
SERIALAPP_ACK_DELAY;
//Build&sendOTAresponsemessage.发送响应消息
rspBuf[0]=stat;
rspBuf[1]=seqnb;
rspBuf[2]=LO_UINT16(delay);
rspBuf[3]=HI_UINT16(delay);
//发送接收数据响应命令
stat=AF_DataRequest(&(pkt->srcAddr),
(endPointDesc_t*)&SerialApp_epDesc,
SERIALAPP_CLUSTERID2,
SERIAL_APP_RSP_CNT,
rspBuf,&SerialApp_MsgID,0,
AF_DEFAULT_RADIUS);
if(stat!
=afStatus_SUCCESS)
{
osal_start_
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- zstack 串口 详解
![提示](https://static.bdocx.com/images/bang_tan.gif)