基于STM32(F103ZGTX系列)项目:智能楼宇(第一部分)

项目介绍

1、项目需求

1)在中控端,用户能够实现自主WIFI配网,从而自由连接所使用的网络。
2)在节点端,能够获取空气中有害气体烟雾的浓度以及环境中的温度、湿度与光照强度的数据,并能够检测到周边环境是否有人存在
3)能够使用LCD屏幕通过freertos系统设计开机页面与数据显示页面,同时将传感器 数据以及当前时间显示出来。
4)联网后能将获取到的传感器数据上传到阿里云平台,并从云平台获取实时时间。
5)中控端与节点端能够通过RS485进行传感器数据的传输
6)中控端能够控制节点端使用继电器对其他外设进行开关。

2、设计方案

1)使用MQ2、MQ135、光敏电阻、HC_SR501热释电传感器模块来获取空气中的有害气体、烟雾、光照强度等的数据。
2)STM32单片机通过FSMC模拟8080时序驱动LCD屏幕进行显示。使用GUI_guider 根据数据的类型将数据分页,然后对不同的显示页面进行设计排版。将lvgl移植到 STM32单片机中,并对必要参数如:屏幕尺寸、堆栈大小等进行配置,配置完成 后将设计好的页面导入STM32单片机中,修改好参数后即可显示。
3)使用lvgl官方字模工具以及lvglfonttool工具对汉字以及使用到的其它字符进行取模,然后将取好的字模导入STM32的flash中,lvgl就能通过读取单片机中的flash来获取到对应的字符的数据,从而将其显示出来
4)在阿里云平台创建好对应的产品,并将产品的各种参数设置好,就可以获得产品的各种信息,包括用户的ID等MQTT连接参数,使用WIFI模块完成网络连接后,使用MQTT库函数拼接好连接报文、订阅报文等,并根据MQTT连接参数连接到阿里 云平台以及产品,就能够向产品发送发布报文、获取从云平台发送的回复报文以及实时时间。
5)Freertos各个任务之间通过区分任务的重要性来为其分配优先级,对于不能够被打 断的流程,如STM32单片机通过时序来接收DHT11传感器模块的数据,就使用临 界区对这部分的程序进行保护。最后还需要通过任务的紧迫性以及重复度来调整各 个任务的创建顺序来优化产品运行时的用户体验以及性能。
6)开机后5秒内按下指定按键即可进入配网模式,通过手机APP连接热点后即可向系统发送新的WIFI信息。
7)可在手机端实时观测数据,并且可以控制开关。

3、相关技术点

1)通过向阿里云平台发送报文与指令来获取网络信息,并解析返回的数据报文来显示信息
2)通过串口获取传感器信息,并根据信息的数据格式来解析数据并显示
3)通过解析时序获取DHT11模块的信息
4)通过FSMC来模拟8080时序驱动LCD屏幕
5)通过SPI来向FLASH中写入读取数据字库、WIFI等信息
6)向STM32单片机移植FREErtos系统来实现任务的同步进行
7)通过ADC模数转换来获取部分传感器数据
8)使用GUIguider进行智能楼宇中控端的界面设计

4、预计效果

从实际使用出发,为了避免误操作改变配网的信息,WIFI配网可以在开机时限时进行,在开机后的五秒内,按下按键即可进入配网模式,在片刻后,使用手机APP连接WIFI热点后即可向空气质量检测仪发送新的WIFI信息,发送完成后,系统将新的WIFI信息保存至FLASH中,每次重新开机都会从FLASH中读取WIFI配网信息并会自动根据新的WIFI信息进行连接。
上电五秒后如果没有进行配网操作,系统正式开始运行,从外表看,屏幕显示开机界面,同时进度条开始动作,在3秒后,进入数据显示页面。在本产品中,一共制作了一个开机页面与一个数据显示页面,数据页面显示温度、湿度、烟雾浓度、有害气体、光照数据以及是否存在人员,数据页面的数据不会自动变化,这是为了防止频繁与节点端进行通信以造成混乱,因此需要进行切换页面才会向节点端请求新的数据。在数据页面,用户可以通过虚拟按键切换监视的节点,以及控制当前显示的节点端的继电器。
在后台,在开机后WIFI模块立即尝试连接预先设定好的网络,在连接到网络后,中控端会向阿里云平台发送将拼接好的连接报文、订阅报文,这个过程并不是阻塞的,而是以毫秒为单位间断执行。同时,中控端还会检测Flash内存储的节点端的地址码,并开始统计数量,然后,开始尝试向所有节点进行广播赋予与Flash中所存储的不相同的地址,如果在一段时间内无响应,则默认没有新节点加入,中控端会以之前所统计的节点数开始运行。而如果有相应,节点端会将中控端给予的地址存至自己的Flash中,然后向中控端发送响应数据,并于下次开机时采用该地址,中控端在接收到节点端的响应后,会将该地址存至Flash中,用于下次开机统计。之后节点板会每两秒主动获取一次传感器的数据并根据烟雾以及有害气体的数据进行判断,当传感器数据超过一定阈值,节点端会通过蜂鸣器响起进行报警,而当传感器数值下降到正常值,蜂鸣器停止报警。中控端的指令接收通过串口接收超时计时的方式在循环内等待与解析,当有指令下达时,节点端会先判断该指令是否是发送给自己的,然后根据预设的功能码进行对应的操作。

设备开发

一、热释电和OLED屏幕

本项目使用的HC_SR501热释电传感器是利用被动式红外探头,靠探测人体发射的 10UM 左右的红外线而进行工作的。人体发射的 10UM 左右的红外线通过菲泥尔滤光片增强后聚集到红外感应源上。红外感应源通常采用热释电元件,这种元件在接收到人体红外辐 射温度发生变化时就会失去电荷平衡,向外释放电荷,后续电路经检测处理后就能产生报警信号。HC_SR501模块当有检测到人体运动,向引脚输出 1(高电平),否则输出 0(低电平),所以只需要检测所连接的引脚电平即可判断是否有人存在。OLED被称为有机发光二极管或有机发光显示器。OLED是通过电流驱动有机薄膜本身来发光的,发的光可为红、绿、蓝、白等单色,同样也可以达到全彩的效果。所以说OLED是一种不同于CRT,和液晶技术的全新发光原理。而LED显示屏是由LED点阵和LEDPC面板组成,通过红色,蓝色,白色,绿色LED灯的亮灭来显示文字、图片、动画、视频,内容可以随时更换,各部分组件都是模块化结构的显示器件。本项目使用是厂商所提供的OLED屏幕驱动。

热释电源代码
#include "sr501.h"
#include "stdio.h"
#include "led.h"
/*****************************************************************
函 数 名 称:SR501_Config
函 数 功 能:SR501初始化
函 数 形 参:无
函 数 返 回:无
PERSON_CHECK--PB5
*******************************************************************/
//配置PB5
void SR501_Config(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure = {0};
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/****************************
函数名称:Person_GetVal
函数作用:人体检测
函数参数:无
函数返回值:无
****************************/
uint32_t Person_run[2] = {0,20};
int people=0;
void Person_GetVal(void)
{
		//有人检测时间加长
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==1)
		{
			//BEEP_ON();
			LED1_ON();
			printf("有人\r\n");
			people=1;
		}
		else
		{
			//BEEP_OFF();
			LED1_OFF();
			printf("无人\r\n");
			people=2;
		}
	}

OLED源代码
#include "oled.h"
#include "delay.h"
#include "oled_font.h"
/*****************************************************************
函 数 名 称:OLED_Config
函 数 功 能:OLED初始化函数
函 数 形 参:无
函 数 返 回:无
引 脚 定 义:
			OLED_RES--PA4复位
			OLED_DC---PA15命令/数据标志
			OLED_CS---PB7(SPI2_CS)
			OLED_SCL--PB13(SPI2_SCK)
			OLED_SI---PB15(SPI2_MISO)
备注:上升沿采样(采用模式0/模式3),高位先发,每8个时钟采样一次
*******************************************************************/
void OLED_PORTConfig(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
	//开启复用窗口
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	//配PA4   PA15
	GPIO_InitTypeDef GPIO_InitStructure = {0};
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	//配PB7
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	//配PB13  PB15
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
	SPI_InitTypeDef SPI_InitStructure = {0};
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
	SPI_InitStructure.SPI_CRCPolynomial = 0x07;
	SPI_Init(SPI2,&SPI_InitStructure);
	SPI_Cmd(SPI2,ENABLE);
}
/*****************************************************************
函 数 名 称:SPI2_SendData
函 数 功 能:SPI2发送数据和接受
函 数 形 参:data表示需要发送的数据
函 数 返 回:返回接收到的数据
备注:
*******************************************************************/
uint8_t SPI2_SendData(uint8_t data)
{
	// 等待发送缓冲区为空
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
	// 发送数据
	SPI_I2S_SendData(SPI2, data);
	// 等待接收缓冲区非空
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
	// 返回接收到的数据
	return SPI_I2S_ReceiveData(SPI2);
}
/*****************************************************************
函 数 名 称:OLED_ReadWriteByte
函 数 功 能:向OLED写入数据
函 数 形 参:data表示写入的数据,cmd表示选择命令还是数据(0--命令,1--数据)
函 数 返 回:无
备注:DC低电平写命令,高电平写数据
*******************************************************************/
uint8_t OLED_ReadWriteByte(uint8_t data,uint8_t cmd)
{
	uint8_t redata;
	if(cmd)
	{
		OLED_DC_H();
	}
	else
	{
		OLED_DC_L();
	}
	OLED_CS_L();//拉低片选
	redata = SPI2_SendData(data);
	OLED_CS_H();
	return redata;
}
/*****************************************************************
函 数 名 称:OLED_SetPos
函 数 功 能:设置OLED显示屏光标位置
函 数 形 参:x - 列地址, y - 页地址
函 数 返 回:无
*******************************************************************/
void OLED_SetPos(uint16_t x, uint16_t y)
{
	//设置页地址和列地址
	OLED_ReadWriteByte(0xB0+y,OLED_CMD);//设置页地址
	OLED_ReadWriteByte(0x00 + (x & 0x0f),OLED_CMD);//列地址低4位
	OLED_ReadWriteByte(0x10 + ((x & 0xf0)>>4),OLED_CMD);//列地址高4位
}
/*****************************************************************
函 数 名 称:OLED_Clear
函 数 功 能:清空OLED显示屏
函 数 形 参:data - 清屏时填充的字节数据
函 数 返 回:无
*******************************************************************/
void OLED_Clear(uint8_t data)
{
  uint8_t i = 0;
  uint8_t j = 0;
	
  for(i=0;i<OLED_PAGE;i++)
  {
	OLED_SetPos(0,i);
    for(j=0;j<OLED_WIGH;j++)
    {
      //写数据
      OLED_ReadWriteByte(data,OLED_DATA);
    }
  }
}
/*****************************************************************
函 数 名 称:OLED_ShowData8
函 数 功 能:在OLED屏幕上显示宽度8点阵数据
函 数 形 参:data - 清屏时填充的字节数据
函 数 返 回:无
*******************************************************************/
void OLED_ShowData8(uint16_t x,uint16_t y,char *data,uint8_t size)
{
	uint16_t i=0,j=0;
	uint8_t page = (size+7)/8;
	for(i=0;i<page;i++)//打印页
	{
		OLED_SetPos(x,y+i);
		for(j=0;j<((size+1)/2);j++)
		{
			OLED_ReadWriteByte(*data++,OLED_DATA);
		}
	}
}
/*****************************************************************
函 数 名 称:OLED_Config
函 数 功 能:初始化OLED
函 数 形 参:无
函 数 返 回:无
*******************************************************************/
void OLED_Config(void)
{
	//管脚初始化
	OLED_PORTConfig();
	//OLED硬件复位
	OLED_CS_H();//拉高片选
	OLED_RES_H();
	delay_ms(200);
	OLED_RES_L();
	delay_ms(200);
	OLED_RES_H();
	delay_ms(200);
	//OLED驱动编程
	OLED_ReadWriteByte(0xAE, OLED_CMD); // 打开 OLED 显示屏
	OLED_ReadWriteByte(0x02, OLED_CMD); // 设置低列地址
	OLED_ReadWriteByte(0x10, OLED_CMD); // 设置高列地址
	OLED_ReadWriteByte(0x20, OLED_CMD); // 设置水平寻址模式
	OLED_ReadWriteByte(0x40, OLED_CMD); // 设置起始行地址为 0
	OLED_ReadWriteByte(0xB0, OLED_CMD); // 设置起始页地址为 0
	OLED_ReadWriteByte(0x81, OLED_CMD); // 设置亮度(对比度),后接亮度值
	OLED_ReadWriteByte(0xCF, OLED_CMD); // 设置亮度值为 0~128
	OLED_ReadWriteByte(0xA1, OLED_CMD); // 设置左右映射方向
	OLED_ReadWriteByte(0xC8, OLED_CMD); // 设置 COM 扫描方向(上下扫描)
	OLED_ReadWriteByte(0xA6, OLED_CMD); // 设置正常显示(不反相)
	OLED_ReadWriteByte(0xA8, OLED_CMD); // 设置多路复用比率
	OLED_ReadWriteByte(0x3F, OLED_CMD); // 设置占空比为 1/64
	OLED_ReadWriteByte(0xD3, OLED_CMD); // 设置显示偏移
	OLED_ReadWriteByte(0x00, OLED_CMD); // 无偏移
	OLED_ReadWriteByte(0xD5, OLED_CMD); // 设置时钟分频比/振荡器频率
	OLED_ReadWriteByte(0x80, OLED_CMD); // 设置分频比
	OLED_ReadWriteByte(0xD9, OLED_CMD); // 设置预充电周期
	OLED_ReadWriteByte(0x1F, OLED_CMD); // 设置预充电周期为 31
	OLED_ReadWriteByte(0xDA, OLED_CMD); // 设置 COM 引脚硬件配置
	OLED_ReadWriteByte(0x12, OLED_CMD); // 配置引脚
	OLED_ReadWriteByte(0xDB, OLED_CMD); // 设置 VCOMH 电平
	OLED_ReadWriteByte(0x30, OLED_CMD); // 设置 VCOMH 为 0.83 x Vcc
	OLED_ReadWriteByte(0x8D, OLED_CMD); // 设置电荷泵使能
	OLED_ReadWriteByte(0x14, OLED_CMD); // 使能电荷泵
	OLED_ReadWriteByte(0xAF, OLED_CMD); // 关闭 OLED 显示屏
	OLED_Clear(OLED_CLOSE);
}
/*****************************************************************
函 数 名 称:OLED_ShowStr
函 数 功 能:显示单个ASCII字符
函 数 形 参:无
函 数 返 回:无
*******************************************************************/
void OLED_ShowStr(uint16_t x,uint16_t y,char* data,uint8_t size)
{
	uint8_t i;
	i = *data - 32;
	if(size == 12)
	{
		OLED_ShowData8(x,y,(char *)ASCII_12[i],12);
	}
	else if(size == 16)
	{
		OLED_ShowData8(x,y,(char *)ASCII_16[i],16);
	}
}
/*****************************************************************
函 数 名 称:OLED_ShowString
函 数 功 能:显示ASCII字符串
函 数 形 参:x —— 起始横坐标
           y —— 起始纵坐标
           data —— 待显示的ASCII字符串
           size —— 字符大小,支持12或16号字体
函 数 返 回:无
*******************************************************************/
void OLED_ShowString(uint16_t x,uint16_t y,char *data,uint8_t size)
{
    uint16_t x_offset = x;  // 初始横坐标偏移
    uint16_t y_offset = y;  // 初始纵坐标偏移
    
    while (*data != '\0')
    {
        if (*data == '\n')
        {
            // 换行符处理
            y_offset += ((size+7)/8); // 增加行高
            x_offset = x;  // 横坐标重新从起始位置开始
            data++;  // 指向下一个字符
        }
        else
        {
           	OLED_ShowStr(x_offset, y_offset, data, size); // 显示当前字符
            x_offset += ((size+1) / 2);  // 横坐标偏移一个字符的宽度(根据字体大小确定)
            data++;  // 指向下一个字符
            // 如果到达屏幕末端,则换行
            if (x_offset >= (128 - (size+1) / 2))
            {
                y_offset += ((size+7)/8);  // 换行
                x_offset = x;  // 横坐标重新从起始位置开始
            }
        }
    }
}
/*****************************************************************
函 数 名 称:OLED_ShowData16
函 数 功 能:在OLED屏幕上显示宽度16点阵数据
函 数 形 参:x:显示位置的横坐标
           y:显示位置的纵坐标
           data:要显示的数据的指针
           size:数据的大小,即字体的宽度
函 数 返 回:无
调 用 示 例:OLED_ShowData16(0,4,(uint8_t *)Chinese_1616[0].Msk,16);
*******************************************************************/
void OLED_ShowData16(uint16_t x,uint16_t y,char *data,uint8_t size)
{
	uint16_t i=0,j=0;
	uint8_t page = (size+7)/8;
	for(i=0;i<page;i++)//打印页
	{
		OLED_SetPos(x,y+i);
		for(j=0;j<size;j++)
		{
			OLED_ReadWriteByte(*data++,OLED_DATA);
		}
	}
}
/*****************************************************************
函 数 名 称:OLED_ShowChinese
函 数 功 能:在OLED屏幕上显示单个汉字
函 数 形 参:x:显示位置的横坐标
           y:显示位置的纵坐标
           data:要显示的汉字的指针
           size:汉字的字体大小
函 数 返 回:无
*******************************************************************/
void OLED_ShowChinese(uint16_t x,uint16_t y,char *data,uint8_t size)
{
	uint16_t i;    
	uint16_t Chinese_Num; // 汉字数目
	Chinese_Num = sizeof(Chinese_1616)/sizeof(typFNT_GB16); // 统计汉字数目
	for(i=0;i<Chinese_Num;i++)
    {
		if ((Chinese_1616[i].Index[0] == *(data)) && (Chinese_1616[i].Index[1] == *(data+1))) //如果汉字存在
		{
			OLED_ShowData16(x,y,(char*)Chinese_1616[i].Msk,size);
			
		}
	}
}
/*****************************************************************
函 数 名 称:OLED_ShowChineseline
函 数 功 能:在OLED屏幕上显示一串中文字符(包括换行处理)
函 数 形 参:x:显示位置的横坐标
           y:显示位置的纵坐标
           data:要显示的中文字符串的指针
           size:中文字符的字体大小
函 数 返 回:无
*******************************************************************/
void OLED_ShowChineseline(uint16_t x,uint16_t y,char *data,uint8_t size)
{
    uint16_t x_offset = x;  // 初始横坐标偏移
    uint16_t y_offset = y;  // 初始纵坐标偏移
    
    while (*data != '\0')
    {
        if (*data == '\n')
        {
            // 换行符处理
			y_offset += ((size+7)/8); // 增加行高
            x_offset = x;  // 横坐标重新从起始位置开始
            data++;  // 指向下一个字符
        }
        else
        {
           	OLED_ShowChinese(x_offset, y_offset, data, size); // 显示当前字符
            x_offset += size;  // 横坐标偏移一个字符的宽度(根据字体大小确定)
            data+=2;  // 指向下一个字符
            // 如果到达屏幕末端,则换行
            if (x_offset >= (128 - (size+1)))
            {
                y_offset += ((size+7)/8);  // 换行
                x_offset = x;  // 横坐标重新从起始位置开始
            }
        }
    }
}
/*****************************************************************
函 数 名 称:ShowDATAmix
函 数 功 能:在OLED屏幕上显示一串中文和字符串
函 数 形 参:x:显示位置的横坐标
           y:显示位置的纵坐标
           data:要显示的中文、字符串的指针
           size:字体大小
函 数 返 回:无
*******************************************************************/
void OLED_ShowDATAmix(uint16_t x,uint16_t y,char *data,uint8_t size)
{
	while (*data != '\0')
	{
		if (*data <= 0x7F) // ASCII字符
		{
			OLED_ShowStr(x, y, data, size);
			x += ((size+1)/2); //移动x坐标
			data++;
		}
		else // 汉字
		{
			OLED_ShowChinese(x, y,data,size);
			x+=size; //移动x坐标
			data += 2;
		}
	}
}

二、数据上传云平台,MQTT协议

中控端所使用的ESP8266是一个拥有完整且成体系的WIFI网络解决方案的模块,本项目所使用的ESP8266是串口型WIFI,速度比较低,不能用来传输图像或者视频等这些大容量数据,主要应用于数据量传输比较少的场合。在本项目中,模块需要执行连接WIFI、阿里云、WIFI配网的任务。
模块连接WIFI的指令流程为:AT(检测模块是否能够正常工作)、AT+CWMODE=1(进入站点模式)、AT_CWJAP=…(连接WIFI);
模块进行WIFI配网的指令流程为:AT(检测模块是否能够正常工作)、AT+CWMODE=2(进入AP模式,自身作为热点)、AT+CWSAP=…(设置AP模式的参数,如热点的名称与密码)、AT+RST(重启模块使设置的参数生效)、AT+CIPMUX=1(开启多链路模式)、AT+CIPSERVER=1,PORT…(开启TCP服务器)、(用户连接热点并使用APP进行WIFI配网)、AT+RESTORE(退出AP模式)。
模块连接并向阿里云发送数据的指令流程(需要先连接WIFI):AT+CIPSTART=TCP…(与阿里云服务器建立连接)、AT+CIPMODE=1(开启透传模式)、AT+CIPSEND(发送数据)、使用MQTTpacket所提供的函数拼接连接报文(连接物联网平台的产品,需要提供物联网产品所生成的用户名、ID与密码)并通过串口5发送、根据物联网平台提供的主题通过串口5发送拼接好的订阅报文、向物联网平台发送拼接好带有所需数据的发布报文(该报文必须为json格式);

报文格式
"{\"id\":123456,\
						\"params\":{\"tem\":%d,\
						\"Humidity\":%d,\
						\"light\":%d,\
						\"Smoke\":%d,\
						\"HCHO\":%d\
						},\
						\"version\":\"1.0\",\
						\"method\":\"thing.event.property.post\"}"

本项目使用串口5来与esp8266进行通信,由于模块特性,向模块发送的指令并不一定能快速得到响应,因此为了判断所发送的指令是否得到有效执行,指令发送函数需要有超时检测机制以及发送失败的检测机制。对于esp8266模块来说,用户向其发送指令大多能够得到其回应,而其回应的数据格式是固定的,因此发送失败检测机制可以通过判断串口接收到的数据是否存在某个数据格式的特定字段来实现。而超时检测机制可以使用Freertos系统提供的延时函数再辅以变量计数的方式,通过向函数输入需要等待的时间,让变量以固定的时间尺度进行递增,当变量大于所输入的时间即可实现超时检测,同时也避免了延时对系统运行的妨碍。
在本项目中,有WIFI配网和WIFI连接两种操作,其中使用到了模块的两种不同的模式:AP模式和STA模式。AP模式允许设备作为一个无线接入点,提供无线接入服务,允许其他无线设备接入并提供数据访问。而STA模式设备作为无线终端,本身并不接受无线接入,而是连接到已有的无线接入点(AP)。在无线网络中,STA通常是客户端的角色。概括的说,AP模式是提供无线接入服务的网络中心节点,而STA模式是连接到网络的终端设备。在连接阿里云时使用到了MQTT协议,这种协议的控制报文由固定报头、可变报头、有效载荷组成。其中,固定报头由四个比特的MQTT控制报文类型、四个比特的指定报文类型控制位、八个比特的剩余长度构成。可变报头由一个字节的最高有效位MSB和一个字节的最低有效位LSB构成。

**WiFi连接**
#include "wifi.h"
#include "usart.h"
#include "rtc.h"
#include "stdio.h"
#include "string.h"
#include "MQTTPacket.h"//包含MQTT库头文件
#include "stdlib.h"
#include "time.h"
/*****************************************************************
 *函 数 名 称:WIFI_SendCmd
 *函 数 功 能:发送指令,并且判断是否发送成功
 *函 数 形 参:CMD:发送的指令内容
							 ACK:期待回复的数据
							 TIME:等待时间 单位ms
 *函 数 返 回:成功返回0 失败返回1
 *作       者:CYM
 *修 改 日 期:xx/xx/xx
 返回1成功0失败
*******************************************************************/
uint8_t WIFI_SendCmd(char * CMD,char * ACK,uint16_t timeout)
{
	uint8_t ret=0;
	char prompt[256];
	USART6_ClearData();
	USART6_SendString((uint8_t*)CMD);
	while(timeout--)
	{
		if(USART6_Data.Rx_flag == 1)
		{
			USART6_Data.Rx_flag=0;
			if(strstr((char *)USART6_Data.Rx_buff,(char *)ACK) != NULL)
			{		
				ret = 1;
				break;
			}
		}
		HAL_Delay(1);
	}
	if(ret == 1)
	{
		sprintf(prompt,"Success成功:%s",CMD);
		printf("%s\r\n",prompt);
	}
	else
	{
		sprintf(prompt,"Error失败:%s",CMD);
		printf("%s\r\n",prompt);
	}
	return ret;
}
/*****************************************************************
 *函 数 名 称:WIFI_ConnectSever
 *函 数 功 能:WIFI链接服务器
 *函 数 形 参:无
 *函 数 返 回:成功返回0 失败返回1
 *作       者:CYM
 *修 改 日 期:xx/xx/xx
*******************************************************************/
struct wifinameandpass flash_wifidata ={0};
void WIFI_ConnectServer(uint8_t *ServerIP,uint16_t Port)
{
	char WIFI_ConnectServer_buff[256];
	if(WIFI_SendCmd("AT\r\n","OK",2000)==1)
	{
		printf("WIFI 正常工作\r\n");
		if(WIFI_SendCmd("AT+CWMODE=1\r\n","OK",2000)==1)
		{
			printf("WIFI 模式设置完成\r\n");
			memset(WIFI_ConnectServer_buff,0,256);
			sprintf(WIFI_ConnectServer_buff,"AT+CWJAP=\"cmyddf\",\"88888888\"\r\n");	
			if(WIFI_SendCmd(WIFI_ConnectServer_buff,"OK",5000)==1)
			{
				printf("WIFI 热点链接完成\r\n");
				memset(WIFI_ConnectServer_buff,0,256);
				sprintf(WIFI_ConnectServer_buff,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ServerIP,Port);
				if(WIFI_SendCmd(WIFI_ConnectServer_buff,"OK",5000)==1)
				{
					printf("WIFI 服务器链接完成\r\n");
					if(WIFI_SendCmd("AT+CIPMODE=1\r\n","OK",1000)==1)
					{
						if(WIFI_SendCmd("AT+CIPSEND\r\n","OK",1000)==1)
						{
							printf("请发送数据\r\n");
							return ;
						}
					}
				}
			}  
		}
	}
}
**MQTTT订阅与发布**
/*****************************************************************
 *函 数 名 称:MQTT_Connect    	
 *函 数 功 能:建立MQTT链接
 *函 数 形 参:NAME:用户名,PD:密码,ID:客户端ID
 *函 数 返 回:0 成功 1失败
 *作       者:CYM
 *修 改 日 期:xx/xx/xx
*******************************************************************/
uint8_t MQTT_Connect(char *ClientID,char *UserName,char *PassWord,uint16_t timeout)
{
	unsigned char sessionPresent=0;
	unsigned char connack_rc = 0;
	uint16_t Connect_len = 0;//连接报文长度
	uint8_t Connect_buff[512] = {0};//存放连的接报文
	//{ {'M', 'Q', 'T', 'C'}, 0, 4, {NULL, {0, NULL}},60,1,0,MQTTPacket_willOptions_initializer,{NULL, {0, NULL}},{NULL, {0, NULL}} }
	MQTTPacket_connectData options =  MQTTPacket_connectData_initializer;
	options.clientID.cstring = ClientID;
	options.username.cstring = UserName;
	options.password.cstring = PassWord;
	//MQTT报文拼接函数,返回值为连接报文的长度
	Connect_len = MQTTSerialize_connect(Connect_buff,512,&options);
	HAL_UART_Transmit(&huart6,(uint8_t *)Connect_buff,Connect_len,3000);
	while(timeout--)
	{
		MQTTDeserialize_connack(&sessionPresent,&connack_rc,USART6_Data.Rx_buff,1024);
		if(connack_rc==0x00)
		{
			printf("连接MQTT成功\r\n");
			USART6_ClearData();//清楚串口wifi
			return 1 ;
		}
		HAL_Delay(1);
	}		
	return 0;
}
/*****************************************************************
 *函 数 名 称:MQTT_Subscribe
 *函 数 功 能:订阅主题
 *函 数 形 参:Topic:主题
 *函 数 返 回:void
 *作       者:QXJ
 *修 改 日 期:xx/xx/xx
*******************************************************************/
void MQTT_subscribe(char * Topic)
{
	uint16_t len =0 ;//订阅报文长度
	uint8_t buff[512] = {0};//存放订阅报文	
	MQTTString topicFilters[1]={0};//存放订阅主题的数组
	topicFilters[0].cstring = Topic;
	int requestedQoSs[1]= {0};//订阅主题的消息质量
	requestedQoSs[0] = 0;
	len = MQTTSerialize_subscribe(buff,512,0,0,1,topicFilters,requestedQoSs);
	HAL_UART_Transmit(&huart6,(uint8_t *)buff,len,3000);
}
/*****************************************************************
函 数 名 称:MQTT_Publish
函 数 功 能:MQTT发布报文构造函数
函 数 形 参:Topic 发布主题, Payload 发布数据
函 数 返 回:无
*******************************************************************/
void MQTT_Publish(char * Topic,unsigned char * Payload)
{
	uint16_t len =0 ;//链接报文长度
	uint8_t buff[512] = {0};//存放链接报文
	MQTTString topicName={0};
	topicName.cstring = Topic;
	len = MQTTSerialize_publish(buff, 512,0,0,0,0,topicName, Payload, strlen((const char*)Payload));
	//发布数据
	HAL_UART_Transmit(&huart6,(uint8_t *)buff,len,3000);
}
/*****************************************************************
函 数 名 称:MQTT_Unsubscribe
函 数 功 能:MQTT退订报文构造函数
函 数 形 参:Topic 退订主题
函 数 返 回:无
*******************************************************************/
void MQTT_Unsubscribe(char * Topic)
{
	uint16_t len =0 ;//退订报文长度
	uint8_t buff[512] = {0};//存放退订报文	
	MQTTString topicFilters[1]={0};//存放退订主题的数组
	topicFilters[0].cstring = Topic;

	len = MQTTSerialize_unsubscribe(buff,512,0,0,1,topicFilters);
	HAL_UART_Transmit(&huart6,(uint8_t *)buff,len,3000);	
}
/*****************************************************************
函 数 名 称:MQTT_DisConnect
函 数 功 能:MQTT断开连接报文构造函数
函 数 形 参:无
函 数 返 回:无
*******************************************************************/
void MQTT_DisConnect(void)
{
	uint16_t len =0 ;//断开连接报文长度
	uint8_t buff[512] = {0};//存放断开连接报文	
	len = MQTTSerialize_disconnect(buff,512);
	HAL_UART_Transmit(&huart6,(uint8_t *)buff,len,3000);	
}
/*****************************************************************
函 数 名 称:MQTT_Send_Data
函 数 功 能:MQTT发送数据函数
函 数 形 参:无
函 数 返 回:无
*******************************************************************/
char sendbuff[256] = {0};
int num = 0;
void MQTT_Send_Data(void)
{
	sprintf(sendbuff, "{\"id\":123456,\
						\"params\":{\"tem\":%d,\
						\"Humidity\":%d,\
						\"light\":%d,\
						\"Smoke\":%d,\
						\"HCHO\":%d\
						},\
						\"version\":\"1.0\",\
						\"method\":\"thing.event.property.post\"}",Tem,Hum,Light,Smoke,Air);
	MQTT_Publish(PubTopic, (unsigned char *)sendbuff);
}
/*****************************************************************
函 数 名 称:Aliot_Config
函 数 功 能:MQTT连接云平台函数
函 数 形 参:无
函 数 返 回:无
*******************************************************************/
void Aliot_Config(void)
{
	WIFI_ConnectServer((uint8_t*)Server_IP,Server_PORT);
	HAL_Delay(500);
	MQTT_Connect(Server_ClientID,Server_UserName,Server_PassWord,3000);
	HAL_Delay(500);
	MQTT_subscribe(SubTopic);
	HAL_Delay(500);
}

进入AP模式

main.c
在这里插入图片描述

wifi.c
/*****************************************************************
 *函 数 名 称:WIFI_SetAPmode
 *函 数 功 能:WIFI设置模式
 *函 数 形 参:无
 *函 数 返 回:成功返回0 失败返回1
 *作       者:QXJ
 *修 改 日 期:xx/xx/xx
*******************************************************************/
void WIFI_SetAPmode(void)
{
		char *p=NULL;
		if(WIFI_SendCmd((u8*)"AT\r\n",(u8*)"OK",100) == 0)
		{
				printf("WIFI 正常工作\r\n");
				if(WIFI_SendCmd((u8*)"AT+CWMODE=2\r\n",(u8*)"OK",1000) == 0)
				{
					printf("WIFI 进入AP模式\r\n");
					if(WIFI_SendCmd((u8*)"AT+CWSAP=\"XJXYD202\",\"123456789\",5,3\r\n",(u8*)"OK",1000) == 0)
					{
							printf("WIFI AP参数配置完成\r\n");
							WIFI_SendCmd((u8*)"AT+CIPMUX=1\r\n",(u8*)"OK",1000);
							WIFI_SendCmd((u8*)"AT+CIPSERVER=1,8080\r\n",(u8*)"OK",1000);
							printf("请依次链接热点和服务器\r\n");
							memset(&rxdata,0,sizeof(rxdata));	
							while(strstr((char*)rxdata.rxbuff,"IPD") == NULL){}
							printf("接受到用户数据:%s\r\n",strstr((char*)rxdata.rxbuff,"IPD"));	
							//提取WiFi名字和密码,然后保存到FLASH指定地址	
							p = strstr((char*)rxdata.rxbuff,"wifiname");
							p += 11;
							for(uint8_t i=0;i<12;i++)
							{
								wifi.wifi_name[i] = p[i];
							}					
							printf("wifiname:%s\r\n",wifi.wifi_name);
							p = strstr((char*)rxdata.rxbuff,"wifipasswd");
							p += 13;
							for(uint8_t i=0;i<9;i++)
							{
								wifi.wifi_passwd[i] = p[i];
							}					
							printf("wifipasswd:%s\r\n",wifi.wifi_passwd);	
							Flash_SectorErase(WIFI_Addr);//擦除扇区
							Flash_PageProgram(WIFI_Addr, (uint8_t *)&wifi,sizeof(wifi));
							//信息提取完毕之后
							WIFI_SendCmd((u8*)"AT+RESTORE\r\n",(u8*)"OK",1000);
					}
						
				}
		}				
}

uint8_t Enter_APmode(void)
{
	uint16_t cnt = 5000;
	printf("请在开机5秒内按下按键4,进入配网模式\r\n");
	while(cnt--)
	{
		if(key_getvalue() == 4)
		{
			return 1;
		}
		Delay_nms(1);
	}
	return 0;
}

三、DHT11温湿度检测

DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有枀高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。该模块的温度测量范围为:零下二十摄氏度到零上六十摄氏度;湿度测量范围为:百分之五到百分之九十五。推荐采样周期为2秒/次。
由于模块使用单总线通信,因此需要严格按照固定的时序对传入数据进行采集。首先,上电后模块处于不稳定阶段,一秒内不能对模块发送任何数据,同时在空闲态还需要将与模块相连的IO口置高电平。
当需要采集数据时,需要先向模块发送一个大于18毫秒且小于30毫秒的低电平,然后将引脚电平拉高以等待模块发送信号,此时,当模块检测到输入的低电平信号后,会返回一个83微秒的低电平信号作为应答,然后发送一个87微秒的高电平信号作为数据传输前的提醒。单片机在接收到一次低电平的应答信号以及一次高电平的提醒后,需要在一个四十次的循环内通过一个检测引脚低电平的循环以及一个检测引脚高电平的循环来检测高低电平信号。模块使用54微秒的低电平和23到27微秒的高电平来作为数据0,使用54微秒的低电平和68到74微秒的高电平来作为数据1,只需要在循环内先放置上述的两个电平信号检测循环,然后使用一个数据0和数据1的持续时间的中间值的延时,当数据为0时,由于此时数据0的23到27微秒的高电平已经结束,取而代之的是数据1的前置54微秒的低电平,所以低电平代表的就是数据0;而又当数据为1时,由于数据1的高电平持续时间比延时时间更长,在延时结束后IO口电平仍为高,所以高电平代表的就是数据1。只需要将这些传入的电平信号存入数组中并加以解析,就能够获得DHT11模块的温湿度数据。

#include "dht11.h"
#include "delay.h"
#include "stdio.h"
//PB6要能够输入输出    
void DHT11_Config(void)
{
	//PB6配置成开漏模式
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure ={0};
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏
	GPIO_InitStructure.GPIO_Pin	= GPIO_Pin_6;//引脚位号
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速率
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	//步骤1:由上拉电阻拉高一直保持高电平
	DHT11HIGH();
}

//根据DHT11时序读取数值
float humidity,tem;
void Dht11ReadData(void)
{
	uint8_t data[5] = {0};
	uint8_t arr[40] = {0};
	uint8_t i = 0;
	uint16_t timeout = 0;
//步骤2:输出一个最少18ms的低电平,最大30ms
	DHT11HIGH();	
	DHT11LOW();
	delay_ms(18);
	DHT11HIGH();
//步骤3:检测有持续83us低电平和87us的高电平
	while(DHT11read()==1)
		{//等待83us低电平到来
		timeout++;
		delay_us(1);
		if(timeout>=100)
			{
			return;
			}
		}
	timeout = 0;
	while(DHT11read()==0)
		{//等待87us高电平到来
		timeout++;
		delay_us(1);
		if(timeout>=100)
			{
			return;
			}
		}
//步骤4:循环读取40位数据	
		//无论收到1或0都要等待54微妙低电平
	for(i=0;i<40;i++)
		{
		timeout = 0;
		while(DHT11read()==1)
			{//等待54us低电平到来
			timeout++;
			delay_us(1);
			if(timeout>=100)
				{
				return;
				}
			}
		timeout = 0;
		while(DHT11read()==0)
			{//等待能判断0/1的高电平到来
			timeout++;
			delay_us(1);
			if(timeout>=100)
				{
				return;
				}
			}
		delay_us(40);
		arr[i] = DHT11read();
	}		
		//将数据拆成五部分,分别放在data数组中
	for(i=0;i<40;i++)
	{ //0~7  8~15  16~23  24~31 32~39
		data[i/8]=data[i/8]+(arr[i]<<(7-i%8));
	}
	//校验
	if(((data[0]+data[1]+data[2]+data[3])&0xff) != data[4])
		{
		return;
		}	
	humidity=data[0]+data[1]/10.0;//湿度
	tem=data[2]+(data[3]&0x7f)/10.0;//温度  0111 1111
	if((data[3]&0x80) != 0)  //1000 0000
		{
		tem = 0-tem;
		}
	printf("湿度参数humidity=%.1f\r\n",humidity);
	printf("温度参数tem=%.1f\r\n",tem);
	return;
}

四、光照及烟雾、有害气体检测

本项目的光照强度检测部分使用的是光敏电阻,烟雾浓度检测部分使用的是MQ2烟雾传感器模块,有害气体检测部分使用的是MQ135传感器模块。
光敏电阻又称光导管,常用的制作材料为硫化镉,另外还有硒、硫化铝、硫化铅和硫化铋等材料。这些制作材料具有在特定波长的光照射下,其阻值迅速减小的特性,因此只需要检测并使用ADC模数转换解析光敏电阻阻值的模拟信号即可得到光照强度的数值。
MQ2烟雾传感器模块属于二氧化锡半导体气敏材料,二氧化锡吸附空气中的氧,形成氧的负离子吸附,使半导体中的电子密度减少,从而使其电阻值增加。当与烟雾接触时,如果晶粒间界处的势垒收到烟雾的浓度而变化,就会引起表面导电率的变化。烟雾的浓度越大,导电率越大,输出电阻越低,同样的只需要使用ADC模数转换解析MQ2烟雾传感器模块阻值的模拟信号即可得到烟雾强度的数值,同理M135亦是如此。光敏电阻的测量范围为30千欧到200千欧,MQ2烟雾传感器模块的测量范围为:100到10000PPM,但由于STM32单片机的模数转换是将3.3V分成4096份,所转换出来的数字量是将一个范围内的电阻值转化为一个具体的数值,因此能够测量到的范围在转换后只有0到4095。
在使用ADC进行模数转换时,可以采用多个ADC对不同的传感器数据进行采集,也可以采用一个ADC的不同通道对不同传感器进行数据采集。但由于ADC的寄存器在同一时间只能存放一个通道的数据,在获取后一个通道的数据时会将前一个通道所获取的数据覆盖掉,因此,需要使用DMA对ADC转换后的数据进行转存,当一个通道获取到数据后,ADC的寄存器会立刻将该数据转移到DMA的寄存器中,实际上,此时的ADC寄存器仅仅充当一个中转站的作用,这样才能够实现一个ADC采集多通道数据采集不丢失。

**多通道**
#include "adc.h"
#include "stdio.h"
//MQ135--PA1	MQ2--PA2	光照--PA3
//	通道1	空气质量		通道2 烟雾		通道3 光照
uint16_t Data[30];
uint16_t MQ2;
uint16_t MQ135;
uint16_t Light;
void ADC_Config(void)
{
	//配置GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure ={0};
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	//配置ADC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//6分频
	
	ADC_InitTypeDef ADC_InitStructure = {0};
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立工作
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续工作
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_NbrOfChannel = 3;//3通道
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式(多通道)
	ADC_Init(ADC1,&ADC_InitStructure);
	//开启3个通道 PA1 PA2 PA3
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,2,ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,3,ADC_SampleTime_239Cycles5);
	ADC_Cmd(ADC1,ENABLE);
	//配置DAM
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	DMA_InitTypeDef DMA_InitStructure = {0};
	DMA_InitStructure.DMA_BufferSize 			= 30;//DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&ADC1->DR;//DMA 外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)Data;//DMA 存储器基地址
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralSRC;//外设作为数据传输的来源
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_HalfWord;//外设数据宽度:16
	DMA_InitStructure.DMA_MemoryDataSize		= DMA_MemoryDataSize_HalfWord;//内存数据宽度:16
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable;//外设地址不递增
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable;//内存地址递增
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Circular;//工作在循环缓存模式
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable;// 禁止存储器到存储器模式,因为是从外设到存储器
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_High;//高优先级
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//选择通道1
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	ADC_DMACmd(ADC1,ENABLE);
	
	// 初始化ADC 校准寄存器
	ADC_ResetCalibration(ADC1);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	// ADC开始校准
	ADC_StartCalibration(ADC1);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	// 由于没有采用外部触发,所以使用软件触发ADC转换
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
void ADC_ReadData(void)
{
	uint8_t i=0;
	MQ2=0;
	MQ135=0;
	Light=0;
	//循环求出十组
		for(i=0;i<10;i++)
	{
		MQ135= MQ135+Data[i*3];
		MQ2=MQ2+Data[i*3+1];
		Light = Light+Data[i*3+2];
	}
	//均值滤波
	MQ135=MQ135/10.0;
	MQ2=MQ2/10.0;
	Light=Light/10.0;
	printf("有毒气体参数 = %d\r\n",MQ135);
	printf("烟雾浓度参数 = %d\r\n",MQ2);
	printf("光照强度参数 = %d\r\n",Light);	
}

五、RTC实时时钟与RS485模块

实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,可提供时钟日历的功能,同时,由于RTC位于后备区,而不是与外设同属1.8V供电区,所以它还具有掉电不丢失数据的特性。
RTC在系统框图中与LSE、LSI、HSE相连,在本项目中,使用LSE作为RTC的时钟源,这是因为LSI是专门给内部看门狗提供时钟的,而HSE是专门给系统提供时钟的。想要使用RTC功能,还需要对定时器的分频器与计数值进行配置,并根据需要对中断进行配置。但是由于RTC位于后备区,想要对其配置首先需要使能后备区访问并复位后备区才能够实现。
SP3485器件属于符合RS-485和RS-422串行协议的+3.3V低功耗半双工收发器家族。SP3485在引脚上兼容Sipex的SP481和SP485,符合热门的行业标准。SP3485器件凭借Sipex的双极型CMOS工艺特性,可实现低功耗操作,而不影响性能。在带负载情况下,SP3485可实现最高10Mbps的数据传输,满足RS-485和RS-422串行协议的电气规格。硬件接线上。需要将中控端的485模块与节点端的485模块的A脚与B脚一一对应相连,同时,二者之间的通信需要设置统一的波特率与数据位校验位等参数。

**解析获得时间**
/*****************************************************************
 *函 数 名 称:WIFI_BackControl
 *函 数 功 能:WIFI接受解析
 *函 数 形 参:无
 *函 数 返 回:无
 *作       者:CYM
 *修 改 日 期:xx/xx/xx
*******************************************************************/
//struct tm NowTime = {0};
void WIFI_BackControl(void)
{

	if(USART6_Data.Rx_flag != 1)
	{
		return;
	}
	uint8_t i=0;
	uint32_t AliTime=0;
	char *p = NULL;  
	char buff[12] = {0};
	 if(strstr((char*)USART6_Data.Rx_buff+10,"serverSendTime")!=NULL)
	{
		p = strstr((char *)(USART6_Data.Rx_buff+10),"serverRecvTime");
		p+=17;//得到时间的首地址 "1718087481"
		for(i=0;i<10;i++)
		{
			buff[i] = p[i];
		}
		AliTime = atoi(buff);
    // 打印获取到的网络时间
    printf("获取到网络时间:%s---%d\r\n", buff, AliTime);
		  set_time(AliTime+28800);
			MX_RTC_Init();
	}
			USART6_ClearData();
}
struct tm *local_time;
void set_time(uint32_t seconds)
{
    // 使用localtime将秒数转换为本地时间结构体
    local_time = localtime(&seconds);

    if (local_time == NULL) 
			{
        perror("localtime");
			}
    // 打印转换后的时间信息
    printf("Year: %d\n", local_time->tm_year + 1900); // tm_year表示自1900年起的年数
    printf("Month: %d\n", local_time->tm_mon + 1);    // tm_mon表示月份(0-11),所以要加1
    printf("Day: %d\n", local_time->tm_mday);         // tm_mday表示一个月中的日数(1-31)
    printf("Hour: %d\n", local_time->tm_hour);        // tm_hour表示小时(0-23)
    printf("Minute: %d\n", local_time->tm_min);       // tm_min表示分钟(0-59)
    printf("Second: %d\n", local_time->tm_sec);       // tm_sec表示秒数(0-59)

}

rtc获取时间
在这里插入图片描述
屏幕显示
在这里插入图片描述
效果
在这里插入图片描述

六、继电器设置

**继电器 .c**
#include "rly.h"
/*****************************************************************
函 数 名 称:Relay_Config
函 数 功 能:初始化继电器
函 数 形 参:无
函 数 返 回:无
			RLY1--PC11
			RLY2--PC10
*******************************************************************/
void Relay_Config(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure = {0};
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	//起始为高电平  继电器关闭
	RELAY1_OFF();
	RELAY2_OFF();
}

**.h**
#ifndef __RLY_H
#define __RLY_H
#include "stm32f10x.h"

#define RELAY1_ON() GPIO_ResetBits(GPIOC,GPIO_Pin_11)
#define RELAY2_ON() GPIO_ResetBits(GPIOC,GPIO_Pin_10)//低
#define RELAY1_OFF() GPIO_SetBits(GPIOC,GPIO_Pin_11)//高
#define RELAY2_OFF() GPIO_SetBits(GPIOC,GPIO_Pin_10)
void Relay_Config(void);

#endif
  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值