请求方式:GET
URL: http://api.heclouds.com/devices/device_id/datapoints
服务器或上位机下发主题报文(控制下位机):
API函数:
请求方式:POST
URL: http://api.heclouds.com/mqtt?topic=xxx
以上2个网络通讯的API函数至关重要,就是实现常规情况下OneNet物联网开发的关键性技术支持。(情况允许的条件下,建议读者朋友们去好好研读一下技术文档,将会为之后的开发大大助力)
三、下位机外设驱动
3.1 ESP8266模块
作者采用的ESP8266模块为ESP8266NodeMCU,是需要进行烧入AT固件,才能实现目标网络通讯。作为常见的物联网开发模块,ESP8266的出现大大降低了物联网开发的难度系数,也普及了物联网的发展。
AT指令最早在蓝牙模块上接触过,所谓AT指令实质上就是一些起控制作用的特殊字符串。模块可以通过AT指令控制搭配使用源代码API函数开发,总体开发速度快,难度较低。
不同厂商芯片的AT固件可能有所不同,但是指令基本一致(作者使用的是乐鑫的)。
说明:由于篇幅有限,这里就不和大家单独详细介绍AT指令。指令的详细参数及使用说明请参考官方文档:ESP8266 AT指令集。
3.2 OLED模块
本项目中0.96寸OLED模块的使用仅为显示DHT11传感器采集到的温湿度信息,以此来对比是否和服务器端以及上位机APP端的数据一致性。对其使用有不是太了解的读者朋友可以参考,作者另一篇基础教学博客:(2条消息) 【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客_oled显示图片程序 【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客_oled显示图片程序")
本项目的代码都是基于作者以前基础教学上的项目代码搭建而成,保证读者朋友可以实现快速复现。
3.3 DHT11模块
本项目中DHT11为下位机MCU采集周围环境温度和湿度的传感器,当然,条件允许的情况下还可以附加很多环境传感器(比如:烟雾传感器,环境光传感器,二氧化碳传感器等等)。当然得益于OneNet平台的布局,本项目教学的底层逻辑支持读者朋友的自我DIY,实现自主化的物联网产品设计。
DHT11模块驱动参考博客:基于stm32的太空人温湿度时钟项目——DHT11(HAL库)_混分巨兽龙某某的博客-CSDN博客
3.4 KEY和LED
KEY和LED都是源于作者正点原子精英版开发板上自备的(如果和作者同款开发板移植开发将会特别简单快速),属于最基本的GPIO操作相信各位应该都是掌握的
特别注意:
(1)这里的KEY按键从设计逻辑上就可以看出应该是需要采用外部中断的;
(2)KEY按下之后会改变LED的亮灭状态,为了同步上位机此时的LED状态,所以需要触发串口通讯中断(考虑嵌套中断情况时候中断优先级的安排)。
四、CubeMX配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3、TIM2配置:由上面可知DHT11的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;
4、I2C2配置:作为OLED的通讯方式;
5、UART1和UART3配置:MCU分别与电脑和ESP8266通讯(记得开启串口通信中断);
6、设置KEY0按键PE4为外部中断(根据自己的开发板来确定)
7、GPIO配置:PE0设置为DHT11的DATA端,PE5为LED,并且设置ESP8266的EN和RST(PB7和PB9);
8、时钟树配置
五、代码与解析
5.1 OLED与DHT11模块代码
受篇幅限制OLED与DHT11部分的代码,这里就不展示了。如果有不懂这部分原理与代码的读者朋友可以参考本人的另一篇博客。博客地址:基于stm32的太空人温湿度时钟项目——DHT11(HAL库)_混分巨兽龙某某的博客-CSDN博客
5.2 ESP8266模块代码
ESP8266部分的代码主要是借助串口通讯AT指令与ESP8266模块(刷入AT固件的)与OneNet平台进行信息交互(包含ESP8266初始化、数据发送,指令发送和数据缓存清除等)。
esp8266.h代码:
#ifndef _ESP8266_H_
#define _ESP8266_H_
#include "main.h"
#include "usart.h"
#include<string.h>
#include<stdio.h>
#include<stdbool.h>
#define ESP8266_WIFI_INFO "AT+CWJAP=\"NJUST\",\"768541ly\"\r\n" //连接上自己的wifi热点:WiFi名和密码
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n" //连接上OneNet的MQTT
#define OK 0 //接收完成标志
#define OUTTIME 1 //接收未完成标志
void ESP8266_Clear(void); //清空缓存
void ESP8266_Init(void); //esp8266初始化
_Bool ESP8266_SendCmd(char *cmd, char *res);//发送数据
unsigned char *ESP8266_GetIPD(unsigned short timeOut);
void ESP8266_SendData(unsigned char *data, unsigned short len);
#endif
esp8266.c代码:
#include "esp8266.h"
unsigned char ESP8266_Buf[128]; //定义一个数组作为esp8266的数据缓冲区
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0; //定义两个计数值:此次和上一次
unsigned char a_esp8266_buf;
/**
* @brief esp8266初始化
* @param 无
* @retval 无
*/
void ESP8266_Init(void)
{
ESP8266_Clear();
printf("1. 测试AT启动\r\n"); //AT:测试AT启动
while(ESP8266_SendCmd("AT\r\n", "OK"))
HAL_Delay(500);
printf("2. 设置WiFi模式(CWMODE)\r\n"); //查询/设置 Wi-Fi 模式:设置WiFi模式为Station模式
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
HAL_Delay(500);
printf("3. AT+CWDHCP\r\n"); //启用/禁用 DHCP
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
HAL_Delay(500);
printf("4. 连接WiFi热点(CWJAP)\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
HAL_Delay(500);
printf("5. 建立TCP连接(CIPSTART)\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
HAL_Delay(500);
printf("6. ESP8266 Init OK\r\n");
}
/**
* @brief 清空缓存
* @param 无
* @retval 无
*/
void ESP8266_Clear(void)
{
memset(ESP8266_Buf, 0, sizeof(ESP8266_Buf)); //将数组中的元素全部初始化为0,
}
/**
* @brief 等待接收完成
* @param 无
* @retval OK:表示接收完成;OUTTIME:表示接收超时完成
* 进行循环调用,检测接收是否完成
*/
_Bool ESP8266_WaitRecive(void)
{
if(esp8266_cnt == 0) //如果当前接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
return OUTTIME;
if(esp8266_cnt == esp8266_cntPre) //如果上一次的值和这次相同,则说明接收完毕
{
esp8266_cnt = 0; //清0接收计数
return OK; //返回接收完成标志
}
else //如果不相同,则将此次赋值给上一次,并返回接收未完成标志
{
esp8266_cntPre = esp8266_cnt;
return OUTTIME;
}
}
/**
* @brief 发送命令
* @param cmd:表示命令;res:需要检查的返回指令
* @retval 0:表示成功;1:表示失败
*/
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
HAL_UART_Transmit(&huart3, (unsigned char *)cmd, strlen((const char *)cmd),0xffff);
while(timeOut--)
{
if(ESP8266_WaitRecive() == OK) //如果收到数据
{
printf("%s",ESP8266_Buf);
if(strstr((const char *)ESP8266_Buf, res) != NULL) //如果检索到关键词,清空缓存
{
ESP8266_Clear();
return 0;
}
}
HAL_Delay(10);
}
return 1;
}
/**
* @brief 数据发送
* @param data:待发送的数据;len:待发送的数据长度
* @retval 无
*/
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收缓存
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令,sprintf()函数用于将格式化的数据写入字符串
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’时可以发送数据
{
HAL_UART_Transmit(&huart3, data, len,0xffff); //发送设备连接请求数据
}
}
/**
* @brief 获取平台返回的数据
* @param 等待的时间
* @retval 平台返回的数据,不同网络设备返回的格式不同,需要进行调试,如:ESP8266的返回格式为:"+IPD,x:yyy",x表示数据长度,yyy表示数据内容
*/
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{
char *ptrIPD = NULL;
do
{
if(ESP8266_WaitRecive() == OK) //如果接收完成
{
ptrIPD = strstr((char *)ESP8266_Buf, "IPD,"); //搜索“IPD”头
if(ptrIPD == NULL) //如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
{
//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
}
else
{
ptrIPD = strchr(ptrIPD, ':'); //找到':'
if(ptrIPD != NULL)
{
ptrIPD++;
return (unsigned char *)(ptrIPD);
}
else
return NULL;
}
}
HAL_Delay(5); //延时等待
} while(timeOut--);
return NULL; //超时还未找到,返回空指针
}
/**
* @brief 串口2收发中断回调函数
* @param
* @retval
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(esp8266_cnt >= 255) //溢出判断,超过一个字节
{
esp8266_cnt = 0;
memset(ESP8266_Buf,0x00,sizeof(ESP8266_Buf));
HAL_UART_Transmit(&huart3, (uint8_t *)"数据溢出", 10,0xFFFF);
}
else
{
ESP8266_Buf[esp8266_cnt++] = a_esp8266_buf; //接收数据转存
}
HAL_UART_Receive_IT(&huart3, (uint8_t *)&a_esp8266_buf, 1); //再开启接收中断
}
代码总结:
ESP8266模块的代码基于HAL库实现,主要是利用AT指令去使下位机(STM32+ESP8266)连接上WIFI,并且与OneNet平台进行MQTT协议通信(TCP连接IP地址和对应端口)。
特别注意:
使用ESP8266进行通讯时,当数据量较大的时候一定要编写缓存清除代码(否则,很有可能出现死机等情况)。当然,这个时候可以搭配**SD NAND(贴片式TF卡)**去存储传输的数据流。同时,利用这些保存在SD卡中的数据,可以在下位机制作精美的数据历史信息UI,极大的拓展了产品价值。
5.3 OneNet与Cjson代码
OneNet部分的代码就是实现MQTT协议去传输数据流给OneNet平台,并且订阅上位机发送的Topic主题,利用Cjson代码去解析收到的数据信息,根据上位机发送Topic主题对应的数据控制下位机MCU实现操作(这里订阅的主题为{“LED_SW”},LED控制主题,各位可以根据自己的情况改动)。
onenet.c代码:
#include "onenet.h"
#include "dht11.h"
#include <string.h>
#include <stdio.h>
//CJSON库
#include "cJSON.h"
#define PROID "549063" //产品ID
#define AUTH_INFO "environment" //鉴权信息
#define DEVID "1004695102" //设备ID
extern unsigned char esp8266_buf[128];
//float sht20_info_tempreture = 12;
//float sht20_info_humidity = 15;
extern int tempreture;
extern int humidity;
//==========================================================
// 函数名称: OneNet_DevLink
//
// 函数功能: 与onenet创建连接
//
// 入口参数: 无
//
// 返回参数: 1-成功 0-失败
//
// 说明: 与onenet平台建立连接
//==========================================================
_Bool OneNet_DevLink(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包,协议类型初始化
unsigned char *dataPtr;
_Bool status = 1;
printf("OneNet_DevLink\r\n"
"PROID: %s, AUIF: %s, DEVID:%s\r\n"
, PROID, AUTH_INFO, DEVID);
if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传平台
da