今天来讲一下本次的项目,一个采集环境数据,并控制的简单项目。
实物获取和资料代码获取我会放在最后面。
其主要功能如下:
1、采集温湿度 2、采集烟雾浓度 3、蜂鸣器报警同时向手机终端发送报警信息 4、继电器控制风扇和加热片 5、OLED显示 6、蓝牙控制
先看实物,在来说说具体的功能
OLED显示屏上会显示出当前的温度,湿度和烟雾浓度,还有极限值,当温度超过温度极限值时,继电器会吸合,打开风扇降温,低于极限值时,另一个继电器吸合打开加热片。湿度同理,也会有一样的控制效果。除了自动控制以外,还可以通过手机app来控制。其中按键是用来调节温湿度极限值的。
接下来,我们一个一个模块来看,先看最简单的mq-2烟雾传感器。
接下来,我们看接线
mq-2有四个引脚,D0引脚悬空,A0引脚接单片机的adc引脚,这里不是pc2引脚,程序上是哪个,就接哪个。
接下来,我们看程序上如何配置adc
void Adc_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA| RCC_APB2Periph_ADC1 | RCC_APB2Periph_AFIO, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //72M/6=12,ADC最大时间不能超过14M
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //PB0/1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PA6 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//------------------------------------ADC设置--------------------------------------------------------
ADC_DeInit(ADC1); //将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode =ENABLE; //多信道扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发转换关闭
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 3; //此处开3个信道(可开的为1~16)
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//ADC常规信道配置
//ADC1,ADC通道x,规则采样顺序值为y,采样时间为239.5周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_55Cycles5 ); //PA6
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 2, ADC_SampleTime_55Cycles5 ); //PB0
ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 3, ADC_SampleTime_55Cycles5 ); //PB1
// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
ADC_DMACmd(ADC1, ENABLE); //使能ADC1的DMA传输
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //复位指定的ADC1的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //获取ADC1复位校准寄存器的状态,设置状态则等待
ADC_StartCalibration(ADC1); //开始指定ADC1的校准状态
while(ADC_GetCalibrationStatus(ADC1)); //获取指定ADC1的校准程序,设置状态则等待
}
配置完成后,在主程序中直接获取adc的值即可。
m = Get_Adc_Average_mq2();
至于dht11温湿度模块,用起来也很简单,至于其中的原理我们日后再细说,今天要讲的是整个项目如何构建。dht11的话,网上随便移植一个代码,在主函数一句话调用就完事了。下面给出一个dht11的代码供大家使用,我也移植来的。
#include "dht11.h"
#include "delay.h"
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //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;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PB11端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化IO口
GPIO_SetBits(GPIOB,GPIO_Pin_11); //PB11 输出高
DHT11_Rst(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
//接口连接:DATA->PB11
//IO方向设置
#define DHT11_IO_IN() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=3<<12;}
IO操作函数
#define DHT11_DQ_OUT PBout(11) //数据端口 PB11
#define DHT11_DQ_IN PBin(11) //数据端口 PB11
u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
dht11的接线也十分的简单,只有一个数据引脚,剩下的vcc接vcc,gnd接gnd即可。
蜂鸣器和继电器就没有什么好说的了,给一个高低电平就能够完成对他的操作,蜂鸣器低电平时,就会发出声响,高电平就会停止鸣叫,继电器也是一样的,高低电平完成他的开和关。最简单的写法,用if判断温湿度,随后决定给高电平还是低电平。
if(temp>=temp_max||temp<temp_min) //温度过高,打开风扇和蜂鸣器
{
//relay_on();
GPIO_ResetBits(GPIOB,GPIO_Pin_7);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
delay_ms(1000);
GPIO_SetBits(GPIOB,GPIO_Pin_5);
Serial_SendString("温度过高,请及时处理\r\n");
}
其中最后一句是发送报警信息给手机的,利用的是蓝牙jdy-31,和大家所熟悉的hc-05差不多,在使用上没有什么区别。
蓝牙主要的是利用串口通信的原理,tx和rx发送接收信息。
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
#include <hc_05.h>
char Serial_RxPacket[100]; //USART1_IRQHandler那接收数据帧中的数据部分存储位置
uint8_t Serial_RxFlag;
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 这两行代码用来使能USART1和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART1的通信参数
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
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);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能USART1的接收中断
// 配置中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置USART1的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE); //用来使能USART1
}
//这个函数接受一个8位无符号整数(Byte)作为参数,将该字节数据发送到USART1串口
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//RESET=0
}
//通过串口发送一个字节数组的数据。做法是函数接受一个指向字节数组的指针(Array)和数组的长度(Length)作为参数,然后通过循环将数组中的每个字节逐个发送
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
//函数接受一个指向字符数组的指针(String),表示要发送的字符串数据。
//用法是它使用循环遍历字符串中的每个字符,并调用Serial_SendByte()函数将字符逐个发送出去,直到遇到字符串结束符’\0’
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
//一个算法,即底数X的Y次幂的值。接受两个正整数参数:X(底数)和Y(指数)。它使用一个while循环,将底数X连续乘以自身Y次,得到结果Result。初始时,结果Result被初始化为1
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
//Number表示要发送的数字,Length表示要发送的数字的位数。它使用一个循环,从最高位开始逐位地发送数值
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');//Serial_Pow(10, Length - i - 1)是用来获取当前位的除数,实现数字的右移操作
}
}
//该函数的目的是将ch的值通过串口发送出去,并返回相同的值。即Serial_SendByte()被调用以将ch的值通过串口发送出去。最后,函数返回ch的值。
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
//通过串口打印格式化的字符串,实现类似printf()的功能
void Serial_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
/**************************************以上跟江科大完全一样************************************/
void USART1_IRQHandler(void)//USART1串口中断服务函数
{
static uint8_t flag = 0;
static uint8_t pRxPacket = 0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//先通过USART_GetITStatus()函数检查USART1接收中断标志位是否置位,如果RXNE置一了,就进if判断
{
uint8_t RxData = USART_ReceiveData(USART1);//调用USART_ReceiveData()函数读取接收寄存器中的数据到变量RxData中
if (flag == 0)
{
if (RxData == '0' )//RxData为0,表示接收到了数据帧的起始符号
{
flag = 1;
pRxPacket = 0;
}
}
else if (flag == 1)
{
if (RxData == '1')//如果接收到字符’1',表示接收到了数据帧的结束符号
{
flag = 0;
Serial_RxPacket[pRxPacket] = '\0';//表示接收到了数据帧的终止符号,将状态切换为0,并在数据包末尾添加字符串结束符号’\0’
Serial_RxFlag = 1;//表示接收到了完整的数据包
}
else//否则,将接收到的数据存储到Serial_RxPacket数组中
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);//手动清除USART1接收中断标志位,以结束中断处理过程
}
}
//在蓝牙上写"0on1"即可实现打开灯,"0off1"关闭灯
我们来看一看在主函数中,是如何使用的。
if (Serial_RxFlag == 1)
{
if (strcmp(Serial_RxPacket, "on") == 0)
{
//LED1_ON();
GPIO_SetBits(GPIOC, GPIO_Pin_13);
Serial_SendString("LED_ON\r\n");
}
else if (strcmp(Serial_RxPacket, "off") == 0)
{
//LED1_OFF();
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
Serial_SendString("LED_OFF\r\n");
我这里写的是,判断接收的信息,是不是“on”,是的话,给pc13引脚高电平,在判断是不是“off”,是的话给低电平。这里引脚改成继电器或者蜂鸣器的引脚都可以,通过改引脚来实现自己想要的结果。
这个项目还是比较简单的,想要实物的话可以联系
成品实物获取链接:https://m.tb.cn/h.5OOYu2j?tk=qDqLWf2di9U CZ3457
资料获取链接:【免费】oled,dht11,mq-2,stm32f103c8t6等相关参考资料资源-CSDN文库