物联网开发第三站:stm32单片机与EC800Z 4G模块实现智能温湿度传感器入网

系列文章目录

物联网开发第一站:使用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

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值