本项目芯片使用STM32F103ZET6,微信小程序开发使用微信开发者工具。
stm32作为下位机,功能是每过一段时间上传温湿度以及光照度给mqtt服务器,然后微信小程序从mqtt服务器订阅对应的主题来接收下位机发过来的数据并进行处理,并在微信小程序中设置LED开关,以及蜂鸣器开关。
如图,汽车上有联网芯片,可以充当客户端,汽车采集到的数据可以发送到mqtt服务器,然后再搭建其他mqtt客户端(电脑,手机等设备),这些设备就可以从mqtt服务器获取汽车的数据。
这里的单片机和微信小程序都是客服端,他们连接上同一个服务器,然后通过订阅/发布对应的主题,来进行信息的交互。
主题:
用来区别接收的数据。比如汽车采集的速度,要传给mqtt服务器,就要给数据打上一个标签,即主题,然后其他mqtt客户端可以根据主题来获取数据。
发布:
客服端发布数据给服务端,并标上主题。
订阅:
客户端订阅主题,然后获取主题对应的数据。
每一个客户端都可以订阅/发布数据,即数据可以互相发送。
main.c
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "beep.h"
#include "adc.h"
#include "lsens.h"
#include "esp8266.h"
#include "MqttKit.h"
#include "onenet.h"
uint8_t Usart_String[50];
uint8_t * dataPtr;
uint8_t tem_light_string[50];
const char *subtopics[] = {"/iot/2462/sub/wsy"};//订阅主题
const char pubtopics[] = {"/iot/2462/pub/wsy"};//发布主题
void dht11_exit()//判断dht11是否存在
{
while(DHT11_Init()) //DHT11初始化
{
LCD_ShowString(30,130,200,16,16,"DHT11 Error");
delay_ms(200);
LCD_Fill(30,130,239,130+16,WHITE);
delay_ms(200);
}
}
void Hardware_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断控制器分组设置
Usart1_Init(115200); //串口1,打印信息用
Usart2_Init(115200); //串口2,驱动ESP8266用
BEEP_Init(); //蜂鸣器初始化
UsartPrintf(USART_DEBUG, " Hardware init OK\r\n");
}
int main(void)
{
u8 timeCount=0;
u8 temperature; //存放温度的值
u8 humidity; //存放湿度的值
u8 adcx;//存放光照值
LED_Init(); //LED端口初始化
delay_init(); //延时函数初始化
Hardware_Init();
LCD_Init(); //LCD初始化
LCD_Clear(WHITE);
Lsens_Init(); //初始化光敏传感器
LCD_ShowString(30,130,200,16,16,"DHT11 OK");
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(30,150,200,16,16,"Temp: C");
LCD_ShowString(30,170,200,16,16,"Humi: %");
LCD_ShowString(30,190,200,16,16,"LSENS_VAL:");
dht11_exit();
ESP8266_Init();
while(OneNet_DevLink()) //接入OneNET
delay_ms(500);
// BEEP=1; //鸣叫提示接入成功
// delay_ms(250);
// BEEP=0;
OneNet_Subscribe(subtopics, 1);
while(1)
{
if(timeCount%40==0) //每100ms读取一次
{
DHT11_Read_Data(&temperature,&humidity); //读取温湿度值
adcx=Lsens_Get_Val(); //读取光照强度
LCD_ShowNum(30+40,150,temperature,2,16); //显示温度
LCD_ShowNum(30+40,170,humidity,2,16); //显示湿度
LCD_ShowNum(30+10*8,190,adcx,3,16);//显示光照度
}
if(++timeCount >= 200) //发送间隔5s 5000ms/25ms=200
{
UsartPrintf(USART_DEBUG, "OneNet_Publish\r\n");
// OneNet_Publish((char *)pubtopics, "MQTT Publish Test");
sprintf((char *)tem_light_string,"{\"tem\":%02d,\"hum\":%02d,\"light\":%d}",temperature,humidity,adcx);
OneNet_Publish((char *)pubtopics, tem_light_string);
timeCount = 0;
ESP8266_Clear();
}
dataPtr = ESP8266_GetIPD(3);
if(dataPtr != NULL)
OneNet_RevPro(dataPtr);
delay_ms(10);
}
}
这里移植了Onenet平台的stm32芯片esp8266连接服务器的代码。
有需要的小伙伴可进入连接获取添加链接描述
esp8266.c
/**
************************************************************
************************************************************
************************************************************
* 文件名: esp8266.c
*
* 作者: 张继瑞
*
* 日期: 2017-05-08
*
* 版本: V1.0
*
* 说明: ESP8266的简单驱动
*
* 修改记录:
************************************************************
************************************************************
************************************************************
**/
//单片机头文件
#include "stm32f10x.h"
//网络设备驱动
#include "esp8266.h"
//硬件驱动
#include "delay.h"
#include "usart.h"
//C库
#include
#include
#define ESP8266_WIFI_INFO "AT+CWJAP=\"xxx\",\"xxxxxx\"\r\n"
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"xxxxxxx\",xxxxx\r\n"
unsigned char esp8266_buf[128];
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;
//==========================================================
// 函数名称: ESP8266_Clear
//
// 函数功能: 清空缓存
//
// 入口参数: 无
//
// 返回参数: 无
//
// 说明:
//==========================================================
void ESP8266_Clear(void)
{
memset(esp8266_buf, 0, sizeof(esp8266_buf));
esp8266_cnt = 0;
}
//==========================================================
// 函数名称: ESP8266_WaitRecive
//
// 函数功能: 等待接收完成
//
// 入口参数: 无
//
// 返回参数: REV_OK-接收完成 REV_WAIT-接收超时未完成
//
// 说明: 循环调用检测是否接收完成
//==========================================================
_Bool ESP8266_WaitRecive(void)
{
if(esp8266_cnt == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
return REV_WAIT;
if(esp8266_cnt == esp8266_cntPre) //如果上一次的值和这次相同,则说明接收完毕
{
esp8266_cnt = 0; //清0接收计数
return REV_OK; //返回接收完成标志
}
esp8266_cntPre = esp8266_cnt; //置为相同
return REV_WAIT; //返回接收未完成标志
}
//==========================================================
// 函数名称: ESP8266_SendCmd
//
// 函数功能: 发送命令
//
// 入口参数: cmd:命令
// res:需要检查的返回指令
//
// 返回参数: 0-成功 1-失败
//
// 说明:
//==========================================================
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
while(timeOut--)
{
if(ESP8266_WaitRecive() == REV_OK) //如果收到数据
{
if(strstr((const char *)esp8266_buf, res) != NULL) //如果检索到关键词
{
ESP8266_Clear(); //清空缓存
return 0;
}
}
delay_ms(10);
}
return 1;
}
//==========================================================
// 函数名称: ESP8266_SendData
//
// 函数功能: 发送数据
//
// 入口参数: data:数据
// len:长度
//
// 返回参数: 无
//
// 说明:
//==========================================================
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收缓存
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’时可以发送数据
{
Usart_SendString(USART2, data, len); //发送设备连接请求数据
}
}
//==========================================================
// 函数名称: ESP8266_GetIPD
//
// 函数功能: 获取平台返回的数据
//
// 入口参数: 等待的时间(乘以10ms)
//
// 返回参数: 平台返回的原始数据
//
// 说明: 不同网络设备返回的格式不同,需要去调试
// 如ESP8266的返回格式为 "+IPD,x:yyy" x代表数据长度,yyy是数据内容
//==========================================================
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{
char *ptrIPD = NULL;
do
{
if(ESP8266_WaitRecive() == REV_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;
}
}
delay_ms(5); //延时等待
} while(timeOut--);
return NULL; //超时还未找到,返回空指针
}
//==========================================================
// 函数名称: ESP8266_Init
//
// 函数功能: 初始化ESP8266
//
// 入口参数: 无
//
// 返回参数: 无
//
// 说明:
//==========================================================
void ESP8266_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//ESP8266复位引脚
GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initure.GPIO_Pin = GPIO_Pin_14; //GPIOC14-复位
GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_Initure);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET);
delay_ms(250);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET);
delay_ms(500);
ESP8266_Clear();
UsartPrintf(USART_DEBUG, "0. AT\r\n");
while(ESP8266_SendCmd("AT\r\n", "OK"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "1. RST\r\n");
ESP8266_SendCmd("AT+RST\r\n", "");
delay_ms(500);
ESP8266_SendCmd("AT+CIPCLOSE\r\n", "");
delay_ms(500);
UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "5. CIPSTART\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "6. ESP8266 Init OK\r\n");
}
//==========================================================
// 函数名称: USART2_IRQHandler
//
// 函数功能: 串口2收发中断
//
// 入口参数: 无
//
// 返回参数: 无
//
// 说明:
//==========================================================
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
{
if(esp8266_cnt >= sizeof(esp8266_buf)) esp8266_cnt = 0; //防止串口被刷爆
esp8266_buf[esp8266_cnt++] = USART2->DR;
USART_ClearFlag(USART2, USART_FLAG_RXNE);
}
}
#define ESP8266_WIFI_INFO “AT+CWJAP=“xxx”,“xxxxxx”\r\n” //填入你要连接的wifi名字和密码
#define ESP8266_ONENET_INFO “AT+CIPSTART=“TCP”,“xxxxxxx”,xxxxx\r\n”//填入你要连接的服务器域名和端口号
onenet.c
/**
************************************************************
************************************************************
************************************************************
* 文件名: onenet.c
*
* 作者: 张继瑞
*
* 日期: 2017-05-08
*
* 版本: V1.1
*
* 说明: 与onenet平台的数据交互接口层
*
* 修改记录: V1.0:协议封装、返回判断都在同一个文件,并且不同协议接口不同。
* V1.1:提供统一接口供应用层使用,根据不同协议文件来封装协议相关的内容。
************************************************************
************************************************************
************************************************************
**/
//单片机头文件
#include "stm32f10x.h"
//网络设备
#include "esp8266.h"
//协议文件
#include "onenet.h"
#include "mqttkit.h"
//硬件驱动
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
//C库
#include
#include
#include "cJSON.h"
#define PROID "0a15a6464f0fee359eadb8992ebb72d7"//设备号ID
#define AUTH_INFO "123456"//密码
#define DEVID "11"//随便写
extern unsigned char esp8266_buf[128];
//==========================================================
// 函数名称: OneNet_DevLink
//
// 函数功能: 与onenet创建连接
//
// 入口参数: 无
//
// 返回参数: 1-成功 0-失败
//
// 说明: 与onenet平台建立连接
//==========================================================
_Bool OneNet_DevLink(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
unsigned char *dataPtr;
_Bool status = 1;
UsartPrintf(USART_DEBUG, "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); //上传平台
dataPtr = ESP8266_GetIPD(250); //等待平台响应
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:UsartPrintf(USART_DEBUG, "Tips: 连接成功\r\n");status = 0;break;
case 1:UsartPrintf(USART_DEBUG, "WARN: 连接失败:协议错误\r\n");break;
case 2:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法的clientid\r\n");break;
case 3:UsartPrintf(USART_DEBUG, "WARN: 连接失败:服务器失败\r\n");break;
case 4:UsartPrintf(USART_DEBUG, "WARN: 连接失败:用户名或密码错误\r\n");break;
case 5:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法链接(比如token非法)\r\n");break;
default:UsartPrintf(USART_DEBUG, "ERR: 连接失败:未知错误\r\n");break;
}
}
}
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: MQTT_PacketConnect Failed\r\n");
return status;
}
//==========================================================
// 函数名称: OneNet_Subscribe
//
// 函数功能: 订阅
//
// 入口参数: topics:订阅的topic
// topic_cnt:topic个数
//
// 返回参数: SEND_TYPE_OK-成功 SEND_TYPE_SUBSCRIBE-需要重发
//
// 说明:
//==========================================================
void OneNet_Subscribe(const char *topics[], unsigned char topic_cnt)
{
unsigned char i = 0;
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
for(; i < topic_cnt; i++)
UsartPrintf(USART_DEBUG, "Subscribe Topic: %s\r\n", topics[i]);
if(MQTT_PacketSubscribe(MQTT_SUBSCRIBE_ID, MQTT_QOS_LEVEL2, topics, topic_cnt, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //向平台发送订阅请求
MQTT_DeleteBuffer(&mqttPacket); //删包
}
}
//==========================================================
// 函数名称: OneNet_Publish
//
// 函数功能: 发布消息
//
// 入口参数: topic:发布的主题
// msg:消息内容
//
// 返回参数: SEND_TYPE_OK-成功 SEND_TYPE_PUBLISH-需要重送
//
// 说明:
//==========================================================
void OneNet_Publish(const char *topic, const char *msg)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
UsartPrintf(USART_DEBUG, "Publish Topic: %s, Msg: %s\r\n", topic, msg);
if(MQTT_PacketPublish(MQTT_PUBLISH_ID, topic, msg, strlen(msg), MQTT_QOS_LEVEL2, 0, 1, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //向平台发送订阅请求
MQTT_DeleteBuffer(&mqttPacket); //删包
}
}
//==========================================================
// 函数名称: OneNet_RevPro
//
// 函数功能: 平台返回数据检测
//
// 入口参数: dataPtr:平台返回的数据
//
// 返回参数: 无
//
// 说明:
//==========================================================
void OneNet_RevPro(unsigned char *cmd)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char *req_payload = NULL;
char *cmdid_topic = NULL;
unsigned short topic_len = 0;
unsigned short req_len = 0;
unsigned char type = 0;
unsigned char qos = 0;
static unsigned short pkt_id = 0;
short result = 0;
char *dataPtr = NULL;
char numBuf[10];
int num = 0;
cJSON * json,*json_value;
type = MQTT_UnPacketRecv(cmd);
switch(type)
{
case MQTT_PKT_CMD: //命令下发
result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); //解出topic和消息体
if(result == 0)
{
UsartPrintf(USART_DEBUG, "cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
break;
case MQTT_PKT_PUBLISH: //接收的Publish消息
result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
if(result == 0)
{
UsartPrintf(USART_DEBUG, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
cmdid_topic, topic_len, req_payload, req_len);
//对数据包进行JSON格式解析
json = cJSON_Parse(req_payload);
if(!json)UsartPrintf(USART_DEBUG,"Error before: [%s]\n",cJSON_GetErrorPtr());
else
{
// //解析开关值
// json_value = cJSON_GetObjectItem(json,"LED_SW");
// if(json_value -> valueint)//json_value大于等于0,且为整型
// {
// LED0= 0;//打开LED0
// }
// else
// {
// LED0= 1;//关闭LED0
// }
//解析开关值
json_value=cJSON_GetObjectItem(json,"target");
UsartPrintf(USART_DEBUG,"json_value= %d\r\n",json_value->string);//先把键值输出来,就是看看叫什么名字
UsartPrintf(USART_DEBUG,"json_value= %d\r\n",json_value->valuestring);//接着看看数值是多少
//从value-int中获取结果
if(strstr(json_value->valuestring,"led")!=NULL)//标准字符串判断,这个就是判断是不是控制LED的,如果是,就是控制LED,如果不是就是控制蜂鸣器
{
json_value=cJSON_GetObjectItem(json,"value");
if(json_value->valueint)
LED1=0;
else
LED1=1;
}
else
{
json_value=cJSON_GetObjectItem(json,"value");
if(json_value->valueint)
BEEP=1;//蜂鸣器鸣叫
else
BEEP=0;//蜂鸣器停止鸣叫
}
}
cJSON_Delete(json);
switch(qos)
{
case 1: //收到publish的qos为1,设备需要回复Ack
if(MQTT_PacketPublishAck(pkt_id, &mqttPacket) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Send PublishAck\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
break;
case 2: //收到publish的qos为2,设备先回复Rec
//平台回复Rel,设备再回复Comp
if(MQTT_PacketPublishRec(pkt_id, &mqttPacket) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Send PublishRec\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
break;
default:
break;
}
}
break;
case MQTT_PKT_PUBACK: //发送Publish消息,平台回复的Ack
if(MQTT_UnPacketPublishAck(cmd) == 0)
UsartPrintf(USART_DEBUG, "Tips: MQTT Publish Send OK\r\n");
break;
case MQTT_PKT_PUBREC: //发送Publish消息,平台回复的Rec,设备需回复Rel消息
if(MQTT_UnPacketPublishRec(cmd) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Rev PublishRec\r\n");
if(MQTT_PacketPublishRel(MQTT_PUBLISH_ID, &mqttPacket) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Send PublishRel\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
}
break;
case MQTT_PKT_PUBREL: //收到Publish消息,设备回复Rec后,平台回复的Rel,设备需再回复Comp
if(MQTT_UnPacketPublishRel(cmd, pkt_id) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Rev PublishRel\r\n");
if(MQTT_PacketPublishComp(MQTT_PUBLISH_ID, &mqttPacket) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Send PublishComp\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
}
break;
case MQTT_PKT_PUBCOMP: //发送Publish消息,平台返回Rec,设备回复Rel,平台再返回的Comp
if(MQTT_UnPacketPublishComp(cmd) == 0)
{
UsartPrintf(USART_DEBUG, "Tips: Rev PublishComp\r\n");
}
break;
case MQTT_PKT_SUBACK: //发送Subscribe消息的Ack
if(MQTT_UnPacketSubscribe(cmd) == 0)
UsartPrintf(USART_DEBUG, "Tips: MQTT Subscribe OK\r\n");
else
UsartPrintf(USART_DEBUG, "Tips: MQTT Subscribe Err\r\n");
break;
case MQTT_PKT_UNSUBACK: //发送UnSubscribe消息的Ack
if(MQTT_UnPacketUnSubscribe(cmd) == 0)
UsartPrintf(USART_DEBUG, "Tips: MQTT UnSubscribe OK\r\n");
else
UsartPrintf(USART_DEBUG, "Tips: MQTT UnSubscribe Err\r\n");
break;
default:
result = -1;
break;
}
ESP8266_Clear(); //清空缓存
if(result == -1)
return;
dataPtr = strchr(req_payload, '}'); //搜索'}'
if(dataPtr != NULL && result != -1) //如果找到了
{
dataPtr++;
while(*dataPtr >= '0' && *dataPtr <= '9') //判断是否是下发的命令控制数据
{
numBuf[num++] = *dataPtr++;
}
num = atoi((const char *)numBuf); //转为数值形式
}
if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
{
MQTT_FreeBuffer(cmdid_topic);
MQTT_FreeBuffer(req_payload);
}
}
#define PROID “0a15a6464f0fee359eadb8992ebb72d7”//一些私人的服务器需要设备号ID
#define AUTH_INFO “123456”//密码
#define DEVID “11”//随便写,这里没有要求
1.微信开发者工具添加链接描述
2.mqtt.js插件 密码6666
3.onenet移植代码用来给下位机连接服务器使用