• 「ZigBee模块」协议栈-串口透传,打造无线串口模块


    前面写比较仔细,后面一个么因为和前面重复了,不多说了,还有个原因...我懒...O(∩_∩)O哈哈~

    串口透传,打造无线串口模块

    一、实验目的

        两台PC机各使用串口连接一个zigbee模块,连接正确后打开串口调试助手发送信息。利用zigbee将从串口接收到的数据无线传送给另一个zigbee模块,另一个zigbee模块通过串口将数据传给PC端并在屏幕上显示。

     

    二、实验平台

      硬件:两个zigbee模块,两台PC机(其实一台也许,连接不同串口即可),编译器,方口转USB数据线两根

      软件:基于Z-stack协议栈的SampleApp工程文件

     

    三、实验过程分析

      打开工程文件,打开MT_UART.c文件,找到函数初始化函数MT_UartInit ()。注意其中部分代码

     1 #if defined (ZTOOL_P1) || defined (ZTOOL_P2)                     //预编译
     2 
     3   uartConfig.callBackFunc         = MT_UartProcessZToolData;     //|选择ZTOOL或者ZAPP
     4 
     5 #elif defined (ZAPP_P1) || defined (ZAPP_P2)                     //|P1-串口0 或 P2-串口1
     6 
     7   uartConfig.callBackFunc         = MT_UartProcessZAppData;      //|在option->c/c++->preprocessor中选择
     8 
     9 #else                                                            //|
    10 
    11   uartConfig.callBackFunc         = NULL;                        //|
    12 
    13 #endif                                                           //|
    MT_UART.c

      这部分是对串口进行预编译,我们定义的是ZTOOL_P1,故协议栈处理的函数是MT_UartProcessZToolData。查看其定义。

      正式看它的定义之前我们先来了解一下协议栈中发送数据的格式。函数定义的上面有一段注释部分,对串口传送数据的格式进行了说明(见图1)。

    图1

      SOP0xFE 数据帧头

      Data LengthData的数据长度,以字节计

      CMD0:命令低字节

      CMD1:命令高字节

      Data:数据帧具体的数据,长度可变,但必须和Data Length相等。

      FCS:校验和

     

      看了这个数据格式我们就会发现一个问题,这个数据格式非常适合硬件之间的通信,因为它包括了具体数据以外的很多数据信息,但是却不适合我们手动发送数据。也就是说如果我们使用串口助手直接发送数据,我们需要在数据前面加上FE、数据长度、命令字,然后数据末尾再计算校验和。这对于我们来说实在太麻烦了,所以我们必须对这个函数作出一些修改。在修改函数之前我们还是要先来了解一下它原本的代码。

      顺便再提一个东西,串口数据包(我是这样叫它的,它的英文名是mtOSALSerialData_t)。串口数据包是一个结构体,成员变量是一个事件包(也是我自己叫的,英文名叫osal_event_hdr_t)和一个指针。时间包也是一个结构体,成员变量是事件(事件号)和状态。也就是说一个串口数据包里面有一个事件号,一个事件状态,一个指针。很明显,这个指针等一下一定会指向一个动态数组,然后依次往数值里面放数据嘛~

      好啦,大家久等啦,来看一下MT_UartProcessZToolData()这个函数吧~

      1 //port是串口号,event是事件
      2 
      3 void MT_UartProcessZToolData ( uint8 port, uint8 event )  
      4 
      5 {
      6 
      7   uint8  ch;
      8 
      9   uint8  bytesInRxBuffer;
     10 
     11   
     12 
     13   (void)event;  // Intentionally unreferenced parameter
     14 
     15  
     16 
     17   while (Hal_UART_RxBufLen(port)) //只要缓冲区有数据
     18 
     19   {
     20 
     21     HalUARTRead (port, &ch, 1);   //传入串口号,读取1个字符到ch
     22 
     23  
     24 
     25     switch (state)                //state一开始默认0x00
     26 
     27     {
     28 
     29       case SOP_STATE:             //SOP_STATE = 0xFE;
     30 
     31         if (ch == MT_UART_SOF)
     32 
     33           state = LEN_STATE;      //切换状态
     34 
     35         break;
     36 
     37  
     38 
     39       case LEN_STATE:             //读取数据长度
     40 
     41         LEN_Token = ch;
     42 
     43         
     44 
     45         //接下去要正式接受有用的数据啦
     46 
     47         //开始之前要为接收数据做一系列初始化
     48 
     49         tempDataLen = 0;          //初始化数据指针
     50 
     51  
     52 
     53         /* Allocate memory for the data */
     54 
     55         //为数据分配内存,其实新建的是串口数据包(我是这样叫它的)
     56 
     57         pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
     58 
     59                                                         MT_RPC_FRAME_HDR_SZ + LEN_Token );
     60 
     61  
     62 
     63         if (pMsg)                 //如果内存分配成功
     64 
     65         {
     66 
     67           /* Fill up what we can */
     68 
     69           //把我们已知的内容填入数据包
     70 
     71           pMsg->hdr.event = CMD_SERIAL_MSG;        //事件号
     72 
     73           pMsg->msg = (uint8*)(pMsg+1);            //为存放的数据的数组开辟一个空间
     74 
     75           pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;   //数组第一位依旧是长度
     76 
     77           state = CMD_STATE1;                      //初始化结束,切换状态
     78 
     79         }
     80 
     81         else
     82 
     83         {
     84 
     85           state = SOP_STATE;
     86 
     87           return;
     88 
     89         }
     90 
     91         break;
     92 
     93  
     94 
     95       case CMD_STATE1:                             //写入CMD0
     96 
     97         pMsg->msg[MT_RPC_POS_CMD0] = ch;
     98 
     99         state = CMD_STATE2;                        //切换状态
    100 
    101         break;
    102 
    103  
    104 
    105       case CMD_STATE2:                             //写入CMD1
    106 
    107         pMsg->msg[MT_RPC_POS_CMD1] = ch;
    108 
    109         /* If there is no data, skip to FCS state */
    110 
    111         //切换状态,如果数据长度为0,则跳过一个状态
    112 
    113         if (LEN_Token)
    114 
    115         {
    116 
    117           state = DATA_STATE;
    118 
    119         }
    120 
    121         else
    122 
    123         {
    124 
    125           state = FCS_STATE;
    126 
    127         }
    128 
    129         break;
    130 
    131  
    132 
    133       case DATA_STATE:                              //依次写入数据
    134 
    135  
    136 
    137         /* Fill in the buffer the first byte of the data */
    138 
    139         pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen++] = ch;
    140 
    141  
    142 
    143         /* Check number of bytes left in the Rx buffer */
    144 
    145         bytesInRxBuffer = Hal_UART_RxBufLen(port);
    146 
    147  
    148 
    149         /* If the remain of the data is there, read them all, otherwise, just read enough */
    150 
    151         if (bytesInRxBuffer <= LEN_Token - tempDataLen)
    152 
    153         {
    154 
    155           HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer);
    156 
    157           tempDataLen += bytesInRxBuffer;
    158 
    159         }
    160 
    161         else
    162 
    163         {
    164 
    165           HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen);
    166 
    167           tempDataLen += (LEN_Token - tempDataLen);
    168 
    169         }
    170 
    171  
    172 
    173         /* If number of bytes read is equal to data length, time to move on to FCS */
    174 
    175         if ( tempDataLen == LEN_Token )           //写完切换状态
    176 
    177             state = FCS_STATE;
    178 
    179  
    180 
    181         break;
    182 
    183  
    184 
    185       case FCS_STATE:                             //帧校验位,确保正确
    186 
    187  
    188 
    189         FSC_Token = ch;
    190 
    191  
    192 
    193         /* Make sure it's correct */
    194 
    195         if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ + LEN_Token) == FSC_Token))
    196 
    197         {
    198 
    199           osal_msg_send( App_TaskID, (byte *)pMsg ); //校验正确就把数据包发送给...App_TaskID
    200 
    201         }                                            //这是什么?就是我们之前登记的任务号!
    202 
    203                                                      //具体查看MT_UartRegisterTaskID()这个函数!
    204 
    205         else 
    206 
    207         {
    208 
    209           /* deallocate the msg */                
    210 
    211           //错误就把包丢掉(释放内存)
    212 
    213           osal_msg_deallocate ( (uint8 *)pMsg );
    214 
    215         }
    216 
    217  
    218 
    219         /* Reset the state, send or discard the buffers at this point */
    220 
    221         state = SOP_STATE;                        //数据包接收完毕,切换回初始状态
    222 
    223  
    224 
    225         break;
    226 
    227  
    228 
    229       default:
    230 
    231        break;
    232 
    233     }
    234 
    235   }
    236 
    237 }
    MT_UartProcessZToolData

      代码很长,注释基本写在代码里面了,总结一下流程。

      ①判断起始码是不是0xFE(不是就别想继续下去啦)

      ②读取数据长度,初始化串口数据包pMsgmtOSALSerialData_t  pMsg

      ③给pMsg装数据

      ④校验和,正确则把pMsg向上发送,错误则丢弃

      ⑤初始化状态

     

      我们要做的就是简化流程,因为我们发送的数据格式是只含有数据内容的,因此要把起始码、数据长度之类的去掉。但是这样会导致数据长度变成未知的,无法声明动态数组。改变思路,定义一个定长的数组!

    修改后代码如下:

     1 void MT_UartProcessZToolData ( uint8 port, uint8 event )
     2 
     3 {
     4 
     5   uint8  ch, len = 0;
     6 
     7   uint8  uartData[128];
     8 
     9   uint8  i;
    10 
    11   
    12 
    13   (void)event;  // Intentionally unreferenced parameter
    14 
    15  
    16 
    17   while (Hal_UART_RxBufLen(port))
    18 
    19   {
    20 
    21     HalUARTRead (port, &ch, 1);
    22 
    23     uartData[len+1] = ch;
    24 
    25     len ++;
    26 
    27   }
    28 
    29   if(len)
    30 
    31   {
    32 
    33     uartData[0] = len;
    34 
    35     pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
    36 
    37                                                     len + 1 );
    38 
    39     if (pMsg)
    40 
    41     {
    42 
    43       /* Fill up what we can */
    44 
    45       pMsg->hdr.event = CMD_SERIAL_MSG;
    46 
    47       pMsg->msg = (uint8*)(pMsg+1);
    48 
    49       for(i=0; i<=len; i++)
    50 
    51         pMsg->msg[i] = uartData[i];
    52 
    53       osal_msg_send( App_TaskID, (byte *)pMsg );
    54 
    55     }           
    56 
    57   }
    58 
    59 }
    MT_UartProcessZToolData

      修改完接收数据包的代码之后,我们就应该去考虑下要怎么处理接收的代码啦。这个自然就是在SampleApp.c中进行的啦。还有,在开始之前要先在SampleApp.c中加入串口初始化,这个过程见上一篇《Z-Stack协议栈基础和数据传输实验》的5.1串口初始化部分。原谅我比较懒......

      打开SampleApp.c,找到事件处理函数SampleApp_ProcessEvent()。看到两个if语句里面分别有一个SYS_EVENT_MSGSAMPLEAPP_SEND_PERIODIC_MSG_EVT。这两个就是事件的编号。关于事件之前有说过,每个任务都可以有16个事件。这个时候会有这样的疑惑,这个事件和我们之前在串口数据包里面放入的事件有什么区别呢?为了解开这个疑惑,找到之前串口数据包的定义(MT_UART.c->MT_UartProcessZToolData()->pMsg查看定义->mtOSALSerialData_t查看定义->osal_event_hdr_t查看定义)。这样找到这个event后发现它是uint8型的,说明它只有8位,这个显然和上面提到的事件不一样嘛~

      这个问题解决了,那么我们应该写在哪个事件下面呢?MT_UART.c->MT_UartProcessZToolData()->osal_msg_send()查看定义,看到函数最后一行

    osal_set_event( destination_task, SYS_EVENT_MSG );

      看到这里应该就明白了,我们这个是属于SYS_EVENT_MSG事件哒。至于具体怎么工作,就是把消息放入队列,处理消息之类的,这里不再多说啦。

      回到SampleApp.c下的事件处理函数SampleApp_ProcessEvent(),在SYS_EVENT_MSG事件下还有一个选择“MSGpkt->hdr.event”,好啦,这个就是我们熟悉的“小事件”啦(因为只有8位,英文名又叫event,所以我直接这样叫它啦)。现在明白了吧,我们要写一个case语句把事件CMD_SERIAL_MSG放进去(这个事件名字就是初始化串口数据包的时候写进去的那个)。同时要在SampleApp.c文件中添加一个头文件#include “MT.h”,CMD_SERIAL_MSG是在这个文件中定义的。

    代码如下:

    //处理串口数据包
    
    case CMD_SERIAL_MSG:
    
      SampleApp_SerialMSG((mtOSALSerialData_t *)MSGpkt);
    
      break;

      代码里面SampleApp_SerialMSG()是什么函数呢?找了一圈没有找到,其实它是要自己写哒~你可以大概浏览一下SampleApp.c里面的函数,有没有发现没有一个符合我们的需求的?所以要自己写咯。

    代码如下:

     1 void SampleApp_SerialCMD(mtOSALSerialData_t *sd)
     2 
     3 {
     4 
     5   uint8 i, num = sd->msg[0];
     6 
     7   uint8 *ch = sd->msg;
     8 
     9   
    10 
    11   for(i=1; i<=num; i++)
    12 
    13     HalUARTWrite(0, ch+i, 1);
    14 
    15   HalUARTWrite(0, "
    ", 1);
    16 
    17   
    18 
    19   //这个是发送数据包的函数,复制后修改参数即可
    20 
    21   void SampleApp_SerialMSG(mtOSALSerialData_t *sd)
    22 
    23 {
    24 
    25   uint8 len = sd->msg[0];
    26 
    27   uint8 *ch = &sd->msg[1];
    28 
    29   
    30 
    31   HalUARTWrite(0, "I:", 2);
    32 
    33   HalUARTWrite(0, ch, len);
    34 
    35   HalUARTWrite(0, "
    ", 1);
    36 
    37   
    38 
    39   if ( AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
    40 
    41                        SAMPLEAPP_SERIAL_CLUSTERID,
    42 
    43                        len+1,
    44 
    45                        ch,
    46 
    47                        &SampleApp_TransID,
    48 
    49                        AF_DISCV_ROUTE,
    50 
    51                        AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
    52 
    53   {
    54 
    55   }
    56 
    57   else
    58 
    59   {
    60 
    61     // Error occurred in request to send.
    62 
    63   }
    64 
    65 }
    SampleApp_SerialCMD

      代码其余部分不解释,需要注意的是发送数据函数里的一个参数SAMPLEAPP_SERIAL_CLUSTERID,你去查看定义会发现查不到......嘿嘿,这个是要自己加上去哒。如图2所示。

     

    图2

      这个参数的作用之前已经说过啦,名字可以任意取。要注意的是SAMPLEAPP_MAX_CLUSTERS这个的值也要相应变大,看它名字就知道啦,它表示所有这类数中的最大值。

    实验进行到这里,我们已经可以把程序烧录到一个zigbee进行测试了。因为没有接收部分代码,实验结果只是通过串口助手发送数据给zigbee然后zigbee再发回给PC端。实验结果见图3

     

    3

      进行到这一步有没有点小开心?不过还要再坚持下,还有接收部分的呢~

      接收部分代码和昨天的实验非常类似,就不详细说啦,看看代码应该就能看懂啦~

     1 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
     2 
     3 {
     4 
     5 //  uint16 flashTime;
     6 
     7 //  uint8 len = pkt->cmd.Data[0];
     8 
     9   uint8 *ch = &pkt->cmd.Data[0];
    10 
    11  
    12 
    13   switch ( pkt->clusterId )
    14 
    15   {
    16 
    17     case SAMPLEAPP_SERIAL_CLUSTERID:
    18 
    19       
    20 
    21       HalUARTWrite(0, "friend:", 7);      
    22 
    23       HalUARTWrite(0, ch, pkt->cmd.DataLength-1);
    24 
    25       HalUARTWrite(0, "
    ", 1);
    26 
    27       break;
    28 
    29  /*   
    30 
    31     case SAMPLEAPP_PERIODIC_CLUSTERID:
    32 
    33       break;
    34 
    35  
    36 
    37     case SAMPLEAPP_FLASH_CLUSTERID:
    38 
    39       flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
    40 
    41       HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
    42 
    43       break;*/
    44 
    45   }
    46 
    47 }
    SampleApp_MessageMSGCB

      最后还要注意一点!两个zigbee一个做协调器,一个做路由器!不然无法通信!就因为这个原因我被坑了好几个小时......

     

    四、实验结果

      

    图4 两个zigbee实验结果

     

    三个zigbee实验结果(一个协调器,两个路由器)

    五、总结流程

     

     

     图6

  • 相关阅读:
    openCV使用
    Object-C知识点 (二) 控件的实用属性
    Linux 配置JDK + MyEclipse
    虚拟机+linux+大杂烩
    THREE.js代码备份——webgl
    THREE.js代码备份——webgl
    THREE.js代码备份——canvas_ascii_effect(以AscII码显示图形)
    THREE.js代码备份——canvas
    THREE.js代码备份——canvas_lines(随机点、画线)
    THREE.js代码备份——线框cube、按键、鼠标控制
  • 原文地址:https://www.cnblogs.com/Donut/p/4141890.html
Copyright © 2020-2023  润新知