关于zigbee和ESP8266进行温湿度检测上传数据的问题

前言

做了一个使用CC2530和ESP8266模块连接,检测温湿度和光照强度,通过ESP8266上传数据并且接受指令控制led。遇见很多坑,废寝忘食的全部解决了,特在此记录一下以供以后参考。

1.for语句的使用

一般情况编写zigbee程序用的是IAR8050,对于for语句有一个很隐秘的坑,就是如果用for语句构建的delay延迟函数会在编译烧录到单片机里却发现没有延迟,延迟函数似乎失效了。我上网找了一下发现是iar有个功能是编译时候会优化程序。

如图所示,这里我已经改成NONE了,之前是high,由于延时函数往往是单纯的for的循环计算,在高程度优化后,for语句以为内部没有内容,就会在编译的时候删掉,从而延迟失效。

2.串口引脚的重定义

根据CC2530的寄存器手册可知,如图所示

U0,U1两个串口分别各自有两套引脚方案,U0是P02,P03或者P14,P15,U1是P04和P05或者P16和P17,由于我的仿真器要用到P1口,而且U0连接着TTL转USB,所以和ESP8266的串口通讯需要用U1,但是在zstack中U1默认是用P16和P17作为TX和RX。而且zstack只能用一个USART,因为在zstack工程中,如图所示,使用U0或U1是靠配置中的宏定义ZTOOL_P2或者ZTOOL_P1确定的。

两者似乎不能同时存在,如果同时存在也是优先于ZTOOL_P2,在写串口函数HalUARTWrite中我们可以看到如下图。

使用哪个串口是由HAL_UART_DMA的值确定的,去寻找其宏定义如下图

由上图可以看到宏定义的值还是由ZTOOL_P2或者P1决定的,而且如果定义了P2,则P1似乎没有作用了。

在zstack中,U1默认使用P1口,而且没有选择为P0口的选项,如果使用U1,则zstack会默认配置P1IO口,通过HAL_uart.c文件可以找到串口初始化函数,默认使用DMA方式的串口。

进入这个函数后可以看到,zstack正是在这里配置了串口的IO口,分别赋值了PERCFG,PxSEL,ADCCFG,这几个关于IO口配置的寄存器。在给PERCFG赋值时,在使用U1的情况下,原来的程序是PERCFG |= HAL_UART_PERCFG_BIT;

查看HAL_UART_PERCFG_BIT的宏定义可以看到在注释中TI已经说U1就是设置在P1上的。

从数据手册中查看PERCFG的内容可以看到U1就是01,U2的设置就是02,而上文提到的PERCFG |= HAL_UART_PERCFG_BIT;就是在PERCFG第一位赋值1,我改成了PERCFG &= ~HAL_UART_PERCFG_BIT;这样U1串口就可以使用P04,P05作为引脚了。

在下两条语句ADCCFG &= ~HAL_UART_Px_RX_TX;PxSEL  |= HAL_UART_Px_RX_TX;中就是配置RX和TX的IO口,一个是避免作为ADC引脚,一个是设置为外设功能。如下图所示

查看HAL_UART_Px_RX_TX的宏定义如下图。

原本其值为0xC0,也就是11000000,即P16和P17,现在是P04和P05,所以改为0X30。‘

还有一个地方就是PxSEL和其他x有关的值,查找宏定义如下图,将原来的P1改为P0,下面从UxCSR开始则不改,因为是涉及到串口的配置。

配置完以上部分就可以使用U1了,同样U0的P1口类似。

3.ESP8266的初始化问题(1)

一开始,我是放在编写了一个初始化子函数然后放在SampleApp_Init( uint8 task_id )中的,后来发现根本无法完成初始化/(ㄒoㄒ)/~~。因为之前用过32连接8266,有相关的经验,但是32的库函数和ZSTACK有一个很大的差别,就是ZSTACK串口默认用DMA,所以串口接收和发送不会产生中断,众所周知(*^_^*),ESP8266模块通过AT指令来完成初始化,为了能确定是否每一条AT指令配置成功,需要检测ESP8266是否传来“OK”,在32中,可以通过while语句循环发送AT指令并通过串口中断检测是否传来“OK”,而在zstack中则不可以用while语句,因为串口的接收不会产生中断,所以会卡死在while中。所以不能放在SampleApp_Init( uint8 task_id )中。至于怎么解决,在后面会写到。

4.Zstack串口DMA工作方式的工作原理

1.Zstack的轮询方式

Zstack是很经典的轮询方式来执行程序的,通过循环检测一系列任务是否需要执行来工作的,这就导致使用HalUARTWrite函数其实是将待发送内容放到指定的DMA读取空间,并没有放到串口上。在之后的一遍的任务轮询时,会检测到有DMA的任务,这里才将待发送内容通过DMA放到串口上。这就导致了使用HalUARTWrite并不能立马从串口发送出去,不具有及时性,编程时需要考虑到这一点。如果可以实时仿真的话,会发现运行完串口发送函数,串口并没有发送,就是这个原因。

2.串口接收函数HalUARTRead()的问题

根据该函数的定义如下图可知。

串口的接受同样使用的时DMA,在外部通过串口发送信息到单片机的那一瞬间,DMA已经将内容从串口接收到一个区域(具体在哪不知道,也许是随机的,也许是固定的)。而HalUARTRead()函数实际上是将该区域的内容放到一个程序中定义的区域(如字符串数组)中去。HalUARTRead(uint8 port, uint8 *buf, uint16 len)函数中有三个参数,port是串口号,我这里用的1,buf是程序定义区域的首地址,len是读取最大长度。

那么问题来了,如果设置的读取最大长度是10,可是发送来了20个字节的信息,那么会选取哪部分呢,这个函数不是会选取20中一部分10字节的内容,而是从buf的首地址开始,往后依次写10个字节,然后继续从首地址开始,将后十个字节写上去,覆盖了前十个字节,所以在编写程序使用该函数时,需要注意到这一点。

又因为采用轮询制执行程序,所以在读取接收的内容之前,DMA会将在此之间传入串口的任意字符读取并储存,并且按照接收先后顺序储存,具体能储存多少字符暂时未知。所以为了防止想要接收的字符串被覆盖,需要提前预估接收最大值len的值。

5.ESP8266的初始化问题(2)

前面说到因为轮询制,而不能产生中断,所以不能将初始化子函数放在SampleApp_Init( uint8 task_id )中,为了解决不能用while语句导致的初始化无法进行,我选择将初始化函数放在SampleApp_ProcessEvent()中作为一个事件来处理,如下图所示。

因为协调器与ESP8266相连,所以只有协调器需要初始化ESP8266,由于初始化8266需要数个AT指令,所以我以轮询为循环,在一次次循环中检测AT指令的回复和发送AT指令。通过ESP_flag值的改变来确定每一次循环需要执行哪一条AT指令,并且在一次循环中获取串口接收内容,判断是否进行下一条AT指令。

6.ESP8266发送函数的编写和注意

由于我使用的是服务器模式,且使用为多连接,所以ESP8266不能使用透传方式发送,需要先发送指令AT+CIPSEND=【客户端ID号】,【字符串长度】,在收到OK>后传输内容,如下图所示。

在这里我依然利用轮询作为循环,先发送AT指令,在第二次轮询接收到OK后发送内容。关于如何利用轮询作为循环,在后面还会讲到。

7.strstr函数的使用注意事项

strstsr函数是检测第一项参数的字符串内是否有第二项参数的字符串。但是需要注意的是,strstr没有规定检测到多少位,那如果这样的话,岂不是如果没一直没有要寻找的字符串,将会一直寻找下去吗?为了避免这样的情况发生,strstr规定当检测到某一位又0即字符“\0”,则停止检测,并返回NULL,如果在此之前已经找到目标字符串,则返回地址。

由于strstr遇到0就会退出并返回NULL,如果我们要寻找的字符串在0之后,则会明明有却无法检测出来。比如ESP8266在执行完AT指令后会将执行的AT指令和执行结果(OK或ERROR)一起送入串口,但很不巧,AT指令和结果中间可能出现0,导致我们无法通过strstr函数得知是否有OK,为了防止这样的情况发生,我补充了一个修正函数。


void correct_string(uint8 *buff, uint16 len)
{
  uint16 i;
  for(i=0;i<len-1;i++)
  {
    if((* (buff+i))==0)
    {
      (* (buff+i))=1;
    }
  }
  (* (buff+len-1))=0;
}

该修正函数会使在指定地址开始的指定长度区域中0改为1,这样就可以避免strstr函数检测到0,并在最后一位置0,这样则防止strstr运行时间过长。

8.ESP8266发送函数使用轮询实现

如下图所示,在接收函数SampleApp_MessageMSGCB()中我是用发送函数像ESP8266发送信息。

由于发送分两步执行,所以启用事件发生时钟函数:

 osal_start_timerEx( SampleApp_TaskID,
                         SAMPLEAPP_ESP_SEND_EVT,
                         SAMPLEAPP_ESP_SEND_EVT_TIMEOUT );

使在指定时间后再执行一次esp8266_send();如下图所示,

如果发送成功则清除ESP8266发送缓冲区espdata的内容,如果发送失败则重新发送AT+CIPSEND。

9.Zstack中事件的添加

zstack中事件的添加是在SampleApp.h中完成的,如下图所示。

分别配置了事件ID和事件发生时间,其中需要注意的是事件的设置,Zstack规定每一个任务最多16个事件,为什么是16个事件呢(・∀・(・∀・(・∀・*)?这里我们可以从函数SampleApp_ProcessEvent()中看出端倪,如下图所示。

单片机是如何知道到底是哪个事件发生了呢,从语句

if ( events & SAMPLEAPP_ESP_CONFIG_EVT )我们可以看到,zstack通过将events和事件ID对比,进行与运算,如果结果为1则是该事件。因为是与运算,所以若要event和某个事件ID与之后为1,和其他事件为0,则只能在16位二进制中只有1位为1,所以只能最多有16个事件,如果有仿真条件的话,可以看到当为系统事件时,即
  if ( events & SYS_EVENT_MSG ),events的值为32768,二进制就是1000 0000 0000 0000。所以在添加事件时候ID只能为0X0001,0X0002,0X0004,0X0008等,不能为其他值,比如0X0003,如果这样的话,就会执行事件ID为0X0002的处理程序。

10.ADC的配置和计算

如果用过Zstack的ADC的伙伴们一定会对Zstack超级烂的宏定义有所印象,这也是一个老生常谈的问题了,在这里我就顺带总结一下并给出自己的理解。

ADC读取电压函数HalAdcRead (uint8 channel, uint8 resolution)最有争议的部分如下图。

那就是分辨率的问题,根据Zstack的宏定义可知分辨率分别为8,10,12,14。然而,根据数据手册如下图所示可知分辨率为7,9,10,12。实际上TI工作人员可能高估了CC2530的adc的性能,后来实验发现只能最高12位。

所以下面的代码要修改为这样:

 switch (resolution)
  {
    case HAL_ADC_RESOLUTION_8:
      reading >>= 9;
      break;
    case HAL_ADC_RESOLUTION_10:
      reading >>= 7;
      break;
    case HAL_ADC_RESOLUTION_12:
      reading >>= 6;
      break;
    case HAL_ADC_RESOLUTION_14:
    default:
      reading >>= 4;
    break;
  }

因为ADC转换的值放在以下寄存器,可知最后两位时保留的,不能用,Zstack是通过将ADCH的值左移八位加上ADCL的值,而真正有效的值是从最高位15位往低位方向的分辨率对应的位数,比如12分辨率就是4位到15位,所以程序中又右移了4位。

接下来就是ADC的计算,毕竟我们不可能直接输出一个16位的变量,所以我们必须将其转化为某个电压,一般来说,就是(adc的16位变量)×(参考电压)/(分辨率的十进制)。但是,(●ˇ∀ˇ●),注意ADC的变量是带符号的,也就是说12位分辨率输出的变量最高位为符号位,所以其实真正的值是后面11位,所以这11位才是真正的分辨率,所以要除以2^(11)即2048而不是4096。

11.总结

以上就是我最近做的一个项目踩过的坑和总结的经验,还包括了2023年学习Zigbee的一些经验。如果还有机会,还能有新的经验和教训我将继续更新。希望本文对各位有所帮助~( •̀ ω •́ )y

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
本文首发于DF创客社区作者:2877137721 原文链接: DIY属于你的智能家居系统,zigbee,esp8266,51单片机 DIY家庭智能家居控制系统(2)51,zigbeeesp8266 相关附件于原文下方下载 【介绍】 随着微功耗处理器以及通讯芯片的发展,以往较为耗能的有线通讯方式越来越阻碍了通讯网络的发展,于是一大批的无线通讯方案应运而生,例如NBIOT,蓝牙4.0,zigbee等等。同时微功耗无线处理器的流行使得传统家电带上了智能的色彩,智能家居必将是未来家庭的必备。某些高科技企业也在揶揄这块市场,纷纷推出了自家的智能家居解决方案,比较知名的有米家方案以及阿里智能的解决方案。但是回到现实的使用上来说,每个家庭的条件环境其实不一样,同样的产品未必在每个家庭上都可以使用方便,所以个性化的定制产品才可以更好的方便我们的生活。 于是在暑假期间,萌生了DIY一套智能家居方案的想法,具有各种智能开关和传感器等节点,并且对接物联平台实现联网控制以及数据上传。由于家庭中将会使用到的智能设备种类繁多,所以我将采用模块化的设计思路,即采用核心板加外围功能部分的思路,像搭积木一样的构建各个智能设备。大家有同样想制作的想法可以参考。 截至发帖前,我完成了智能网关,智能墙壁开关,无线遥控开关以及无线气象站的设计制作,之后有新的设备加入的话,我会及时的更新。 【准备事项】 完成这个涉及到多方面的项目,需要的硬件设备以及开发环境较多,所以我先大概给出一个列表: 软件篇: ArduinoIDE:用于给ESP8266编程使用 KEIL uVision5 :用于给51单片机编程使用 串口调试器:用于监控单片机输出数据 lceda设计软件:绘制电路原理图,设计PCB电路板 硬件篇: NodeMCU开发板一块 亿佰特zigbee模块若干(视节点数量而定) STC8F2系列单片机若干 核心的主要控制器件在此列出,其他元器件会在制作过程中一一说明。 【制作过程】 我构想了一张智能家庭的网络拓扑图,大家可以欣赏以下,后期图上的设备都将加入进来: 由于涉及到多个设备的设计制作,所以本个报名贴先奉上智能家居核心板,智能网关以及无线气象站的制作过程: #制作过程之核心板篇# >>>本项目使用zigbee网络进行智能家居之间的通信。 好早之前就接触过zigbee组网,对于这种低成本低功耗的网络还是抱有很大信心的。这里讲解选择这个网络的几大理由: mesh结构的网络很适合智能家居的控制结构,在入网的任何一个节点都可以访问到所有节点的数据,这点很适合网关控制各个设备。 低功耗使得终端设备甚至可以采用电池供电,使得所有的模组都尽量可能的无线化。 多跳传输,无线方案中最大的问题就是数据发送的不稳定以及障碍物对信号的遮挡导致数据无法正确传输,而ZigBee的多跳恰好解决了这个问题,节点会自动选择优质的传输路线多跳传输,保证信号质量。 总结一下:zigbee网络确实是好,但是对于我这种比较懒惰的人并不想去学习那复杂的传输理论以及zigbee通讯芯片的编程,于是我选择上网购买ZigBee模块,最后选择了一种小型的串口转zigbee模块,比较方便单片机通讯。 说到模块化,那么就需要核心的控制板。本质来说就是将单片机以及购买的zigbee模块集成到一张电路板上,并且预留各种接口,方便后期移植到各种设备上,这样一张电路板就可以适用各种智能家居设备了。 在保证功能足够的同时,体积也是我需要考虑的问题,如果核心板做的过大,会导致无法安装到某些空间狭小的智能设备中,所以小体积是我所着重考虑的。 基于多方面考虑之后,stc8F2k08s2进入了我的视线,小巧的sop16封装以及简单的外围电路,足够的IO口(14个)。这款芯片成为了核心板的控制单片机。于是一番绘制原理图以及PCB之后,成品大概明朗了: 焊接好的成品如下: 这将会成为以后所有我的智能家居方案的核心控制板。 #制作过程之智能网关篇# 完成了核心板的制作,网关成为了下一个比较关注的对象,因为家中的所有智能设备的控制以及通讯都会由他来完成,包括链接到互联网上传数据,所以说对于网关的硬件选取也是重中之重。上联互联网,下接zigbee小型通讯网,网关担任了一个家庭控制中心的角色,对于它的选择我认为esp8266是个不错的方案,可以支持arduino ide编程,这对于不太了解网络通讯协议的我来说是个好消息,因为在arduino的编程中,我可以借助强大的库函数来完成我想要的功能。 ESP8266是一个拥有了近80Mhz的主频的32位处理器,内置了wifi模组省去了网线,并且拥有丰富的外设以及较高的性能,可以胜任智能网关的工作。当然,近期乐鑫发布的ESP32系列芯片也会是个好的选择,更高的主频以及wifi蓝牙双模的设计让其拥有更加方便的接

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值