基于STM32的温湿度监测系统:最小系统板上的实时追踪
目录
功能概述
基于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上电,等待数据发送至服务端。
有需要可自行下载: 项目链接。