系列文章目录
物联网开发第一站:使用MQTTX调试软件接入移动OneNet物联网平台
物联网开发第二站:使用4G物联网模块AT指令连接移动onenet云平台
前言
由于工作原因,拖更很久,今天终于有时间来写这篇文章了,新来的小伙伴可以先看前面两张的内容,本章是以前面内容为基础的实际应用文章。本章涉及到的代码太多,文中只摘取部分关键代码分析,源码链接放到本章最后。
准备工作:
1.windows10个人电脑
2.串口调试助手
3.移远EC800Z物联网模块+物联网卡或普通手机卡
4.USB转TTL工具
5.STM32单片机
6.DHT11温湿度传感器
7.C/C++ 基础知识
一、硬件设计
如果是想自己设计电路板的可以参考下面的电路
模块搜网的过程中电流比较大,模块工作电压在3.3-4.3V,建议电压3.8V,工作电流是多少没有测量过。我用的AMS-1117 3.3V的线性稳压管也能正常工作,如果带载设备较多建议使用DC-DC电路单独供电。下面是供电方案:
实际输出电压是3.9V左右完全满足需求。
传感器电路
本章使用的是模块化电路,引脚连接如图所示
二、代码部分
1.初始化
1.1 串口
void BSP_UART1_Init(USARTBaudRate BaudRate)
{
// 串口1是接在APB2上的
USART_InitTypeDef USART_InitStructure;// 串口
NVIC_InitTypeDef NVIC_InitStructure;// 中断
GPIO_InitTypeDef GPIO_InitStructure;// GPIO
RCC_APB2PeriphClockCmd(UART1_CLK,ENABLE);// 使能串口1
RCC_APB2PeriphClockCmd(UART1_TX_PIN_CLK|UART1_RX_PIN_CLK,ENABLE);// 使能串口1
USART_DeInit(USART1);
GPIO_InitStructure.GPIO_Pin = UART1_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART1_TX_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = UART1_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART1_TX_PORT, &GPIO_InitStructure);
// 设置串口
USART_InitStructure.USART_BaudRate = Rate[BaudRate];// 波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 发送接收
USART_InitStructure.USART_Parity = USART_Parity_No;// 无校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;// 停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 发送位数
USART_Init(USART1, &USART_InitStructure);
// 设置中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// 响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;// 响应优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_Init( &NVIC_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_Cmd(USART1,ENABLE);
}
1.2 DHT11引脚初始化
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DHT11_GPIO_APB2_CLK, ENABLE); //使能PG端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //关闭SWJ调试引脚重映射
GPIO_InitStructure.GPIO_Pin = DHT11_PIN; //PG11端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure); //初始化IO口
GPIO_SetBits(DHT11_PORT,DHT11_PIN); //PG11 输出高
DHT11_Rst(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
2.DHT11数据读取
代码如下:
DHT11_INFO dht11_info;
void DHT11PinOutPut(void);
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT_L(); //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT_H(); //DQ=1
delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i<5;i++)//读取40位数据
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) //忽略小数位
{
*humi=buf[0];
*temp=buf[2];
}
}else return 1;
return 0;
}
3. 4G模块初始化
初始化,模块默认波特率115200bps,8位,无校验
/*******************************************************************
* @name gNT26E_Init
* @brief 4G模块初始化
*********************************************************************/
void gNT26E_Init(void)
{
NT26ModuleCheck_Pin_Init(); //4G模块监测
NetReset();
BSP_UART1_Init(Usart_BaudRate_115200);// 串口初始化
ChackIPEffective(&gSystemPar);
ATCmd_Init();
}
4.AT指令发送与读取
AT指令发送代码段主要流程 :
1.发送AT指令
2.等待回复模块回复,超时处理
3.对回复内容判断:模块数据接收处理,AT指令错误判断,等待回复结果判断
4.返回AT指令操作结果
/*******************************************************************
* @name USARTSendAndWaitStr
*
* @brief 串口发送数据并等待回复
*
* @param pSendStr,发送数据,tSrcLen,数据长度,Wait100MSecs 等待时长ms
* pWaitStr:等待回复数据,tWaitLen:等待回复数据长度,tRetryCnt 尝试次数
* Clear_Flag:是保留空接收缓存区 1:清空 0:保留
* @retval 0:正常,1:异常
* @founder 一明九零
* @create 2023-06-01 一明九零
* @revise 2023-06-01 一明九零
*********************************************************************/
int8_t USARTSendAndWaitStr(uint8_t *pSendStr,uint16_t tSendLen,uint16_t Wait10MSecs,
uint8_t *pWaitStr,uint16_t tWaitLen,uint8_t tRetryCnt,uint8_t Clear_Flag)
{
int8_t err=1;
uint8_t i;
uint16_t j;
int16 tPot;
uint16_t datalen;
for(i = 0; i < tRetryCnt; i++)
{
gsLPUart[ATCmd.Usartx].RXBufNum=0;
memset(gsLPUart[ATCmd.Usartx].RXBuf,0x00,sizeof(gsLPUart[ATCmd.Usartx].RXBuf));
UsartSendData(ATCmd.SendChannel,pSendStr,tSendLen);
for(j=0;j<(Wait10MSecs);j++)
{
#ifndef DeBug
IWDG_Feed();
#endif
if(gsLPUart[ATCmd.Usartx].RXBufNum!=0)
{
datalen=gsLPUart[ATCmd.Usartx].RXBufNum;
delay_ms(10); //等待时间100ms
if(datalen==gsLPUart[ATCmd.Usartx].RXBufNum)
break;
}
else
{
delay_ms(10);
}
}
if(gsLPUart[ATCmd.Usartx].RXBufNum!=0)
{
if(NoFind!=(tPot=FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,(uint8_t *)"+QMTRECV: ",sizeof("+QMTRECV: ")-1)))
{ //AT指令发送后有平台数据接收到
gsLPUart[ATCmd.Usartx].DelBufNum=strlen((const char *)&gsLPUart[ATCmd.Usartx].RXBuf[tPot]); //计算接收到的数据帧长度
memcpy(gsLPUart[ATCmd.Usartx].DelBuf,&gsLPUart[ATCmd.Usartx].RXBuf[tPot],gsLPUart[ATCmd.Usartx].DelBufNum); //清空串口接收缓存区,将 “+LIPURC:”与其之后的数据转存在数据处理缓存区等待系统处理
memset(&gsLPUart[ATCmd.Usartx].RXBuf,0x00,sizeof(gsLPUart[ATCmd.Usartx].RXBuf));
gsLPUart[ATCmd.Usartx].RXBufNum=0;
err=0;
return err;
}
else
{
if((NoFind) != FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,pWaitStr,tWaitLen))
{
if(Clear_Flag)
memset(gsLPUart[ATCmd.Usartx].Data,0x00,sizeof(gsLPUart[ATCmd.Usartx].Data));
return 0;
}
if((FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,(uint8_t *)"ERROR",sizeof("ERROR")-1))!=NoFind)
{
for(uint16_t j = 0; j < Wait10MSecs; j++)
{
delay_ms(10);
}
continue;
}
else if((FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,(uint8_t *)"busy",sizeof("busy")-1))!=NoFind)
{
continue;
}
else //都没有,再次等待模块回应
{
for(j=0;j<(Wait10MSecs);j++)
{
delay_ms(10);
if((NoFind) != FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,pWaitStr,tWaitLen))
{
if(Clear_Flag)
{
gsLPUart[ATCmd.Usartx].RXBufNum=0;
memset(gsLPUart[ATCmd.Usartx].RXBuf,0x00,sizeof(gsLPUart[ATCmd.Usartx].RXBuf));
}
return 0;
}
}
return -1;
}
}
}
}
return 1;
}
5.MQTT联网
宏定义的内容均在onenet端获取,对应关系可以查看第一章的内容,token需要使用token生成工具进行生成。
/*设备的ID号*/
#define EquipmentID "\"DHT2024061501\","
/*产品ID号*/
#define ProductsID "\"YM85f3DyK8\","
/* Token生成器生成的token*/
#define Token "\"version=2018-10-31&res=produ85f3DyK8%2Fdevices%2FDHT2024061501&et=2039hod=md5&sign=iYiWsX8yxd3vGAOJRg%3D%3D\""
/*设备编码,需要导出EXcel表查看*/
#define EquipmentNum "2173213435"
/*onenet MQTT服务器地址*/
#define OneNetAddr "\"mqtts.heclouds.com\",1883"
/*设备端订阅主题名称*/
#define SubTopic "$sys/YM85f3DyK8/DHT2024061501/thing/property/post/reply"
/*设备端发布主题名称*/
#define PubTopic "$sys/YM85f3DyK8/DHT2024061501/thing/property/post"
int8_t AT_QMTCFG_Version(char *str,uint16_t len) //配置模块MQTT协议版本号
{
for(uint8_t i=0;i<4;i++)
{
if(gSystemPar.gIP[i].Efficacy!=0xAA)
{
continue;
}
memset(str,0x00,len);
sprintf((char *)str,"AT+QMTCFG=\"version\",0,4\r\n");
USARTSendAndWaitStr((uint8_t *)str,strlen(str),100,(uint8_t *)"OK",sizeof("OK")-1,5,1);
}
return 0;
}
int8_t AT_QMTCFG_KeepAlive(char *str,uint16_t len) //配置保活时间
{
for(uint8_t i=0;i<4;i++)
{
if(gSystemPar.gIP[i].Efficacy!=0xAA)
{
continue;
}
memset(str,0x00,len);
sprintf((char *)str,"AT+QMTCFG=\"keepalive\",%d,30\r\n",i);
USARTSendAndWaitStr((uint8_t *)str,strlen(str),100,(uint8_t *)"OK",sizeof("OK")-1,5,1);
}
return 0;
}
int8_t AT_LMQTTOPEN(char *str,uint16_t len) //IP服务器连接
{
uint8_t netbuff[4]={0};
USARTSendAndWaitStr((uint8_t *)"AT+QMTOPEN?\r\n",sizeof("AT+QMTOPEN?\r\n"),100,(uint8_t *)"OK",sizeof("OK")-1,3,0);
for(uint8_t i=0;i<4;i++)
{
if(gSystemPar.gIP[i].Efficacy!=0xAA)
{
continue;
}
memset(str,0x00,len);
sprintf(str,"+QMTOPEN: %d",i);
if(NoFind!=FindStrPos(gsLPUart[ATCmd.Usartx].RXBuf,gsLPUart[ATCmd.Usartx].RXBufNum,(uint8_t *)str,strlen(str)))
{
continue;
}
netbuff[i]=0xAA;
}
for(uint8_t i=0;i<4;i++)
{
if(netbuff[i]!=0xAA)
{
continue;
}
memset(str,0x00,len);
MyPrinfOut(str,"AT+QMTOPEN=%d,",i);
strncat(&str[strlen(str)],OneNetAddr,sizeof(OneNetAddr));
sprintf(&str[strlen(str)],",\r\n");
USARTSendAndWaitStr((uint8_t *)str,strlen(str),100,(uint8_t *)"OK",sizeof("OK")-1,3,1);
}
return 0;
}
//MQTT设备登录服务器
int8_t AT_LMQTTCONN(uint8_t channel,char *str,uint16_t len) 接
{
if((0==strlen(gSystemPar.Authn[channel].username))||
(0==strlen(gSystemPar.Authn[channel].password)))
{
return -1;
}
memset(str,0x00,len);
sprintf(str,"AT+QMTCONN=%d,",channel);
strncat(&str[strlen(str)],EquipmentID,sizeof(EquipmentID));
strncat(&str[strlen(str)],ProductsID,sizeof(ProductsID));
strncat(&str[strlen(str)],Token,sizeof(Token));
sprintf(&str[strlen(str)],"\r\n");
return USARTSendAndWaitStr((uint8_t *)str,strlen(str),100,(uint8_t *)"OK",sizeof("OK")-1,5,1);
}
//设备订阅主题
int8_t AT_LMQTTSUBUNSUB(char *str,uint16_t len,uint8_t mode) //MQTT订阅主题
{
for(uint8_t i=0;i<4;i++)
{
if(gNetStation.NetStation[i]!=0xA5)
{
continue;
}
memset(str,0x00,len);
sprintf((char *)str,"AT+QMTSUB=%d,1,\"",i);
strncat((char *)str,(char *)SubTopic,sizeof(SubTopic));
sprintf((char *)&str[strlen(str)],"\",0\r\n");
if(NoFind!= USARTSendAndWaitStr((uint8_t *)str,strlen(str),1000,(uint8_t *)"OK",sizeof("OK")-1,3,1))
{
gNetStation.NetStation[i]=0xAA;
if(mode)
gRunData.NeedReport[i]=0xA5;
}
}
return 0;
}
/*******************************************************************
* @name ATCmdSendData_MQTT
* @brief 通过AT指令发送帧数据 MQTT协议
* @param pdata:要发送的数据首指针;len:要发送的数据长度
* @retval tpos:返回帧长度
* @create 2023-06-01 一明九零
* @revise 2023-06-01 一明九零
*********************************************************************/
uint8_t ATCmdSendData_MQTT(uint8_t channel,uint8_t *pdata,uint16_t datalen)
{
uint8_t senddata[600]={0};
memset(senddata,0x00,sizeof(senddata));
//设备发布主题与主题内容
sprintf((char *)senddata,"AT+QMTPUBEX=%d,0,0,0,\"",channel);
strncat((char *)senddata,(char *)PubTopic,sizeof(PubTopic));
sprintf((char *)&senddata[strlen((char *)senddata)],"\",%d\r\n",datalen);
if(0==USARTSendAndWaitStr(senddata,strlen((char *)senddata),200,(uint8_t *)">",sizeof(">")-1,3,1))
{
if(0==USARTSendAndWaitStr(pdata,strlen((char *)pdata),50,(uint8_t *)"OK",sizeof("OK")-1,1,1))
{
}
}
else
{
SendFail(channel);
}
return 0;
}
6.主程序
联网流程,下面的代码是完整的联网流程,代码过长我就不在复制了,需要源码的,在文章最后有获取方式。
流程中MQTT遗嘱配置流程是个空流程。
协议版本号设置为4,对应的MQTT协议为3.1.1。
主程序中只调用以下几个任务,定时器计时触发循环调用即可
总结
本章内容到此结束,附上视频讲解:
https://www.bilibili.com/video/BV1azeGeeE6o/?spm_id_from=333.999.0.0&vd_source=e0244aa328846f9302cd4a88da8a5f75
本例程的主要源文件的已上传:https://download.csdn.net/download/weixin_44252100/89640687?spm=1001.2014.3001.5503