本文将使用STM32单片机以及 wifi模块实现实现单片机上云端数据传输的效果。本文主要采用STM32F103c8t6芯片、ESP8266-01S wifi模块以及DHT11模块实现上述功能。
一、DHT11初始化以及数据采集
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。
初始化需对DHT11的GPIO口进行初始化,如下所示:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
数据传输时定义了一个数据结构DHT11_Data_TypeDef;
typedef struct
{
uint8_t humi_int; //湿度的整数部分
uint8_t humi_deci; //湿度的小数部分
uint8_t temp_int; //温度的整数部分
uint8_t temp_deci; //温度的小数部分
uint8_t check_sum; //校验和
} DHT11_Data_TypeDef;
然后编写DHT11_Read_TempAndHumidity(数据采集函数)
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
/*输出模式*/
DHT11_Mode_Out_PP();
/*主机拉低*/
DHT11_Dout_0;
/*延时18ms*/
DHT11_DELAY_MS(18);
/*总线拉高 主机延时30us*/
DHT11_Dout_1;
DHT11_DELAY_US(30); //延时30us
/*主机设为输入 判断从机响应信号*/
DHT11_Mode_IPU();
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DHT11_Dout_IN()==Bit_RESET)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(DHT11_Dout_IN()==Bit_RESET);
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(DHT11_Dout_IN()==Bit_SET);
/*开始接收数据*/
DHT11_Data->humi_int= DHT11_ReadByte();
DHT11_Data->humi_deci= DHT11_ReadByte();
DHT11_Data->temp_int= DHT11_ReadByte();
DHT11_Data->temp_deci= DHT11_ReadByte();
DHT11_Data->check_sum= DHT11_ReadByte();
/*读取结束,引脚改为输出模式*/
DHT11_Mode_Out_PP();
/*主机拉高*/
DHT11_Dout_1;
/*检查读取的数据是否正确*/
if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)
return SUCCESS;
else
return ERROR;
}
else
return ERROR;
}
数据传输时只需要将定义好的DHT11_Data_TypeDef传输给此函数就能采集到要采集的温湿度数据。
一、ESP8266初始化以及数据采集ESP8266是一款嵌入式系统级芯片,它集成了Wi-Fi功能和微控制器能力于一身,常用于物联网(IoT)项目中。这款芯片支持TCP/IP协议栈,能够连接到WiFi网络,并通过AT命令或者更高级的API与主控设备进行通信。它的低功耗特性使得它可以长时间运行在电池供电下,广泛应用于智能家居、智能门锁、无线传感器网络等应用中。
烧录基础AT指令,连接WIFI,以及连接云台
#define ESP8266_WIFI_INFO "AT+CWJAP=\"wifi name\",\"password\"\r\n"
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"broker.emqx.io\",1883\r\n" //域名以及端口号
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");
while(ESP8266_SendCmd("AT+RST\r\n", ""))
delay_ms(500);
while(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. AT+CIPMUX\r\n");
while(ESP8266_SendCmd("AT+CIPMUX=0\r\n", "OK"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "6. CIPSTART\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
delay_ms(500);
UsartPrintf(USART_DEBUG, "7. ESP8266 Init OK\r\n");
}
程序烧录成功之后会串口提示如下代表烧录成功
三、cJSON语句
JSON 全称 JavaScript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。
它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。
本文只使用其最简单的格式,如下所示:
{
"name": "mculover666",
"age": 22,
"weight": 55.5,
}
cJSON语句的本文主要使用其对将要发送给MQTT服务器的数据进行统一打包,以便将数据的名称和值进行绑定,相互连接。
四、MQTT服务器MQTT协议全称是(Message Queuing Telemetry Transport),即消息队列遥测传输协议。
是一种基于发布/订阅(Publish/Subscribe)模式的轻量级通讯协议,并且该协议构建于TCP/IP协议之上,我们知道TCP协议本身就具有高可靠性的特点,因此基于其上的MQTT协议同样也是具有高可靠、低开销的特点,之所以低开销,是以为MQTT协议传输的最小的报文也只有两个字节。
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)
payload,可以理解为消息的内容,是指订阅者具体要使用的内容
想要连接云平台首先需要mqtt协议文件(mqttkit.c,mqttkit.h)导入工程文件。并且需要编写根据不同协议文件来封装协议相关的内容即相关发布文件以及接收文件
//=======================================================
// 函数名称: 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_LEVEL0, 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);
/*-------------------------------------------------不需要回复----------------------------------------------------------*/
// if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0) //命令回复组包
// {
UsartPrintf(USART_DEBUG, "Tips: Send CmdResp\r\n");
// ESP8266_SendData(mqttPacket._data, mqttPacket._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格式解析
// UsartPrintf(USART_DEBUG,"req_payload: %s\n",req_payload); //测试用
json = cJSON_Parse(req_payload);
// UsartPrintf(USART_DEBUG,"json: %d\n",json); //测试用
if(!json)UsartPrintf(USART_DEBUG,"Error before: [%s]\n",cJSON_GetErrorPtr());
else
{
json_Value = cJSON_GetObjectItem(json,"LED_SW");
UsartPrintf(USART_DEBUG,"json_Value: %d\n",json_Value);
if(json_Value->valueint) //json_Value>0 且为整形
{
LED2_ON; //开灯
UsartPrintf(USART_DEBUG,"json_Value: %s:%d\n",json_Value->string,json_Value->valueint);
UsartPrintf(USART_DEBUG,"LED2已打开\n");
}
else
{
LED2_OFF; //关灯
}
}
cJSON_Delete(json);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
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);
}
}
五、MQTT服务器主(MAIN)程序编写主程序主要是将想要发送的数据打包成相应格式然后将其上传至MQTT服务器,然后将MQTT服务器上的数据进行读取判断。
/*MAIN函数*/
int main(void)
{
unsigned short timeCount = 0; //发送间隔变量
unsigned char *dataPtr = NULL;
System_Init();
while(1)
{
OLED_Refresh();
Show_OLED_USART();
if(++timeCount >= 80) {
UsartPrintf(USART_DEBUG, "OneNet_Publish\r\n");
sprintf(Pub_Buf,"{\"Temp\":%d.%d,\"Hum\":%d.%d}",DHT11_Data.temp_int ,DHT11_Data.temp_deci ,DHT11_Data.humi_int ,DHT11_Data.humi_deci);
OneNet_Publish(devPubTopic, Pub_Buf);
timeCount = 0;
ESP8266_Clear();
}
dataPtr = ESP8266_GetIPD(3);
if(dataPtr != NULL)
OneNet_RevPro(dataPtr);
delay_ms(10);
}
}
程序运行成功如下图所示。