基于STM32的温湿度监测系统:最小系统板上的实时追踪

目录

  • 功能概述
  • 所用硬件
  • 演示效果
  • 程序部分
  • DHT11温湿度的获取
  • DS18B20温度的获取
  • ESP8266的使用
  • Socket服务器的创建
  • 主函数部分
  • 调试及下载
  • 功能概述

    基于STM32F103C8T6最小系统板制作的温湿度监测系统,监测温度和湿度并通过WIFI模块将监测的数据传输到电脑上的Socket服务器端。

    所用硬件

    STM32F103C8T6最小系统板、DHT11温湿度传感器、DS18B20温度传感器、ESP8266-01SWIFI模块。

    注:传感器可只用DHT11,DS18B20可有可无。

    演示效果

    实物图:

    服务器测试图:

    程序部分

    DHT11温湿度的获取

    关于DHT11温湿度传感器的使用,网上有很多详细的资料,这里只进行简单的说明。

    这是DHT11的通讯过程,我们需要通过引脚先将DHT11置于低电平至少18ms,再置高电平20-40us。然后DHT11会先返回80us的低电平信号,再返回一个80us的高电平信号,之后开始传送数据。

    代码部分

    dht11.h

    #ifndef __DHT11_H
    #define __DHT11_H
    #include "stm32f10x.h"
    void DHT11_GPIO_OUT(void);
    void DHT11_GPIO_IN(void);
    uint8_t DHT_Read_Byte(void);
    uint8_t DHT_Read(void);
    #endif
    

    dht11.c

    #include "Delay.h"
    #include "dht11.h"
    uint8_t dat[5]={0x00,0x00,0x00,0x00,0x00};
    uint8_t sum = 0;
    
    /*
    *初始化输出
    */
    void DHT11_GPIO_OUT(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
    		
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
     	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /*
    *初始化输入
    */
    void DHT11_GPIO_IN(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    		
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;	
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
      	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /*
    *读取一个字节
    */
    uint8_t DHT_Read_Byte(void)
    {
    	uint8_t temp;
    	uint8_t ReadDat = 0;
    	
    	uint8_t retry = 0;
    	uint8_t i;
    	for(i=0; i<8;i++)
    	{	
    		//读取一位数据时,会先收到低电平信号50us
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0 && retry< 100)
    		{
    			Delay_us(1);
    			retry++;
    		}
    		retry = 0;
    		Delay_us(30);
    		temp = 0;
    		//再接收高电平信号16us-28us(代表数据0)或高电平信号70us(代表数据1)
    		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)==1) temp=1;
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 1 && retry<100)
    		{
    			Delay_us(1);
    			retry++;
    		}
    		retry = 0;
    		ReadDat<<=1;
    		ReadDat|=temp;
    	}
    	return ReadDat;
    }
    
    /*
    *读取一次数据
    */
    uint8_t DHT_Read(void)
    {
    	uint8_t i;
    	uint8_t retry = 0;
    	//总线设置为输出并拉低18ms
    	DHT11_GPIO_OUT();
    	GPIO_ResetBits(GPIOB, GPIO_Pin_14);
    	Delay_ms(18);
    	//总线再拉高40ms
    	GPIO_SetBits(GPIOB, GPIO_Pin_14);
    	Delay_us(40);
    	//总线设置为输入
    	DHT11_GPIO_IN();
    	Delay_us(20);
    	//等待响应信号为低电平
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
    	{	
    		//等待响应信号为高电平,超时等待100us
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)==0&&retry<100)
    		{
    			Delay_us(1);
    			retry++;
    		}
    		retry=0;
    		//等待数据传送,超时等待100us
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)==1&&retry<100)
    		{
    			Delay_us(1);
    			retry++;
    		}
    		retry=0;
    		//循环读取数据,dat[0]为湿度整数数据,dat[1]为湿度小数数据,dat[2]为温度整数数据,dat[3]为温度小数数据,dat[4]为校验和数据
    		for(i=0;i<5;i++)
    		{
    			dat[i] = DHT_Read_Byte();
    		}
    		Delay_us(50);
    	}
    	//当校验和数据=湿度整数数据+湿度小数数据+温度整数数据+温度小数数据,说明正确传送。
    	sum = dat[0] + dat[1] + dat[2] + dat[3];
    	if(dat[4]  == sum)
    	{
    		return 1;
    	}
    	else
    		return 0;
    }
    

    DS18B20温度的获取

    没有使用DS18B20可跳过这部分
    关于DS18B20的初始化时序图以及读写时序图网上有很多详细的资料,这里不再赘述,直接上代码。

    ds18b20.h

    #ifndef __DS18B20_H
    #define __DS18B20_H
    #include "stm32f10x.h"
    #include "stdio.h"
    
    void DS18B20_GPIO_OUT(void);
    void DS18B20_GPIO_IN(void);
    void DS18B20_RST(void);
    int DS18B20_Check(void);
    uint8_t DS18B20_Read_Bit(void);
    void DS18B20_Write_One(void);
    void DS18B20_Write_Zero(void);
    uint8_t DS18B20_Read_Byte(void);
    void DS18B20_Write_Byte(uint8_t data);
    void DS18B20_Start(void);
    float DS18B20_Get_Temp(void);
    
    #endif
    

    ds18b20.c

    #include "ds18b20.h"
    #include "Delay.h"
    /*
    *初始化输出
    */
    void DS18B20_GPIO_OUT(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
    		
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
      	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /*
    *初始化输入
    */
    void DS18B20_GPIO_IN(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    		
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;	
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
      	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /*
    *复位ds18b20
    */
    void DS18B20_RST(void)
    {
    	DS18B20_GPIO_OUT();
    	GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(750);
    	GPIO_SetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(15);
    }
    
    /*
    *检测ds18b20的存在
    */
    int DS18B20_Check(void)
    {
    	uint8_t retry = 0;
    	DS18B20_GPIO_IN();
    	while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1 && retry<200)
    	{
    		retry++;
    		Delay_us(1);
    	}
    	if(retry>=200) return 1;//等待超时
    	else retry = 0;
    	
    	while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0 && retry<240)
    	{
    		retry++;
    		Delay_us(1);
    	}
    	if(retry>=240) return 1;
    	
    	return 0;
    }
    
    /*
    *读取一位数据
    */
    uint8_t DS18B20_Read_Bit(void)
    {
    	uint8_t data;
    	DS18B20_GPIO_OUT();
    	GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(2);
    	GPIO_SetBits(GPIOB, GPIO_Pin_13);
    	DS18B20_GPIO_IN();
    	Delay_us(10);
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1)
    		data = 1;
    	else data =0;
    	Delay_us(50);
    	return data;
    }
    
    /*
    *进行写数据1操作
    */
    void DS18B20_Write_One(void)
    {
    	DS18B20_GPIO_OUT();
    	GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(2);
    	GPIO_SetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(60);
    }
    
    /*
    *进行写数据0操作
    */
    void DS18B20_Write_Zero(void)
    {
    	DS18B20_GPIO_OUT();
    	GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(60);
    	GPIO_SetBits(GPIOB, GPIO_Pin_13);
    	Delay_us(2);
    }
    
    /*
    *读取一个字节
    */
    uint8_t DS18B20_Read_Byte(void)
    {
    	uint8_t data = 0;
    	for(uint8_t i=0;i<8;i++)
    	{
    		data>>=1;
    		if(DS18B20_Read_Bit())
    		{
    			data |=0x80;
    		}
    	}
    	return data;
    }
    
    /*
    *写入一个字节
    */
    void DS18B20_Write_Byte(uint8_t data)
    {
    	DS18B20_GPIO_OUT();
    	for(int i =1;i<=8;i++)
    	{
    		if(data&0x01)
    			DS18B20_Write_One();
    		else
    			DS18B20_Write_Zero();
    		data >>=1;
    	}
    }
    
    /*
    *初始化ds18b20
    */
    void DS18B20_Start(void)
    {
    	DS18B20_RST();
    	DS18B20_Check();
    	DS18B20_Write_Byte(0xcc);
    	DS18B20_Write_Byte(0x44);
    }
    
    /*
    *温度获取
    */
    float DS18B20_Get_Temp(void)
    {
    	float data;
    	uint8_t TH,TL;
    	uint16_t TB;
    	DS18B20_Start();
    	DS18B20_RST();
    	DS18B20_Check();
    	DS18B20_Write_Byte(0xCC);
    	DS18B20_Write_Byte(0xBE);
    	TL = DS18B20_Read_Byte();
    	TH = DS18B20_Read_Byte();
    	
    	TB = TH;
    	TB <<=8;
    	TB = TB|TL;
    	data = TB * 0.0625;
    	return data;
    }
    

    ESP8266的使用

    同样的esp8266的工作模式的使用,AT指令的调试这里不再介绍,网上有相关的AT指令介绍。

    我是通过stm32上的串口和esp8266进行通信,我们使用的是AT指令中的STA工作模式(通过让esp8266连接其他wifi进行工作),通过透传的方式让笔记本电脑和esp8266连接同一wifi进行通信。

    注:esp8266的工作对工作电压有着一定要求,如果使用stm32进行供电可能会因为供电不足导致,esp8266发送乱码的现象出现。

    代码部分

    UART.h

    #ifndef __UART_H
    #define __UART_H
    #include "stm32f10x.h"
    #include "stdio.h"
    void USART_Init_Config(u32 bound);
    #endif
    

    UART.c

    #include "UART.h"
    /*
    *初始化串口PA9和PA10
    *u32 bound设置串口波特率
    */
    void USART_Init_Config(u32 bound)
    {
    	GPIO_InitTypeDef GPIO_InitStruct;
    	USART_InitTypeDef USART_InitStruct;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE);
    	
    	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);
    	
    	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);
    	
    	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    	USART_InitStruct.USART_Parity = USART_Parity_No;
    	USART_InitStruct.USART_StopBits = USART_StopBits_1;
    	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    	USART_InitStruct.USART_BaudRate = bound;
    	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	USART_Init(USART1,&USART_InitStruct);
    	
    	USART_Cmd(USART1,ENABLE);
    	
    }
    
    /*
    *重写fputc,这样我们可以直接使用printf()来发送指令
    */
    int fputc(int ch,FILE *f)
    {
    	
    	USART_SendData(USART1,(u8)ch);
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	return ch;
    }
    

    esp8266.h

    #ifndef __ESP8266_H
    #define __ESP8266_H
    #include "stm32f10x.h"
    #include "string.h"
    void ESP_Init(void);
    void ESP_SendMeassage(u8 hum, float tem);
    #endif
    

    esp8266.c

    #include "esp8266.h"
    #include "UART.h"
    #include "Delay.h"
    /*
    *初始化esp8266
    */
    void ESP_Init(void)
    {	
    	//设置工作模式为STA模式
    	printf("AT+CWMODE=1\r\n");
    	//延时500ms指令有足够时间操作
    	Delay_ms(500);
    	//设置esp8266要连接的wifi信息
    	printf("AT+CWJAP=\"WIFI名称\",\"WIFI密码\"\r\n");
    	Delay_ms(3000);
    	//设置连接的服务端的信息,这里的ip地址和端口号与后面socket服务端使用的ip地址和端口号是一致的
    	printf("AT+CIPSTART=\"TCP\",\"ip地址例如192.168.1.9\",端口号例如8080\r\n");
    	Delay_ms(3000);
    }
    /*
    *发送温度湿度信息
    *hum是dht11发送的,temp是ds18b20发送的
    */
    void ESP_SendMeassage(u8 hum, float tem)
    {
    	//进入透传模式
    	printf("AT+CIPMODE=1\r\n");
    	Delay_ms(500);
    	//开始发送数据
    	printf("AT+CIPSEND\r\n");
    	Delay_ms(500);
    	
    	printf("湿度:%d%%,温度:%f度\r\n",hum, tem);
    	
    	//退出发送数据
    	printf("+++");
    	Delay_ms(100);
    	//退出透传模式
    	printf("AT+CIPMODE=0\r\n");
    	Delay_ms(100);
    }
    

    Socket服务器的创建

    关于套机字通信可以看看这个作者写的: 套接字编程.

    先来看看这里的socket通信过程

    与一般的socket编程不同的地方在于,我们的服务器端只需要接收数据,不需要向客户端发送数据,同时我们只需要进行服务器端的socket编程,无需写客户端部分的代码,只需交给esp8266来完成即可。

    服务端代码
    注意这部分是单独在vs中进行编程的,并不在项目工程当中。

    server.cpp

    #include <iostream>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    using namespace std;
    int main()
    {
    	//服务器ip地址,端口号
    	const char* host = "192.168.1.9";
    	unsigned int port = 8080;
    
    	WSADATA wsaData;
    	int len;
    	int recv_len;
    	SOCKET  sock_server, sock_client;
    	SOCKADDR_IN server_addr, client_addr;
    	server_addr.sin_family = AF_INET;
    	inet_pton(AF_INET, host, &server_addr.sin_addr);
    	server_addr.sin_port = htons(port);
    
    	//初始化套接字
    	WORD w_req = MAKEWORD(2, 2);
    	if (WSAStartup(w_req, &wsaData) != 0)
    	{
    		WSACleanup();
    		cout << "初始化套接字失败" << endl;
    	}
    	
    	//创建一个套接字
    	sock_server = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock_server == INVALID_SOCKET)
    	{
    		cout << "套接字创建失败" << endl;
    		WSACleanup();
    		exit(1);
    	}
    
    	//绑定套接字
    	if (bind(sock_server, (SOCKADDR*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
    	{
    		cout << "套接字绑定失败" << endl;
    		WSACleanup();
    		exit(1);
    	}
    	else
    	{
    		cout << "套接字绑定成功" << endl;
    		cout << "IP:" << host << endl;
    		cout << "端口:" << port << endl;
    	}
    
    	//监听套接字
    	if (listen(sock_server, SOMAXCONN) < 0)
    	{
    		cout << "套接字设置监听失败" << endl;
    		WSACleanup();
    		exit(1);
    	}
    	else
    	{
    		cout << "套接字设置监听成功" << endl;
    	}
    
    	cout << "等待连接建立" << endl;
    
    	//接收连接请求
    	len = sizeof(client_addr);
    	sock_client = accept(sock_server, (SOCKADDR*)&client_addr, &len);
    	if (sock_client == SOCKET_ERROR)
    	{
    		cout << "套接字连接失败" << endl;
    		WSACleanup();
    		return 0;
    	}
    
    	cout << "连接建立,等待数据接收" << endl;
    	//接收数据
    	while (1)
    	{
    		char recv_buf[100] = { "\0" };
    		recv_len = recv(sock_client, recv_buf, 100, 0);
    		if (recv_len < 0)
    		{
    			cout << "接收失败" << endl;
    		}
    		else
    		{
    			cout << recv_buf << endl;
    		}
    	}
    
    	closesocket(sock_server);
    	closesocket(sock_client);
    	return 0;
    }
    

    主函数部分

    main.c

    #include "stm32f10x.h"
    #include "UART.h"
    #include "dht11.h"
    #include "Delay.h"
    #include "ds18b20.h"
    #include "esp8266.h"
    
    //dat读取dht11数据
    extern u8 dat[5];
    int main(void)
    {	
    	//data用于读取ds18b20的数据
    	float data;
    	//初始化串口,波特率设置为115200
    	USART_Init_Config(115200);
    	//初始化esp8266
    	ESP_Init();
    	//进行温湿度的读取
    	while(1)
    		{
    				//当DHT11得到数据时
    				if(DHT_Read())
    			{	
    				//ds18b20也读取一次数据
    				data = DS18B20_Get_Temp();
    				ESP_SendMeassage(dat[0], data);
    				/*
    				若不使用ds18b20,则使用这段代码,将ESP_SendMeassge()注释掉。
    				printf("湿度:%d%%,温度:%d度\r\n",dat[0],dat[2]);
    				*/
    				Delay_ms(3000);	
    			}
    			
    		}		
    			
    }
    

    调试及下载

    将程序烧录到stm32中,串口PA9和PA10与esp8266的RX和TX连接,PB14与dht11数据端连接,PB13与ds18b20数据端连接,将其余的VCC和GND接好。电脑上先运行socket服务端,再将stm32上电,等待数据发送至服务端。

    有需要可自行下载: 项目链接

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于STM32的温湿度监测系统:最小系统板上的实时追踪

    发表回复