打印
[应用相关]

MQTT+ESP8266实现STM32数据上传

[复制链接]
3226|95
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
一、环境介绍
项目完整代码:
ESP8266+MQTT上传数据: ESP8266通过MQTT协议将温湿度数据传输至服务器
https://gitee.com/zmfjoker/esp8266_mqtt

硬件部分:

        单片机:用的STM32F103C8T6

        温度传感器:AHT21

        光敏传感器:GY-302

        WIFI模块:ESP-01S

        画了个简单的电路图用来测试





使用特权

评论回复
沙发
为你转身|  楼主 | 2022-2-28 23:16 | 只看该作者
二、I2C配置
1.I2C总线协议
温度和光照传感器都是通过I2C驱动的

I2C总线协议图

        I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态 时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备 将释放总线,总线再次处于空闲状态。

使用特权

评论回复
板凳
为你转身|  楼主 | 2022-2-28 23:18 | 只看该作者
2.硬件I2C和模拟I2C
        I2C有两种模式,直接使用硬件外设,还有就是通过GPIO模拟出I2C波形来控制。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

下面是使用硬件I2C传送给的序列图,具体说明可以去看STM32中文参考手册



主发送器传送序列图



主接收器传送序列图

本次测试采用的是硬件I2C的方式来驱动。

使用特权

评论回复
地板
为你转身|  楼主 | 2022-2-28 23:19 | 只看该作者
myiic.c

#include "myiic.h"
#include "stm32f10x.h"



/*********************************************
        函数名:I2C1外设初始化                     *
        参  数:无                                *
        返回值:无                                *
**********************************************/
void I2C1_Init(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;
        I2C_InitTypeDef  I2C_InitStructure;
       
        //开需要的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);      
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
       
        //设置IO口
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;        //选择端口号                     
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;                 //OD开漏输出      
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                //设置IO接口速度(2/10/50MHz)   
        GPIO_Init(GPIOB, &GPIO_InitStructure);
       
        //设置I2C参数
        I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;                                //设置为I2C模式
        I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;                //I2C占空比
        I2C_InitStructure.I2C_OwnAddress1 = STM32_I2C_OWN_ADDR;        //主机地址(从机不得用此地址)
        I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;                                //允许应答
        I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;         //7位地址模式
        I2C_InitStructure.I2C_ClockSpeed = 100000;                                //总线速度设置100kbps
        I2C_Init(I2C1,&I2C_InitStructure);
        I2C_Cmd(I2C1,ENABLE);                                                                        //开启I2C                                       
}


/**********************************************
        函数名:I2C1发送一连串数据                  *
        参  数:SlaveAddr从机地址                                    *
               WriteAddr寄存器地址                *
               pdata 数据指针                     *
               t_len pdata的数据长度              *
        返回值:无                                 *
***********************************************/
void I2C1_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr, u8* pdata, u16 t_len)
{
        //发送起始信号与地址(写)
        I2C_GenerateSTART(I2C1,ENABLE);                                                                        //产生起始信号
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));                //EV5,等待主机模式起始位发送完
        I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);        //发送器件地址,主机为写
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));        //EV6,等待主机发送模式地址发送完成       
        I2C_SendData(I2C1,WriteAddr);                                                                                        //内部功能地址或命令
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));                                //EV8,等待主机模式数据发送完成       
        //循环发送数据
        while(t_len--)
        {
                I2C_SendData(I2C1,*pdata);        //发送数据
                pdata++; //数据指针移位
                while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));                //EV8,等待主机模式数据发送完成
        }
        I2C_GenerateSTOP(I2C1,ENABLE);        //产生停止信号
}


/***********************************************
        函数名:I2C1读取一连串数据                   *
        参  数:SlaveAddr从机地址                   *
                readAddrCmd 读取的地址或命令         *
            pdata 存放读取数据的指针             *
                NumByteToRead 需要读取的数据的数量   *
        返回值:无                                  *
************************************************/
void I2C1_READ_BUFFER(u8 SlaveAddr, u8 readAddrCmd, u8* pdata, u16 NumByteToRead)
{
        //发送起始信号、地址(写)、从机寄存器地址或命令
        while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));        //等待总线空闲
        I2C_GenerateSTART(I2C1,ENABLE);                        //产生起始信号
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));                //EV5,等待主机模式起始位发送完
        I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送器件地址,主机为写
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));        //EV6,等待主机发送模式地址发送完成
//        I2C_Cmd(I2C1,ENABLE);                //????
        I2C_SendData(I2C1,readAddrCmd);        //发送读的从机寄存器地址或命令
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));                                //EV8,等待主机模式数据发送完成
       
        //发送起始信号、地址(读)
        I2C_GenerateSTART(I2C1,ENABLE); //开启信号
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));                //EV5,等待主机模式起始位发送完
        I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver);                //发送器件地址,主机为读
        while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));                 //EV6,等待主机接收模式地址发送完成
       
        //有数据没接收完
        while(NumByteToRead)
        {
                if(NumByteToRead == 1)                //只剩下最后一个数据时进入 if 语句
                {
                        I2C_AcknowledgeConfig(I2C1,DISABLE);        //最后有一个数据时关闭应答位
                        I2C_GenerateSTOP(I2C1,ENABLE);                        //最后一个数据时发送一个停止位
                }
                if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED))        //EV7,主机接收到数据
                {
                        *pdata = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pdata
                        pdata++; //指针移位
                        NumByteToRead--; //字节数减 1
                }
        }
        I2C_AcknowledgeConfig(I2C1,ENABLE);                //接收完后重新开启应答位
}

使用特权

评论回复
5
为你转身|  楼主 | 2022-2-28 23:20 | 只看该作者
三、传感器配置
1.温度传感器AHT20
下面两张图片是AHT20产品规格书上截取的,讲述了AHT20读写的过程

使用特权

评论回复
6
为你转身|  楼主 | 2022-2-28 23:22 | 只看该作者
aht20.c
#include "aht20.h"
#include "delay.h"

/************************************
                                                                        *
        功能:读取AHT20状态字                         *
                                                                        *
*************************************/

u8 AHT20_ReadStatus(void)
{
        u8 tmp[1];
        u8 a;
        I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 1);
        a = tmp[0];
        return a;
}


/*******************************************
   读AHT20 设备状态字 中的Bit3: 校准使能位 *
                                                                                   *
   校准使能位:1 - 已校准; 0 - 未校准           *
********************************************/
uint8_t AHT20_ReadStatus_bit3(void)
{
        uint8_t tmp;
        tmp = AHT20_ReadStatus();
        return (tmp>>3)&0x01;
}



/************************
    芯片初始化命令                *
                                                *
                                                *
*************************/
void AHT20_IcInitCmd(void)
{
        uint8_t tmp[2];
        tmp[0] = 0x08;
        tmp[1] = 0x00;
        I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS, 0xbe, tmp, 2);
}
/************************
    AHT20 软复位命令        *
                                                *
*************************/
void AHT20_SoftResetCmd(void)
{
        uint8_t tmp[1];
        I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xba, tmp, 0);
}




/************************************
                                                                        *
        功能:读取AHT20状态字bit7忙标志 *
        状态:1  设备忙;0   设备空闲   *
                                                                        *
*************************************/
u8 AHT20_ReadStatus_bit7(void)
{
        u8 tmp[1];
       
        I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 1);
       
        return (tmp[0]>>7)&0x01;
}

/**************************
                                                  *
        功能:发送AC触发命令  *
                                                  *
***************************/

void AHT20_AC_CMD(void)
{
        u8 tmp[2];
        tmp[0] = 0x33;
        tmp[1] = 0x00;
       
        I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xac, tmp, 2);
}


/***********************************
                                                                   *
        功能:重新初始化寄存器                   *
        入口参数:addr        寄存器地址           *
                                                                   *
************************************/
void JH_Reset_REG(uint8_t addr)
{
        uint8_t Byte_first,Byte_second,Byte_third;
        u8 tmp1[2];
        u8 tmp2[3];
        u8 tmp3[2];
        tmp1[0] = 0x00;
        tmp1[1] = 0x00;
       
        I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,addr, tmp1, 3);
       
        delay_ms(5);
       
        I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, addr, tmp2, 3);
        Byte_first = tmp2[0];
        Byte_second = tmp2[1];
        Byte_third = tmp2[2];
       
        delay_ms(10);
       
       
        tmp3[0] = Byte_second;
        tmp3[1] = Byte_third;
        I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xb0|addr, tmp3, 2);
       
        Byte_second=0x00;
        Byte_third =0x00;
       
}

void AHT20_Start_Init(void)
{
        JH_Reset_REG(0x1b);
        JH_Reset_REG(0x1c);
        JH_Reset_REG(0x1e);
}



/***********************************
                                                                   *
        功能:没有CRC校验直接读取温度  *
                                                                   *
************************************/


void AHT20_READ_CTdata(u32 *ct)
{
        volatile uint8_t  Byte_1th=0;
        volatile uint8_t  Byte_2th=0;
        volatile uint8_t  Byte_3th=0;
        volatile uint8_t  Byte_4th=0;
        volatile uint8_t  Byte_5th=0;
        volatile uint8_t  Byte_6th=0;
        uint32_t RetuData = 0;
        uint16_t cnt ;
        u8 tmp[6];
       
        AHT20_AC_CMD();                              //发送AC指令
       
        delay_ms(80);
       
        cnt = 5;
        //空闲状态等待
        while(AHT20_ReadStatus_bit7())
        {
                delay_ms(75);
                if(cnt--)
                {
                 break;
                 }
        }
        //读取六个字节
        I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 6);
       
        // 计算湿度
        RetuData = 0;   //第一个字节是状态字
        RetuData = (RetuData|tmp[1]) << 8;
        RetuData = (RetuData|tmp[2]) << 8;
        RetuData = (RetuData|tmp[3]);
        RetuData = RetuData >> 4;
        ct[0] = RetuData;
               
        // 计算温度
        RetuData = 0;
        RetuData = (RetuData|tmp[3]) << 8;
        RetuData = (RetuData|tmp[4]) << 8;
        RetuData = (RetuData|tmp[5]);
        RetuData = RetuData&0xfffff;
        ct[1] = RetuData;
}


这段代码是根据AHT20官方参考例程改的,官方的代码用的模拟I2C方式,这里改成了硬件方式

使用特权

评论回复
7
为你转身|  楼主 | 2022-2-28 23:23 | 只看该作者
aht20.h

#ifndef __AHT20_H
#define __AHT20_H
#include "sys.h"
#include "myiic.h"
#include "delay.h"

#define ATH20_SLAVE_ADDRESS                0x70        //I2C从机地址

//****************************************
// 定义 AHT20 内部地址
//****************************************
#define        AHT20_STATUS                        0x00        //状态字 寄存器地址
#define        AHT20_INIT_REG                        0xBE        //初始化 寄存器地址
#define        AHT20_SoftReset                        0xBA        //软复位 单指令
#define        AHT20_TrigMeasure_REG        0xAC        //触发测量 寄存器地址

u8 AHT20_ReadStatus(void);                                //读取状态字
uint8_t AHT20_ReadStatus_bit3(void);        //读取状态字bit[3],返回1 或 0
void AHT20_IcInitCmd(void);                                //芯片初始化0xbe
void AHT20_SoftResetCmd(void);                        //软复位0xba
u8 AHT20_ReadStatus_bit7(void);                        //读取状态子bit[7], 1-设备忙  0-设备空闲
void AHT20_AC_CMD(void);                                //发送0xac  触发测量

void JH_Reset_REG(uint8_t addr);                 //初始化寄存器
void AHT20_Start_Init(void);                        //初始化指令

void AHT20_READ_CTdata(u32 *ct);                //读取温度

#endif

使用特权

评论回复
8
为你转身|  楼主 | 2022-2-28 23:25 | 只看该作者
2.光照传感器
  light.c
#include "light.h"
#include "stm32f10x.h"
#include "myiic.h"
#include "delay.h"


/**************************
                                                  *
        功能:启动函数              *
                                                  *
***************************/
void Single_Write_BH1750(u8 add)
{
        u8 tmp[1];
       
        I2C1_SAND_BUFFER(SlaveAddress,add, tmp, 0);
       

}

/***********************************
                                                                   *
        功能:读取2个数据                       *
        参数:*data_light存储数据的数组*
                                                                   *
************************************/
void Multiple_read_BH1750(u8 *data_light)
{
        u8 tmp[2];
        u8 i;
        I2C1_READ_BUFFER(SlaveAddress,0x00, tmp, 2);
        for(i = 0;i<2;i++)
        {
                data_light[i] = tmp[i];
        }
       
       
}

/**************************
                                                  *
        功能:获取光照强度    *
                                                  *
***************************/
void read_light(u32* light)
{
        u8 buf[2];
        int dis_data;                       
        u32 temp1;
       
        Single_Write_BH1750(0x01);//发送上电命令
        Single_Write_BH1750(0x10);//发送高分辨率连续测量命令
        delay_ms(180);                          //根据手册要求这里要延迟180ms
        Multiple_read_BH1750(buf);//读出两个数据
        dis_data=buf[0];
        dis_data=(dis_data<<8)+buf[1]; //2个字节合成数据
        temp1=dis_data/1.2;                           //计算光照度
        *light = temp1;

}

使用特权

评论回复
9
为你转身|  楼主 | 2022-2-28 23:27 | 只看该作者
     这里只是简单写了下读取的代码,产品数据手册上还有一些测量分辨率的代码没写上,后期测试会加上去,这里获取光强度代码可以不用入口参数,直接给一个return返回值也是一样的。

使用特权

评论回复
10
为你转身|  楼主 | 2022-2-28 23:28 | 只看该作者
light.h

#ifndef __LIGHT_H
#define __LIGHT_H
#include "stm32f10x.h"


#define          SlaveAddress   0x46     //设备地址
void Single_Write_BH1750(u8 aa);
void Multiple_read_BH1750(u8 *data_light);
void conversion(u8 temp_data);
void read_light(u32 *light);    //获取光照强度

#endif

使用特权

评论回复
11
为你转身|  楼主 | 2022-2-28 23:28 | 只看该作者
3.传感器部分测试
两个传感器代码大致部分已经写完,还有一些数据处理还没写

下面是传感器做测试的主函数代码

main.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "myiic.h"
#include "aht20.h"
#include "light.h"



int main()
{
        u32 light_num;
        uint32_t CT_data[2];
       
        volatile int  c1,t1;
        delay_init();                      //延时功能初始化
        LED_Init();                                //LED初始化
        usart_Init(115200);                        //串口1初始化,连电脑串口调试,PA9-TX,PA10-RX
        I2C1_Init();                                //I2C1外设初始化,速度为100kbps
        GPIO_ResetBits(GPIOB,GPIO_Pin_0);
        while(1)
        {
        GPIO_ResetBits(GPIOB,GPIO_Pin_0);                        //点亮LED
        delay_ms(500);
        GPIO_SetBits(GPIOB,GPIO_Pin_0);                        //LED       
        read_light(&light_num);                                        //读取光照强度                       
        AHT20_READ_CTdata(CT_data);                                //读取温湿度
        c1 = CT_data[0]*100*10/1024/1024;                  //计算得到湿度值c1(放大了10倍)
        t1 = CT_data[1]*200*10/1024/1024-500;        //计算得到温度值t1(放大了10倍)
        printf("当前光照:%d\r\n", light_num);
        printf("当前湿度:%d%%\r\n",c1/10);
    printf("当前温度:%d.%d\r\n",t1/10,t1%10);
        }
}



调试串口输出的数据

使用特权

评论回复
12
为你转身|  楼主 | 2022-2-28 23:32 | 只看该作者
四、ESP8266配置
wifi.c


#include "wifi.h"
#include "stm32f10x.h"
#include "usart.h"
#include "usart2.h"
#include "delay.h"
#include "string.h"

char wifi_mode = 0;     //联网模式 0:SSID和密码写在程序里   
/************************************
                                                                        *
        功能:初始化wifi复位IO                    *
                                                                        *                                                                       
*************************************/

void WiFi_ResetIO_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;                    //定义一个设置IO端口参数的结构体
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA , ENABLE); //使能PA端口时钟
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;               //准备设置PA4
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //速率50Mhz
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;               //推挽输出方式
        GPIO_Init(GPIOA, &GPIO_InitStructure);                        //设置PA4
        GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);              //清除数据端口位

}

/************************************
                                                                        *
        功能:wifi发送指令                            *
        参数:cmd 要发的指令                        *
                  waittime 等待时间                        *
        返回值:0 正确 ;1 错误                        *
                                                                        *                                                                       
*************************************/

char WiFi_SendCmd(char *cmd, int waittime)
{
        USART2_RX_STA=0;                                                             //接收状态标记清零
        memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);                //清空串口2接收缓冲区
        usart2_printf("%s\r\n", cmd);                                        //发送指令
       
        while(waittime--)                                                                //等待倒计时
        {
                delay_ms(10);
                if(strstr(USART2_RX_BUF,"OK"))                                //找是否接收到 OK 表示指令发送成功
                {
                        break;
                }
        }
        if(waittime == 0)                                                                //超时返回1
        {
                return 1;
        }
        else
                return 0;                                                                        //未超时返回0
}



/************************************
                                                                        *
        功能:wifi复位                                       *
        参数:waittime 等待时间                        *
        返回值:0 正确;1 错误                        *
                                                                        *                                                                       
*************************************/
char WiFi_Reset(int waittime)
{
        GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);         //复位IO
        delay_ms(500);                                           //延时500ms
        GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);           //复位IO拉高电平       
        while(waittime--)                                                                         //等待超时时间到0
        {                                                
                delay_ms(100);                                          //延时100ms
                if(strstr(USART2_RX_BUF, "ready"))               //如果接收到ready表示复位成功
                        break;                                                                           //主动跳出while循环                                               
        }
        printf("\r\n");                                                 //串口1输出信息
        if(waittime == 0)                                                                         //超时返回1
        {
                return 1;
        }
        else
                return 0;                                                                                 //未超时返回0
}



/************************************
                                                                        *
        功能:wifi连网                                       *
        参数:waittime 等待时间                        *
        返回值:0 正确;1 错误                        *
                                                                        *                                                                       
*************************************/

char WiFi_JoinAP(int waittime)
{
        USART2_RX_STA=0;                                                             //接收状态标记清零
        memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);                //清空串口2接收缓冲区
        usart2_printf("AT+CWJAP=%s%s\r\n",WIFI,PASSWORLD);//发送指令
       
        while(waittime--)                                                                //等待倒计时
        {
                delay_ms(1000);
                if(strstr(USART2_RX_BUF,"OK"))                                //找是否接收到 OK 表示指令发送成功
                {
                        break;
                }
        }
        printf("\r\n%s\r\n", USART2_RX_BUF);
        printf("\r\n");                                            //串口1输出信息
        if(waittime == 0)                                                                //超时返回1
        {
                return 1;
        }
        else
                return 0;                                                                        //未超时返回0
}


/************************************
                                                                        *
        功能:连接TCP服务器,透传模式        *
        参数:waittime 等待时间                        *
        返回值:0 正确;其他 错误                *
                                                                        *                                                                       
*************************************/
char WiFi_Connect_Server(int waittime)
{       
        USART2_RX_STA=0;                                      //WiFi接收数据量变量清零                        
        memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);         //清空WiFi接收缓冲区   
        usart2_printf("AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", "192.168.0.127", 1883);//发送连接服务器指令
        while(waittime--)                                                                //等待超时与否
        {                           
                delay_ms(100);                                     //延时100ms       
                if(strstr(USART2_RX_BUF, "CONNECT"))          //如果接受到CONNECT表示连接成功
                        break;                                  //跳出while循环
                if(strstr(USART2_RX_BUF, "CLOSED"))           //如果接受到CLOSED表示服务器未开启
                        return 1;                               //服务器未开启返回1
                if(strstr(USART2_RX_BUF, "ALREADY CONNECTED"))//如果接受到ALREADY CONNECTED已经建立连接
                        return 2;                               //已经建立连接返回2
                printf("%d ", waittime);                   //串口1输出现在的超时时间  
        }
        printf("\r\n");                              //串口1输出信息
        if(waittime <= 0)return 3;                       //超时错误,返回3
        else                                            //连接成功,准备进入透传
        {
                printf("连接服务器成功,准备进入透传\r\n"); //串口1显示信息
                USART2_RX_STA = 0;                          //WiFi接收数据量变量清零                        
                memset(USART2_RX_BUF, 0, USART2_RXBUFF_SIZE);    //清空WiFi接收缓冲区     
                usart2_printf("AT+CIPSEND\r\n");               //发送进入透传指令
                while(waittime--)                                                         //等待超时与否
                {                           
                        delay_ms(100);                            //延时100ms       
                        if(strstr(USART2_RX_BUF, "\r\nOK\r\n\r\n>"))//如果成立表示进入透传成功
                                break;                                   //跳出while循环
                        printf("%d ", waittime);                //串口1输出现在的超时时间  
                }
                if(waittime <= 0)return 4;                      //透传超时错误,返回4       
        }
        return 0;                                             //成功返回0       
}
/*-------------------------------------------------*/
/*函数名:WiFi_Smartconfig                         */
/*参  数:timeout:超时时间(1s的倍数)            */
/*返回值:0:正确   其他:错误                     */
/*-------------------------------------------------*/
char WiFi_Smartconfig(int timeout)
{
       
        USART2_RX_STA=0;                                           //WiFi接收数据量变量清零                        
        memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);                     //清空WiFi接收缓冲区     
        while(timeout--)                                                                        //等待超时时间到0
        {                                          
                delay_ms(1000);                                                         //延时1s
                if(strstr(USART2_RX_BUF, "connected"))                               //如果串口接受到connected表示成功
                        break;                                                  //跳出while循环  
                printf("%d ", timeout);                                 //串口输出现在的超时时间  
        }       
        printf("\r\n");                                          //串口输出信息
        if(timeout <= 0)return 1;                                     //超时错误,返回1
        return 0;                                                   //正确返回0
}
/*-------------------------------------------------*/
/*函数名:等待加入路由器                           */
/*参  数:timeout:超时时间(1s的倍数)            */
/*返回值:0:正确   其他:错误                     */
/*-------------------------------------------------*/
char WiFi_WaitAP(int timeout)
{               
        while(timeout--){                               //等待超时时间到0
                delay_ms(1000);                                             //延时1s
                if(strstr(USART2_RX_BUF, "WIFI GOT IP"))         //如果接收到WIFI GOT IP表示成功
                        break;                                                                                                                            //主动跳出while循环
                printf("%d ", timeout);                     //串口输出现在的超时时间
        }
        printf("\r\n");                              //串口输出信息
        if(timeout <= 0)return 1;                         //如果timeout<=0,说明超时时间到了,也没能收到WIFI GOT IP,返回1
        return 0;                                       //正确,返回0
}

/***********************************
                                                                   *
        函数名:WiFi连接服务器         *
        参  数:无                     *
        返回值:0:正确   其他:错误   *
                                                                   *
************************************/
char WiFi_Connect_IoTServer(void)
{       
        printf("准备复位模块\r\n");                   //串口提示数据
        if(WiFi_Reset(50))                                                                //复位,100ms超时单位,总计5s超时时间
        {                             
                printf("复位失败,准备重启\r\n");              //返回非0值,进入if,串口提示数据
                return 1;                                   //返回1
        }else printf("复位成功\r\n");                 //串口提示数据
       
        printf("准备设置STA模式\r\n");                //串口提示数据
        if(WiFi_SendCmd("AT+CWMODE=1",50))//设置STA模式,100ms超时单位,总计5s超时时间
        {            
                printf("设置STA模式失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
                return 2;                                   //返回2
        }else printf("设置STA模式成功\r\n");          //串口提示数据
       
        if(wifi_mode==0) //如果联网模式=0:SSID和密码写在程序里
        {                              
                printf("准备取消自动连接\r\n");            //串口提示数据
                if(WiFi_SendCmd("AT+CWAUTOCONN=0",50))                 //取消自动连接,100ms超时单位,总计5s超时时间
                {      
                        printf("取消自动连接失败,准备重启\r\n"); //返回非0值,进入if,串口提示数据
                        return 3;                                  //返回3
                }else printf("取消自动连接成功\r\n");        //串口提示数据
                               
                printf("准备连接路由器\r\n");                //串口提示数据       
                if(WiFi_JoinAP(30))//连接路由器,1s超时单位,总计30s超时时间
                {                          
                        printf("连接路由器失败,准备重启\r\n");  //返回非0值,进入if,串口提示数据
                        return 4;                                 //返回4       
                }else printf("连接路由器成功\r\n");         //串口提示数据                       
        }
       
        printf("准备设置透传\r\n");                    //串口提示数据
        if(WiFi_SendCmd("AT+CIPMODE=1",50))                          //设置透传,100ms超时单位,总计5s超时时间
        {           
                printf("设置透传失败,准备重启\r\n");       //返回非0值,进入if,串口提示数据
                return 8;                                    //返回8
        }else printf("设置透传成功\r\n");              //串口提示数据
       
        printf("准备关闭多路连接\r\n");                //串口提示数据
        if(WiFi_SendCmd("AT+CIPMUX=0",50))                                  //关闭多路连接,100ms超时单位,总计5s超时时间
        {            
                printf("关闭多路连接失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
                return 9;                                    //返回9
        }else printf("关闭多路连接成功\r\n");          //串口提示数据
         
        printf("准备连接服务器\r\n");                  //串口提示数据
        if(WiFi_Connect_Server(100))                                       //连接服务器,100ms超时单位,总计10s超时时间
        {            
                printf("连接服务器失败,准备重启\r\n");     //返回非0值,进入if,串口提示数据
                return 10;                                   //返回10
        }else printf("连接服务器成功\r\n");            //串口提示数据       
        return 0;                                        //正确返回0
}


五、MQTT
1.MQTT协议介绍
        MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

使用特权

评论回复
13
为你转身|  楼主 | 2022-2-28 23:33 | 只看该作者
2.MQTT主题订阅
2.1.CONNECT——连接报文
         固定报头 + 可变报头 + 有效载荷




使用特权

评论回复
14
为你转身|  楼主 | 2022-2-28 23:35 | 只看该作者
byte1转换成16进制 :0x10

byte2是剩余长度,指的是报文后面字符总数 = 可变报头字符数 + 有效载荷字符数

可变报头


协议名由六个字节组成,转出相应16进制:00 04 4D 51 54 54


协议级别:04

连接标志:

        这里比较复杂,暂且设置为11000010

        转换成16进制:C2

使用特权

评论回复
15
为你转身|  楼主 | 2022-2-28 23:40 | 只看该作者
2.2.CONNACK——确认连接请求


向服务器发送CONNECT报文后,会返回相应码,返回00说明连接成功

CONNECT代码
/*********************************
    函数名:连接服务器报文         *
    参  数:无                    *
    返回值:无                    *
**********************************/
void MQTT_ConectPack(void)
{       
        int temp,Remaining_len;
       
        Fixed_len = 1;                                                        //连接报文中,固定报头长度暂时先=1
        Variable_len = 10;                                                    //连接报文中,可变报头长度=10
        Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len; //连接报文中,负载长度      
        Remaining_len = Variable_len + Payload_len;                           //剩余长度=可变报头长度+负载长度
       
        temp_buff[0]=0x10;                         //固定报头第1个字节 :固定0x01               
        do{                                        //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
                temp = Remaining_len%128;              //剩余长度取余128
                Remaining_len = Remaining_len/128;     //剩余长度取整128
                if(Remaining_len>0)                      
                        temp |= 0x80;                      //按协议要求位7置位         
                temp_buff[Fixed_len] = temp;           //剩余长度字节记录一个数据
                Fixed_len++;                               //固定报头总长度+1   
        }while(Remaining_len > 0);                 //如果Remaining_len>0的话,再次进入循环
       
        temp_buff[Fixed_len + 0] = 0x00;     //可变报头第1个字节 :固定0x00                   
        temp_buff[Fixed_len + 1] = 0x04;     //可变报头第2个字节 :固定0x04
        temp_buff[Fixed_len + 2] = 0x4D;         //可变报头第3个字节 :固定0x4D
        temp_buff[Fixed_len + 3] = 0x51;         //可变报头第4个字节 :固定0x51
        temp_buff[Fixed_len + 4] = 0x54;         //可变报头第5个字节 :固定0x54
        temp_buff[Fixed_len + 5] = 0x54;     //可变报头第6个字节 :固定0x54
        temp_buff[Fixed_len + 6] = 0x04;         //可变报头第7个字节 :固定0x04
        temp_buff[Fixed_len + 7] = 0xC2;         //可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,不保留会话
        temp_buff[Fixed_len + 8] = 0x00;          //可变报头第9个字节 :保活时间高字节 0x00
        temp_buff[Fixed_len + 9] = 0x64;         //可变报头第10个字节:保活时间高字节 0x64   100s
       
        /*     CLIENT_ID      */
        temp_buff[Fixed_len+10] = ClientID_len/256;                                                  //客户端ID长度高字节
        temp_buff[Fixed_len+11] = ClientID_len%256;                                                 //客户端ID长度低字节
        memcpy(&temp_buff[Fixed_len+12],ClientID,ClientID_len);                 //复制过来客户端ID字串       
        /*     用户名        */
        temp_buff[Fixed_len+12+ClientID_len] = Username_len/256;                                 //用户名长度高字节
        temp_buff[Fixed_len+13+ClientID_len] = Username_len%256;                                 //用户名长度低字节
        memcpy(&temp_buff[Fixed_len+14+ClientID_len],Username,Username_len);    //复制过来用户名字串       
        /*      密码        */
        temp_buff[Fixed_len+14+ClientID_len+Username_len] = Passward_len/256;        //密码长度高字节
        temp_buff[Fixed_len+15+ClientID_len+Username_len] = Passward_len%256;        //密码长度低字节
        memcpy(&temp_buff[Fixed_len+16+ClientID_len+Username_len],Passward,Passward_len); //复制过来密码字串

        TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);      //加入发送数据缓冲区
}

使用特权

评论回复
16
为你转身|  楼主 | 2022-2-28 23:41 | 只看该作者
2.3.SUBSCRIBE——订阅报文
        客户端向服务端订阅,服务端发送PUBLISH报文给客户端,SUBSCRIBE报文指定了最大的QoS等级

固定报头

byte1:82

byte2:剩余长度

可变报头

byte1:00

byte2:0A

有效载荷

        主题过滤器(主题) +   服务质量要求(QoS等级)

举例:


转换成16进制: 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                            69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

共49个字节(0x31)

                00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                    69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

QoS等级,这里为 00

                 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                    69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00

固定报头 + 可变报头 + 有效载荷

                82 36 00 0A 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30                 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65                 74 00


使用特权

评论回复
17
为你转身|  楼主 | 2022-2-28 23:43 | 只看该作者
2.4.SUBACK——订阅确认
固定报头

   

byte1:90

byte2:剩余长度

可变报头



        可变报头包含等待确认的SUBSCRIBE报文的报文标识符,也就是和订阅报文时的可变报头一致

这里为 00 0A

有效载荷

        有效载荷包含一个返回码清单


        

         允许返回码值:

        0x00        最大QoS

        0x01        成功-最大QoS1

        0x02        成功-最大QoS2

        0x80        Failure 失败

将三个连起来:90 03 00 0A 01

SUBSCRIBE代码
/*----------------------------------------------------------*/
/*函数名:SUBSCRIBE订阅topic报文                            */
/*参  数:QoS:订阅等级                                     */
/*参  数:topic_name:订阅topic报文名称                     */
/*返回值:无                                                */
/*----------------------------------------------------------*/
void MQTT_Subscribe(char *topic_name, int QoS)
{       
        Fixed_len = 2;                                                 //SUBSCRIBE报文中,固定报头长度=2
        Variable_len = 2;                                                     //SUBSCRIBE报文中,可变报头长度=2       
        Payload_len = 2 + strlen(topic_name) + 1;                      //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
       
        temp_buff[0] = 0x82;                                   //第1个字节 :固定0x82                     
        temp_buff[1] = Variable_len + Payload_len;             //第2个字节 :可变报头+有效负荷的长度       
        temp_buff[2] = 0x00;                                   //第3个字节 :报文标识符高字节,固定使用0x00
        temp_buff[3] = 0x01;                                           //第4个字节 :报文标识符低字节,固定使用0x01
        temp_buff[4] = strlen(topic_name)/256;                 //第5个字节 :topic_name长度高字节
        temp_buff[5] = strlen(topic_name)%256;                           //第6个字节 :topic_name长度低字节
        memcpy(&temp_buff[6], topic_name, strlen(topic_name)); //第7个字节开始 :复制过来topic_name字串               
        temp_buff[6 + strlen(topic_name)] = QoS;               //最后1个字节:订阅等级
       
        TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);  //加入发送数据缓冲区
}

使用特权

评论回复
18
为你转身|  楼主 | 2022-2-28 23:48 | 只看该作者
2.5.UNSUBSCRIBE——取消订阅
固定报头 + 可变报头 + 有效载荷

固定报头



byte1:A2

byte2:剩余长度

可变报头



包含一个报文标识符,与订阅主题报文的可以报头一致

00 0A

有效载荷

由主题过滤器,没有服务质量要求,与订阅主题报文一致,这里不重复说明

00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

将它们连起来

A2 35 00 0A 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74=

使用特权

评论回复
19
为你转身|  楼主 | 2022-2-28 23:48 | 只看该作者
2.6.UNSUBACK——取消订阅确认
固定报头 + 可变报头 + 有效载荷

固定报头



byte1:B0

byte2:剩余长度

可变报头

00 0A

有效载荷



连起来:B0 02 00 0A

使用特权

评论回复
20
为你转身|  楼主 | 2022-2-28 23:51 | 只看该作者
3.发布消息
3.1.PUBLISH——发布消息报文
        控制报文从客户端向服务端或者服务端向客户端传输一个应用消息

        这里QoS设置 00

组成:

        固定报头 + 可变报头 + 有效载荷 + 响应 + 动作

固定报头



byte 1 的4-7位是确认的,0-3我们设为0

byte1:30

byte2:剩余长度

可变报头

由主题名(Topic Name)和报文标识符(Packet Identifier)组成

报文标识符只有当QoS等级为1或2时才有效,我们这里为0

举例:

        主题名:/sys/a1fSNCGROG5/XPO01/thing/event/property/post

        转换成16进制:2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74                                 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74

        共48字节(0x30):

                               00 30 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31                                2F 74  68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F                                73 74

有效载荷

有效载荷包含将被发布的应用消息,数据的内容和格式是应用特定的

特定的格式:Json

举例:

7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 3122 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 30 2C 22 43 75 72 72 65 6E 74 48 75 6D 69 64 69 74 79 22 3A 33 30 2C 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 3A 31 31 2E 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D


这里需要注意,有效载荷前面不需要计算长度

响应



QoS等级为0无响应

动作



固定报头 + 可变报头 + 有效载荷

00 30 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 3122 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 30 2C 22 43 75 72 72 65 6E 74 48 75 6D 69 64 69 74 79 22 3A 33 30 2C 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 3A 31 31 2E 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D

PUBLISH
/*----------------------------------------------------------*/
/*函数名:等级0 发布消息报文                                  */
/*参  数:topic_name:topic名称                              */
/*参  数:data:数据                                         */
/*参  数:data_len:数据长度                                 */
/*返回值:无                                                 */
/*----------------------------------------------------------*/
void MQTT_PublishQs0(char *topic, char *data, int data_len)
{       
        int temp,Remaining_len;
       
        Fixed_len = 1;                              //固定报头长度暂时先等于:1字节
        Variable_len = 2 + strlen(topic);           //可变报头长度:2字节(topic长度)+ topic字符串的长度
        Payload_len = data_len;                     //有效负荷长度:就是data_len
        Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度
       
        temp_buff[0] = 0x30;                              //固定报头第1个字节 :固定0x30          
        do{                                         //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
                temp = Remaining_len%128;                   //剩余长度取余128
                Remaining_len = Remaining_len/128;      //剩余长度取整128
                if(Remaining_len>0)                      
                        temp |= 0x80;                            //按协议要求位7置位         
                temp_buff[Fixed_len] = temp;            //剩余长度字节记录一个数据
                Fixed_len++;                                     //固定报头总长度+1   
        }while(Remaining_len>0);                    //如果Remaining_len>0的话,再次进入循环
                             
        temp_buff[Fixed_len+0] = strlen(topic)/256;                       //可变报头第1个字节     :topic长度高字节
        temp_buff[Fixed_len+1] = strlen(topic)%256;                                  //可变报头第2个字节     :topic长度低字节
        memcpy(&temp_buff[Fixed_len+2], topic,strlen(topic));             //可变报头第3个字节开始 :拷贝topic字符串       
        memcpy(&temp_buff[Fixed_len + 2 + strlen(topic)], data, data_len);//有效负荷:拷贝data数据
       
        TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);//加入发送数据缓冲区       
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

681

帖子

0

粉丝