如何在Zstack中使用串口

供稿:hz-xin.com     日期:2024-05-01
如何在Zstack中使用串口

网上和论坛里面很多帖子都把精力集中到分析协议栈的串口工作机制上,比如分析DMA
工作原理,中断工作原理,然后分析输入和输出Buffer的处理等内容,学习者跟着协议栈的
串口底层一直到顶层转圈、转圈、再转圈,蒙圈了。
实际上,从应用角度讲,我们根本就没有必要去深入的追究Zstack中串口的工作机制,
也没有必要去搞清楚到底是怎么DMA和Interrupt的,我们只要调用几个简单函数就可以正
常使用串口了。其实协议栈已经把使用串口的条件准备好了,我们何必再纠结硬件底层实现
呢?应用者应该把协议栈看作一个平台,平台之上的应用才是我们的目标。下面我就讲一下
如何利用协议栈现有平台来实现自己的串口应用。这里我所提及的现有平台即是Zstack自带
的MT包,其实Zstack中的这个MT包功能相当强大,通过TI提供的ZTOOL工具可以用串
口的方式同整个协议栈进行交互,在我们编写Zigbee应用程序的过程中,很多不知道该如何
调用的函数都能在MT中找到参考!这个不多说了,有兴趣的同学可以去专门研究一下MT
包。
二、使用方法
在MT包中,已经有了串口初始化即串口数据处理函数可用,关键的几个函数出现在
MT_Uart.c文件中。我们拿出来几个关键函数说明一下(我捡重要语句注释):
第一个函数
void MT_UartInit ()
{ // 这个是MT中的一个串口初始化函数,主要作用是初始化串口工作的一些规矩
halUARTCfg_t uartConfig;

App_TaskID = 0; //处理串口数据的任务ID,可以先不管

uartConfig.configured = TRUE;
uartConfig.baudRate = MT_UART_DEFAULT_BAUDRATE;
//默认38400波特率;可以更改,但是可能有新问题,具体解释内容比较多,我不说;
uartConfig.flowControl = FALSE;//MT_UART_DEFAULT_OVERFLOW;
//禁止硬件流控,如果你的串口只有RXD,TXD和GND三条线,必须这么做;
uartConfig.flowControlThreshold= MT_UART_DEFAULT_THRESHOLD;
uartConfig.rx.maxBufSize = MT_UART_DEFAULT_MAX_RX_BUFF;
uartConfig.tx.maxBufSize = MT_UART_DEFAULT_MAX_TX_BUFF;
uartConfig.idleTimeout = MT_UART_DEFAULT_IDLE_TIMEOUT;
uartConfig.intEnable = TRUE;
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = MT_UartProcessZToolData;
//如果编译的时候选择使用ZTOOL,那么MT_UartProcessZtoolData将会处理串口接到的数
//据串
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
uartConfig.callBackFunc = MT_UartProcessZAppData;
//如果编译的时候没有选择ZTOOL,而是选择使用了ZAPP,则由MT_UartProcessZAppData
//函数来处理串口数据串
(*如果是用CC2530的P0口那两根串口引脚,你就要define ZTOOL_P1,如果是P1口的那
两根串口引脚,你就要define ZTOOL_P2,对于ZAPP_P1和ZAPP_P2也是一个情况*)
#else
uartConfig.callBackFunc = NULL;
//这个地方,如果你有兴趣自己写一个串口处理函数,那么你实现一个My_UartProcessData
//函数,然后填到这里,替换NULL。
#endif

#if defined (MT_UART_DEFAULT_PORT)
HalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig);
//如果定义了默认串口,(0或者1),打开串口,这个HalUartOpen函数会做一大堆工作,具
//体说来就是初始化呗。。。,我没有必要展开。需要注意的是这个函数把前面哪一堆初始化
//的uartConfig做为参数传进去了噢!
#else

(void)uartConfig;
#endif

#if defined (ZAPP_P1) || defined (ZAPP_P2)

MT_UartMaxZAppBufLen = 1;
MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY;
//这两句,如果是不想使用MT_UartProcessZToolData来处理串口数据,就。。。。
//再说就要深入串口机制了,网上讲解文章太多了,自己看吧,我一会儿使用
//MT_UartProcessZToolData。
#endif
}
第二个函数
void MT_UartRegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}

第三个函数
void MT_UartProcessZToolData ( uint8 port, uint8 event )

osal_msg_deallocate ( (uint8 *)pMsg );
}
我们往上看看这个Message是什么?MT_UartProcessZToolData函数开始不远的地方有以下程
序段:
if (pMsg)
{

pMsg->hdr.event = CMD_SERIAL_MSG;
pMsg->msg = (uint8*)(pMsg+1);
pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;
state = CMD_STATE1;
}
从这里看到,这个函数建立了一个消息头,用CMD_SERIAL_MSG做为消息,那么
osal_msg_send给任务的那个消息将会以CMD_SERIAL_MSG出现。。。。。。。
好了,以上三个函数看完,我们试着使用一下:
以GenericApp例子为例:
void GenericApp_Init( byte task_id )

XXXXXXXXXXXXX
XXXXXXXXXXXXX
(这个函数的最后,其实放在这个函数的哪里都行)
MT_UartInit(); //added by kennan
MT_UartRegisterTaskID(GenericApp_TaskID);


再看一下MT_UartRegisterTaskID(GenericApp_TaskID):
void MT_UartRegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}

好了,这样,我们顺利地把串口发来的数据用MT_UartProcessZToolData来处理,并且把处理
后的数据打包发给了任务GenericApp_TaskID。
接下来,我们看一下在GenericApp_TaskID中如何处理吧。
在GenericApp的主处理函数中:
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )

if ( events & SYS_EVENT_MSG )
{
MSGpkt = (afIncomingMSGPacket_t*)osal_msg_receive( GenericApp_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t
*)MSGpkt)->keys );
break;
//增加
Case CMD_SERIAL_MSG:
ProcessUartData((mtOSALSerialData_t *)MSGpkt);
//这个函数你自己实现吧,想做啥呢?想做啥就做啥。如果想把接
//到的数据发回串口,调用HalUARTWrite就行了。

如果你不知道如何提取串口消息并处理,我就好人做到底,帮你实现一个ProcessUartData()
函数吧。这个函数的作用是把接收到的数据从CM0开始一直到payload的最后一个字节发送
给串口回显,不包括校验字节噢。
ProcessUartCommand((mtOSALSerialData_t *)MSGpkt)
{ //为了正确地进行下面工作,用mtOSALSerialData_t类型来指向整个zigbee数据包(不是串
//口数据包)
uint8 *pMsg;
pMsg = MSGpkt->msg;
//定义一个指针,指向真正的串口接收数据存放位置,MSGptk里面还有一些别的Header噢。
switch ( MSGpkt->hdr.event )
{
case CMD_SERIAL_MSG://如果是串口消息。。。。
HalLedSet( HAL_LED_RED, HAL_LED_MODE_FLASH );
//用LED灯指示一下收到数据啦
uint8 *pBuffer;
uint8 datalength;
uint8 i;
//定义几个变量,为从接收到的串口包里面提取数据以及写回串口做准备
datalength = *pMsg++;
//串口包中第一个字节是数据长度噢
pBuffer = osal_mem_alloc(datalength);
//分配一块内存准备把串口消息数据拿出来
if(pBuffer != NULL)
{
for(i = 0;i < datalength; i++)
*pBuffer++ = *pMsg++;
//把消息中的串口数据按照datalength数量挨个捞出来放血(放血啥意思?把池子
//里面那东西捞出来放血,明白不?
HalUARTWrite(0,pBuffer,datalength);
//捞出来放血的串口数据再写回串口,也就是送到串口助手显示
Osal_memfree(pBuffer);
//动态申请的内存记得用完了free一下噢
}
break;
default:
break;
}
}
说明:因为ZTOOL发过来的数据是有格式的,所以如果你用串口助手来测试,那么发的数
据要按照格式来,如果你不想按那个格式,你可以自己去修改MT_UartProcessZToolData里面
的相关程序。这种方法对于想要通过PC来控制zigbee的应用场合非常实用,因为你PC发过
来的一般也会有命令和数据,如果不用MT的格式,你自己也要规范一个格式,既然MT已
经有了,我们就借用就好。
对于MT_UartProcessZAppData这个处理方法,也就是你define了ZAPP_P1或ZAPP_P2
的情况,其机制也是类似的,只不过没有规定格式,你更自由,这里我就不多说了。
再有,如果你真的在测试的时候不知道那么一长串数据的xor 结果是多少,也可以去
MT_UartProcessZToolData函数中,找到:

//if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ +
LEN_Token) == FSC_Token))
{
osal_msg_send( App_TaskID, (byte *)pMsg );
}
else
//{

//osal_msg_deallocate ( (uint8 *)pMsg );
//}
把我标红的几个位置注释掉,就不会校验了,你也不用算xor结果了,不过发数据的时候
这个位置还是要的,你随便填个0好了。

和文件一样,open /dev/ttyS0就是串口1 然后设置波特率,停止位 奇偶校验那些 ,之后read write就可以了 头文件是

网上和论坛里面很多帖子都把精力集中到分析协议栈的串口工作机制上,比如分析DMA
工作原理,中断工作原理,然后分析输入和输出Buffer的处理等内容,学习者跟着协议栈的
串口底层一直到顶层转圈、转圈、再转圈,蒙圈了。
实际上,从应用角度讲,我们根本就没有必要去深入的追究Zstack中串口的工作机制,
也没有必要去搞清楚到底是怎么DMA和Interrupt的,我们只要调用几个简单函数就可以正
常使用串口了。其实协议栈已经把使用串口的条件准备好了,我们何必再纠结硬件底层实现
呢?应用者应该把协议栈看作一个平台,平台之上的应用才是我们的目标。下面我就讲一下
如何利用协议栈现有平台来实现自己的串口应用。这里我所提及的现有平台即是Zstack自带
的MT包,其实Zstack中的这个MT包功能相当强大,通过TI提供的ZTOOL工具可以用串
口的方式同整个协议栈进行交互,在我们编写Zigbee应用程序的过程中,很多不知道该如何
调用的函数都能在MT中找到参考!这个不多说了,有兴趣的同学可以去专门研究一下MT
包。
二、使用方法
在MT包中,已经有了串口初始化即串口数据处理函数可用,关键的几个函数出现在
MT_Uart.c文件中。我们拿出来几个关键函数说明一下(我捡重要语句注释):
第一个函数
void MT_UartInit ()
{ // 这个是MT中的一个串口初始化函数,主要作用是初始化串口工作的一些规矩
halUARTCfg_t uartConfig;

App_TaskID = 0; //处理串口数据的任务ID,可以先不管

uartConfig.configured = TRUE;
uartConfig.baudRate = MT_UART_DEFAULT_BAUDRATE;
//默认38400波特率;可以更改,但是可能有新问题,具体解释内容比较多,我不说;
uartConfig.flowControl = FALSE;//MT_UART_DEFAULT_OVERFLOW;
//禁止硬件流控,如果你的串口只有RXD,TXD和GND三条线,必须这么做;
uartConfig.flowControlThreshold= MT_UART_DEFAULT_THRESHOLD;
uartConfig.rx.maxBufSize = MT_UART_DEFAULT_MAX_RX_BUFF;
uartConfig.tx.maxBufSize = MT_UART_DEFAULT_MAX_TX_BUFF;
uartConfig.idleTimeout = MT_UART_DEFAULT_IDLE_TIMEOUT;
uartConfig.intEnable = TRUE;
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = MT_UartProcessZToolData;
//如果编译的时候选择使用ZTOOL,那么MT_UartProcessZtoolData将会处理串口接到的数
//据串
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
uartConfig.callBackFunc = MT_UartProcessZAppData;
//如果编译的时候没有选择ZTOOL,而是选择使用了ZAPP,则由MT_UartProcessZAppData
//函数来处理串口数据串
(*如果是用CC2530的P0口那两根串口引脚,你就要define ZTOOL_P1,如果是P1口的那
两根串口引脚,你就要define ZTOOL_P2,对于ZAPP_P1和ZAPP_P2也是一个情况*)
#else
uartConfig.callBackFunc = NULL;
//这个地方,如果你有兴趣自己写一个串口处理函数,那么你实现一个My_UartProcessData
//函数,然后填到这里,替换NULL。
#endif

#if defined (MT_UART_DEFAULT_PORT)
HalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig);
//如果定义了默认串口,(0或者1),打开串口,这个HalUartOpen函数会做一大堆工作,具
//体说来就是初始化呗。。。,我没有必要展开。需要注意的是这个函数把前面哪一堆初始化
//的uartConfig做为参数传进去了噢!
#else

(void)uartConfig;
#endif

#if defined (ZAPP_P1) || defined (ZAPP_P2)

MT_UartMaxZAppBufLen = 1;
MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY;
//这两句,如果是不想使用MT_UartProcessZToolData来处理串口数据,就。。。。
//再说就要深入串口机制了,网上讲解文章太多了,自己看吧,我一会儿使用
//MT_UartProcessZToolData。
#endif
}
第二个函数
void MT_UartRegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}

第三个函数
void MT_UartProcessZToolData ( uint8 port, uint8 event )

osal_msg_deallocate ( (uint8 *)pMsg );
}
我们往上看看这个Message是什么?MT_UartProcessZToolData函数开始不远的地方有以下程
序段:
if (pMsg)
{

pMsg->hdr.event = CMD_SERIAL_MSG;
pMsg->msg = (uint8*)(pMsg+1);
pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;
state = CMD_STATE1;
}
从这里看到,这个函数建立了一个消息头,用CMD_SERIAL_MSG做为消息,那么
osal_msg_send给任务的那个消息将会以CMD_SERIAL_MSG出现。。。。。。。
好了,以上三个函数看完,我们试着使用一下:
以GenericApp例子为例:
void GenericApp_Init( byte task_id )

XXXXXXXXXXXXX
XXXXXXXXXXXXX
(这个函数的最后,其实放在这个函数的哪里都行)
MT_UartInit(); //added by kennan
MT_UartRegisterTaskID(GenericApp_TaskID);


再看一下MT_UartRegisterTaskID(GenericApp_TaskID):
void MT_UartRegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}

好了,这样,我们顺利地把串口发来的数据用MT_UartProcessZToolData来处理,并且把处理
后的数据打包发给了任务GenericApp_TaskID。
接下来,我们看一下在GenericApp_TaskID中如何处理吧。
在GenericApp的主处理函数中:
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )

if ( events & SYS_EVENT_MSG )
{
MSGpkt = (afIncomingMSGPacket_t*)osal_msg_receive( GenericApp_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t
*)MSGpkt)->keys );
break;
//增加
Case CMD_SERIAL_MSG:
ProcessUartData((mtOSALSerialData_t *)MSGpkt);
//这个函数你自己实现吧,想做啥呢?想做啥就做啥。如果想把接
//到的数据发回串口,调用HalUARTWrite就行了。

如果你不知道如何提取串口消息并处理,我就好人做到底,帮你实现一个ProcessUartData()
函数吧。这个函数的作用是把接收到的数据从CM0开始一直到payload的最后一个字节发送
给串口回显,不包括校验字节噢。
ProcessUartCommand((mtOSALSerialData_t *)MSGpkt)
{ //为了正确地进行下面工作,用mtOSALSerialData_t类型来指向整个zigbee数据包(不是串
//口数据包)
uint8 *pMsg;
pMsg = MSGpkt->msg;
//定义一个指针,指向真正的串口接收数据存放位置,MSGptk里面还有一些别的Header噢。
switch ( MSGpkt->hdr.event )
{
case CMD_SERIAL_MSG://如果是串口消息。。。。
HalLedSet( HAL_LED_RED, HAL_LED_MODE_FLASH );
//用LED灯指示一下收到数据啦
uint8 *pBuffer;
uint8 datalength;
uint8 i;
//定义几个变量,为从接收到的串口包里面提取数据以及写回串口做准备
datalength = *pMsg++;
//串口包中第一个字节是数据长度噢
pBuffer = osal_mem_alloc(datalength);
//分配一块内存准备把串口消息数据拿出来
if(pBuffer != NULL)
{
for(i = 0;i < datalength; i++)
*pBuffer++ = *pMsg++;
//把消息中的串口数据按照datalength数量挨个捞出来放血(放血啥意思?把池子
//里面那东西捞出来放血,明白不?
HalUARTWrite(0,pBuffer,datalength);
//捞出来放血的串口数据再写回串口,也就是送到串口助手显示
Osal_memfree(pBuffer);
//动态申请的内存记得用完了free一下噢
}
break;
default:
break;
}
}
说明:因为ZTOOL发过来的数据是有格式的,所以如果你用串口助手来测试,那么发的数
据要按照格式来,如果你不想按那个格式,你可以自己去修改MT_UartProcessZToolData里面
的相关程序。这种方法对于想要通过PC来控制zigbee的应用场合非常实用,因为你PC发过
来的一般也会有命令和数据,如果不用MT的格式,你自己也要规范一个格式,既然MT已
经有了,我们就借用就好。
对于MT_UartProcessZAppData这个处理方法,也就是你define了ZAPP_P1或ZAPP_P2
的情况,其机制也是类似的,只不过没有规定格式,你更自由,这里我就不多说了。
再有,如果你真的在测试的时候不知道那么一长串数据的xor 结果是多少,也可以去
MT_UartProcessZToolData函数中,找到:

//if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ +
LEN_Token) == FSC_Token))
{
osal_msg_send( App_TaskID, (byte *)pMsg );
}
else
//{

//osal_msg_deallocate ( (uint8 *)pMsg );
//}
把我标红的几个位置注释掉,就不会校验了,你也不用算xor结果了,不过发数据的时候
这个位置还是要的,你随便填个0好了。

关注这个问题

有人知道ZStack的存储是怎么计算的么?本人对这个比较感兴趣
答:ZStack主存储URL指定到某个分区的目录上,主存储的总容量为目录所属分区的总容量。ZStack只关心自己使用的容量(镜像、云盘、快照),其他用户在这个分区使用的空间不计入已使用的容量中。因此,可用容量=所属分区的总容量-...

使用ZStack的话,云盘可以建立在共享存储上,且可以进行任意挂载吗...
答:我一直都在用ZStack,之前也在他们社区问过类似的问题,关于这个问题可以说非常有话语权了。ZStack是可以给一个区域内不同集群添加不同主存储的,主存储可以是本地存储也可以是共享存储,全看你选择哪个。? 如果主存储为...

有用ZStack云平台创建云主机失败的小伙伴么,想了解一下原因。
答:以正常提供虚拟化服务? 如果云主机一直处于启动中,需要检查使用此云主机的镜像是否巨大,镜像缓存的下载是否需要耗时很久。如果镜像缓存存在,需继续检查物理机的/var/log/zstack/zstack-kvmagent.log ...

使用ZStack企业版时,镜像服务器添加不上怎么办?
答:镜像服务器添加不上请检查以下因素:Sftp镜像服务器和镜像仓库需检查SSH端口号、用户名、密码、用户名是否拥有sudo权限。Ceph镜像服务器需检查分布式块存储是否正常。需检查CephIP地址、CephSSH端口、用户名、密码、用户名是否有...

ZStack计算节点使用Ceph存储时,在创建、启动、迁移云主机时,出现以下错 ...
答:c.执行/etc/init.d/zstack-kvmagentstop停止管理节点agent。[root@ceph-host~]#/etc/init.d/zstack-kvmagentstop2017-05-0810:07:57,590DEBUG[zstacklib.utils.shell]/sbin/iptables-save2017-05-0810:07:57,596...

stm32w的zigbee能用Zstack协议栈么
答:用不了 、z-stack协议栈是半开源协议栈,有一部分是以库的形式提供,stm32和TI的cc系列的硬件又不一样,没法改,所以不能使用z-stack协议栈。

请问使用ZStack部署SharedMountPoint主存储对文件系统有什么要求么...
答:部署这个的话好像你的计算节点需要挂载同一个NFS服务,并且还要挂载到本地计算节点相同的目录。这样操作应该就可以了,没有其他要求了。

为什么ZStack不使用快照做备份,而使用灾备做备份?
答:快照数据依赖原始卷,数据安全性低,快照较多时,会严重影响云主机性能。一般用于特定操作的临时回滚。灾备数据相对独立,安全性更高,灾备数据不占用主存储空间,数据量大小不影响云主机的性能。针对重要数据的长期备份,建议采用...

SwiftUI中Preferences的使用
答:这样 HStack1 就确定了自己的尺寸。5. HStack1 确定了自己的尺寸后, Text 通过 GeometryReader 拿到了 HStack1 确定好的尺寸,并通过 SizeKey 告诉期上面的给父 View 。6.由于 ZStack 调用了...

如何在Zstack中使用串口?
答:#else(void)uartConfig;#endif#if defined (ZAPP_P1) || defined (ZAPP_P2)MT_UartMaxZAppBufLen = 1;MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY;//这两句,如果是不想使用MT_UartProcessZToolData来处理串口...