物联网之Zigbee系统开发六(LED、风扇、温湿度的采集和控制)

A9-zigbee协调器-zigbee终端 通用指令

命令格式:一个字节,按位  char

7

6

5

4

3

2

1

0

仓库编号

设备编号

操作设备

仓库编号

 

0x40

1号仓库

0x80

2号仓库

0xc0

3号仓库

设备编号

 

操作掩码

 

0x00

风扇

0x00

关闭风扇

0x01

1档

0x02

2档

0x03

3档

0x10

蜂鸣器

0x00

关闭

0x01

打开

0x02

自动报警关闭

0x03

自动报警打开

0x20

LED

0x00

关闭

0x01

打开

0x30

数码管

0x00~0x09

显示0~9数字

0x0f

关闭数码管

仓库编号 + 设备编号 + 操作掩码 = 命令

例如:

0x40 +0x 00 + 0x 01 =0x 41  风扇一档

0x40 + 0x30 + 0x08 = 0x78   数码管显示8

0x40 +0x 30 + 0x0f = 0x7f   关闭数码管

这里实际使用到的只有1号仓储:LED灯的量灭、风扇的开关、温湿度的上传

LED灯打开:0x40+0x20+0x01=0x61

LED灯关闭:0x40+0x20+0x00=0x60

风扇打开:0x40+0x00+0x01=0x41

风扇关闭:0x40+0x00+0x00=0x41

程序文件里面设置了四个宏定义:LED_CTR、FAN_CTR、HT_CTR、ALL_CTR。在设置里面定义不同的宏来实现相关的功能,而不需要每个终端或者协调器新建工程,然后烧写文件:

协调器控制终端三个LED灯量灭(D7 D8 D9)

/******添加头文件******/
#include "MT_UART.h"
#include "string.h"

在工程预编译选项 MT_TASK、MT_SYS_FUNC、MT_ZDO_FUNC、LCD_SUPPORTED=DEBUG 前加上 x,加上 x 代表不编译对应的功能代码。 

/******添加头文件******/
#include "MT_UART.h"
#include "string.h"

编写LED灯相关函数

#ifdef LED_CTR
void led_init(void)//p1_3 p1_4 p1_1(D7 D8 D9)初始化
{
  //功能设置寄存器,设置为普通I/O口
  P1DIR |= 19;  //设置为输出模式
  //P1SEL &= ~19; //设置为普通io模式
  //P1INP &= ~19; //设置为上拉/下拉模式
  //P1 &= ~(1<<4);
  P1 |= (1<<4);
}

void led_over()//p1_3 p1_4 p1_1(D7 D8 D9)翻转
{
  P1 ^= 19;
}

void led_on()//p1_3 p1_4 p1_1(D7 D8 D9)低电平点亮
{
  P1&= ~19;
  //st(P1_3 = 0);
}

void led_off()//p1_3 p1_4 p1_1(D7 D8 D9)高电平点亮
{
  P1|= 19;
}
#endif

如果编译时遇到:IARError[PA045]:function "XXX" has no prototype。可通过以下方式解决:

这个报错的意思是没有函数没有原型,但有的时候是明明声明了原型的。解决这个问题的办法很简单,至少有两种方法。 
1 调用无参函数的时候,括号内的参数要写void。如void show(void) 
2取消勾选require prototype! 

协调器控制终端风扇开关

#ifdef FAN_CTR
void fan_init()//风扇初始化
{
  P1DIR |= 1<<3;  //设置为输出模式
}

void fan_over()//风扇翻转
{
  P1 ^= 1<<3;
}

void fan_on()//风扇开
{
  P1 &= ~(1<<3);
}

void fan_off()//风扇关
{
  P1 |= 1<<3;
}
#endif

终端获取温湿度值

DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器

DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零。操作流程如下:

一次完整的数据传输为40bit,高位先出。

数据格式:8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和 

数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。

用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。

总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束。主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。

总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1.格式见下面图示。如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。当最后一bit数据传送完毕后,DHT11拉低总线

50us,随后总线由上拉电阻拉高进入空闲状态。

DHT11的时序图:

zigbee延时相关函数

#include "OnBoard.h"要包含头文件
//延时函数

/*Board specific micro-second wait*/
extern void Onboard_wait( uint16 timeout );//微秒us延时,貌似我用到的协议栈中只有这一个延时函数,下面的函数好像没有

void Delay_1us(void) //1 us延时
{
    MicroWait(1); //这是协议栈内的函数
}

void Delay_us(uint Time) //us延时
{
    unsigned char i;
    for(i=0;i<Time;i++)
    {
        MicroWait(1);
    }
}

void Delay_ms(uint Time)//n ms延时
{
    unsigned char i;
    while(Time--)
    {
        for(i=0;i<100;i++)
        MicroWait(10);
    }
}

/****************************
延时函数
*****************************/
void Delay_us(void) //1 us延时
{
    MicroWait(1);
}

#include "stdint.h"
#include "stdio.h"

#ifdef HT_CTR
//温湿度传感器上传的40位数据包的存储位置
uint8_t ucharT_data_H=0,ucharT_data_L=0,ucharRH_data_H=0,ucharRH_data_L=0,ucharcheckdata=0;

uint8_t FLAG_HT = 0;//采集的温湿度数据是否已经发送给协调器,1表示还没发送,0表示已经发送

char buf[128]={0};

void DHT11_TH(void)   //温湿传感器采集函数
{
  int i = 0;
  //char buf[128]={0};
  memset(buf,0,128);
  ucharT_data_H=0;ucharT_data_L=0;ucharRH_data_H=0;ucharRH_data_L=0;ucharcheckdata=0;
  P1DIR |= 1<<2;  //设置为输出模式
  P1 &= ~(1<<2);//主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒
  Onboard_wait(18000);//18毫秒延时
  P1DIR &= ~(1<<2);  //设置为输入模式
  Onboard_wait(40);//40微秒延时
  while(!(P1&(1<<2)));//响应信号:低电平(80微秒)
  while(P1&(1<<2));// 响应限号:高电平(80微秒)
  for(i=0;i<8;i++)
  {
    while(!(P1&(1<<2))); // 第(1、2、3、4、5、6、7、8)bit开始,低电平(50微秒)
    Onboard_wait(30);//30微秒之后判断P1_2引脚是否为高电平
    if(P1&(1<<2))//如果为高电平,则输入为高电平
    {
      ucharRH_data_H |= (1<<(7-i));
      while(P1&(1<<2));
    }
    else
    {
      ucharRH_data_H &= ~(1<<(7-i));
    }
    //while(!(P1&(1<<2)));
  }
  
  for(i=0;i<8;i++)
  {
    while(!(P1&(1<<2))); // 第(1、2、3、4、5、6、7、8)bit开始,低电平(50微秒)
    Onboard_wait(30);//30微秒之后判断P1_2引脚是否为高电平
    if(P1&(1<<2))//如果为高电平,则输入为高电平
    {
      ucharRH_data_L |= (1<<(7-i));
      while(P1&(1<<2));
    }
    else
    {
      ucharRH_data_L &= ~(1<<(7-i));
    }
   // while(!(P1&(1<<2)));
  }
  
  for(i=0;i<8;i++)
  {
    while(!(P1&(1<<2))); // 第(1、2、3、4、5、6、7、8)bit开始,低电平(50微秒)
    Onboard_wait(30);//30微秒之后判断P1_2引脚是否为高电平
    if(P1&(1<<2))//如果为高电平,则输入为高电平
    {
      ucharT_data_H |= (1<<(7-i));
      while(P1&(1<<2));
    }
    else
    {
      ucharT_data_H &= ~(1<<(7-i));
    }
   // while(!(P1&(1<<2)));
  }
  
  for(i=0;i<8;i++)
  {
    while(!(P1&(1<<2))); // 第(1、2、3、4、5、6、7、8)bit开始,低电平(50微秒)
    Onboard_wait(30);//30微秒之后判断P1_2引脚是否为高电平
    if(P1&(1<<2))//如果为高电平,则输入为高电平
    {
      ucharT_data_L |= (1<<(7-i));
      while(P1&(1<<2));
    }
    else
    {
      ucharT_data_L &= ~(1<<(7-i));
    }
    //while(!(P1&(1<<2)));
  }
  
  for(i=0;i<8;i++)
  {
    while(!(P1&(1<<2))); // 第(1、2、3、4、5、6、7、8)bit开始,低电平(50微秒)
    Onboard_wait(30);//30微秒之后判断P1_2引脚是否为高电平
    if(P1&(1<<2))//如果为高电平,则输入为高电平
    {
      ucharcheckdata |= (1<<(7-i));
      while(P1&(1<<2));
    }
    else
    {
      ucharcheckdata &= ~(1<<(7-i));
    }
    //while(!(P1&(1<<2)));
  }
  if(ucharcheckdata == (ucharT_data_H + ucharT_data_L + ucharRH_data_H + ucharRH_data_L))
  {
    HalUARTWrite(0,"success\n", strlen("success\n"));//串口发送
    sprintf(buf,"温度:%u.%u 湿度:%u.%u\n",ucharT_data_H,ucharT_data_L,ucharRH_data_H,ucharRH_data_L);
    HalUARTWrite(0,(unsigned char *)buf, strlen(buf));//串口发送
    FLAG_HT = 1;
  }
  else
  {
   HalUARTWrite(0,"error\n", strlen("error\n"));//串口发送
  }
}
#endif

注意:这里编译可能会报错:找不到stdint.h文件,可以手动添加文件所在目录:

  // Setup for the periodic message's destination address
  // Broadcast to everyone 协调器设置为广播模式
  SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;
  SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;

  // Setup for the flash command's destination address - Group 1终端设置为单播模式
  SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddr16Bit;//设置为单播模式
  SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_Flash_DstAddr.addr.shortAddr = 0x0000;//协调器地址

#include "MT.h"

#ifdef ALL_CTR
  MT_UartRegisterTaskID( task_id );//注册串口任务,使串口产生事件
#endif

#ifdef ALL_CTR
#include "MT.h"
#include "mt_uart.h"
#endif 

#ifdef ALL_CTR
case CMD_SERIAL_MSG: //添加轮询事件,CMD_SERIAL_MSG:检测是否有串口数据到达
SerialData_Analysis((mtOSALSerialData_t*)MSGpkt);/*协议栈中并没有封装
串口数据到达时处理事件的函数,所以这里需要自己编写封装一个事件处理函数。函数实
现见下面代码函数需要传入一个串口数据结构体,而MSGpkt是一个消息结构体,所以需要
强制转换为mtOSALSerialData_t*类型*/
break;
#endif
#ifdef ALL_CTR
void SerialData_Analysis((mtOSALSerialData_t*)MSGpkt)
{
  if(zgDeviceLogicalType == ZG_DEVICETYPE_COORDINATOR)//如果是协调器,则以广播的形式将串口接收的数据发送给终端
  {
    AF_DataRequest( &SampleApp_Periodic_DstAddr/*SampleApp_Periodic_DstAddr:宏定义,表示广播地址*/, &SampleApp_epDesc,
                       SAMPLEAPP_PERIODIC_CLUSTERID,//SAMPLEAPP_PERIODIC_CLUSTERID:命令号。发送数据和对方接收消息数据时的命令号要保持一致
                       *(MSGpkt->msg),
                       (MSGpkt->msg) + 1,
                       &SampleApp_TransID,
                       AF_DISCV_ROUTE,
                       AF_DEFAULT_RADIUS );
  }
//  else if(zgDeviceLogicalType == ZG_DEVICETYPE_ENDDEVICE)//如果是终端,则以单播的形式将串口接收的数据发送给协调器
//  {
//      AF_DataRequest( &SampleApp_Flash_DstAddr/*SampleApp_Flash_DstAddr:宏定义,表示协调器的地址*/, &SampleApp_epDesc,
//                       SAMPLEAPP_PERIODIC_CLUSTERID,//SAMPLEAPP_PERIODIC_CLUSTERID:命令号。发送数据和对方接收消息数据时的命令号要保持一致
//                       *(MSGpkt->msg),
//                       (MSGpkt->msg) + 1,
//                       &SampleApp_TransID,
//                       AF_DISCV_ROUTE,
//                       AF_DEFAULT_RADIUS );
//  }
}
#endif

void MT_UartInit ()
{
  halUARTCfg_t uartConfig;
 
  /* Initialize APP ID */
  App_TaskID = 0;
 
  /* UART Configuration */
  uartConfig.configured           = TRUE;
  uartConfig.baudRate             = MT_UART_DEFAULT_BAUDRATE;
  uartConfig.flowControl          = MT_UART_DEFAULT_OVERFLOW;
  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; /*调用一个回调函数,
             才能真正触发CMD_SERIAL_MSG(是否有串口数据送达)事件,进而才能调用对应事
             件处理函数。但是该回调函数要求发送过来的数据格式必须与协议栈定义的格式保持
             一致,否则不会触发CMD_SERIAL_MSG(是否有串口数据送达)事件,所以我们需要
             改写函数,使得该回调函数接收到任意格式的串口数据都能触发CMD_SERIAL_MSG
            (是否有串口数据送达)事件。*/
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
  uartConfig.callBackFunc         = MT_UartProcessZAppData;
#else
  uartConfig.callBackFunc         = NULL;
#endif
 
  /* Start UART */
#if defined (MT_UART_DEFAULT_PORT)
  HalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig);
#else
  /* Silence IAR compiler warning */
  (void)uartConfig;
#endif
 
  /* Initialize for ZApp */
#if defined (ZAPP_P1) || defined (ZAPP_P2)
  /* Default max bytes that ZAPP can take */
  MT_UartMaxZAppBufLen  = 1;
  MT_UartZAppRxStatus   = MT_UART_ZAPP_RX_READY;
#endif
 
}

 

void MT_UartProcessZToolData ( uint8 port, uint8 event )
{
  uint8 ch, len = 0;
  uint8 uartData[128];//定义一个数组暂存数据
  uint8 i;
  (void)event;//Intentionally unreferenced parameter
  while(Hal_UART_RxBufLen(port))//只要检测到串口缓存区有数据
  {
    HalUARTRead (port,&ch,1);//就把数据一个一个的传给ch变量
    uartData[len+1] = ch;//再把ch的数据传给数组
    len ++;
  }
  if(len)
  {
    uartData[0] = len;//第一个元素呢设为数据长度
    pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof(mtOSALSerialData_t ) + len + 1 );//为串口数据包分配内存
    if (pMsg)
    {
      /* Fill up what we can */
      pMsg->hdr.event = CMD_SERIAL_MSG;//设置串口数据到达事件
      pMsg->msg = (uint8*)(pMsg+1);//把数据定位到结构体数据部分
      for(i=0; i<=len; i++)
      {
        pMsg->msg[i] = uartData[i];//再把暂存区数据放入串口数据包中
      }
      osal_msg_send( App_TaskID, (byte *)pMsg );//将数据包发往消息队列
    }
    osal_msg_deallocate ((uint8 *)pMsg );//分起的内存用完后记得开除,以免内存溢出。
  }
}

控制终端LED、风扇,协调器接收数据并打印到PC端:

        // Received whenever the device changes state in the network
        case ZDO_STATE_CHANGE:
          SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
          if ( (SampleApp_NwkState == DEV_ZB_COORD)
              || (SampleApp_NwkState == DEV_ROUTER)
              || (SampleApp_NwkState == DEV_END_DEVICE) )
          {
            // Start sending the periodic message in a regular interval.
#ifdef HT_CTR //温湿度数据需要不停的上传,所以设置定时器,每隔5秒钟上传一次数据
            osal_start_timerEx( SampleApp_TaskID,
                              SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
                              SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
#endif
            
            /******************************************************************************************************************IO初始化*********************/
#ifdef LED_CTR  
            led_init();//初始化LED灯
#endif
#ifdef FAN_CTR
            fan_init();//初始化电风扇
#endif
          }
          else
          {
            // Device is no longer in the network
          }
          break;

        default:
          break;
      }

#ifdef HT_CTR
    DHT11_TH();//温湿度数据采集
    if(FLAG_HT)//采集的数据通过无线传输给协调器
    {
      AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
                       SAMPLEAPP_FLASH_CLUSTERID,
                       strlen(buf)+1,//注意这个位置必须是实际长度加1,不然协调器接收到的数据尾部是不确定的(乱码)
                       buf,
                       &SampleApp_TransID,
                       AF_DISCV_ROUTE,
                       AF_DEFAULT_RADIUS );
    }
    FLAG_HT = 0;
#endif

        case AF_INCOMING_MSG_CMD://第二个额外事件:判断是否有新的无线消息到达了
          SampleApp_MessageMSGCB( MSGpkt );
          break;

void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
  uint16 flashTime;

  switch ( pkt->clusterId )
  {
    case SAMPLEAPP_PERIODIC_CLUSTERID://中断接收到协调器发送过来的数据
      if(pkt->cmd.Data[0] & 0x40)//如果是1号仓库
      {
#ifdef FAN_CTR
        if((pkt->cmd.Data[0] & (3<<4)) == 0)//如果是风扇
        {
          if((pkt->cmd.Data[0] & 15) == 0)//关闭风扇指令
          {
            fan_off();
          }
          else if(pkt->cmd.Data[0] & 0x01)//开启风扇指令
          {
            fan_on();
          }
        }
#endif
#ifdef LED_CTR
        if(pkt->cmd.Data[0] & 0x20)//如果是LED灯
        {
          if((pkt->cmd.Data[0] & 15) == 0)//关闭LED灯
          {
            led_off();
          }
          else if(pkt->cmd.Data[0] & 0x01)//开启LED灯
          {
            led_on();
          }
        }
#endif
      }
      break;

    case SAMPLEAPP_FLASH_CLUSTERID://终端发送过来的温湿度数据打印到PC端
#ifdef ALL_CTR
      HalUARTWrite(0,pkt->cmd.Data, strlen(pkt->cmd.Data));//串口发送
#endif
      flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
      HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
      break;
  }
}

实验结果:灯的量灭和风扇的开关这里不做演示

猜你喜欢

转载自blog.csdn.net/weixin_39148042/article/details/82219913