一、项目需求
1. 使用 mq-2 获取环境烟雾值,并显示在 LCD1602 上;
2. 按键修改阈值,并显示在 LCD1602 上;
3. 烟雾值超过阈值时,蜂鸣器长响,风扇打开;烟雾值小于阈值时,蜂鸣器不响,风扇关闭;
二、项目框图
三、介绍烟雾传感器(MQ-2)
四、项目实现
复制项目文件夹19-串口打印功能
重命名为46-烟雾报警系统项目
打开项目工程,加载文件
main.c
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
#include "key.h"
#include "LCD1602.h"
#include "fan.h"
#include "beep.h"
uint16_t adc_result = 0;
int main(void)
{
float smoke_value = 0;
float limit_value = 1.0;
uint8_t key_num = 0;
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init();//初始化led灯
uart1_init(115200);
//adc
adc_dma_init();
key_init();
lcd1602_init();
beep_init();
fan_init();
printf("hello world!\r\n");
lcd1602_show_line(1,1,"Smoke: 0.0 V");
lcd1602_show_line(2,1,"Limit: - 1.0 +");
while(1)
{
key_num = key_scan();
if(key_num == 1)
limit_value +=0.1;
else if(key_num == 2)
limit_value -=0.1;
//通过按键更改阈值
lcd1602_display_limit(limit_value);
smoke_value = adc_get_smoke();
lcd1602_display_smoke(smoke_value);
//判断smoke_value的值是不是大于limit_value
//判断烟雾的值是不是大于我们限定的值
if(smoke_value > limit_value)
{
beep_ON();
fan_ON();
}
else
{
beep_OFF();
fan_OFF();
}
}
}
LCD1602.c
adc.c
#include "adc.h"
DMA_HandleTypeDef dma_handle = {0};//DMA的句柄
ADC_HandleTypeDef adc_handle = {0};//ADC的句柄
uint16_t adc_value = 0;
//初始化ADC函数
void adc_config(void)
{
adc_handle.Instance = ADC1;//基地址
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;//要不要扫描?不需要扫描
adc_handle.Init.ContinuousConvMode = ENABLE;//连续模式?连续转换模式
adc_handle.Init.NbrOfConversion = 1;//转换个数?1个
adc_handle.Init.DiscontinuousConvMode = DISABLE;//间断模式?不间断模式
adc_handle.Init.NbrOfDiscConversion = 0;//间断个数0
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;//触发方式:软件触发
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);//ADC校准
}
//初始化msp函数
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)//判断是不是ADC1
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//ADC时钟句柄
GPIO_InitTypeDef gpio_init_struct = {0};//GPIO的句柄
__HAL_RCC_ADC1_CLK_ENABLE();//打开ADC1的时钟
__HAL_RCC_GPIOB_CLK_ENABLE();//打开GPIO口的时钟
gpio_init_struct.Pin = GPIO_PIN_0;//引脚
gpio_init_struct.Mode = GPIO_MODE_ANALOG;//模式:虚拟量的输入
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;//外设的时钟选择:ADC
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频?6分频
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);//ADC时钟初始化函数
}
}
void dma_config(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;//通道1
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;//半字对齐
dma_handle.Init.MemInc = DMA_MINC_ENABLE;//内存增量失能模式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//半字对其
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;//内存增量失能模式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;//DMA优先级
dma_handle.Init.Mode = DMA_CIRCULAR;//模式:循环搬运模式
HAL_DMA_Init(&dma_handle);
//这行代码的作用是将ADC的句柄(adc_handle)与DMA的句柄(dma_handle)关联起来
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);//当ADC完成数据采集时,数据可以直接通过DMA传输到指定的内存地址,而不需要CPU的干预。
}
//通道配置的函数
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime)
{
//
ADC_ChannelConfTypeDef adc_ch_config = {0};
adc_ch_config.Channel = ch;//指定通道-外界传进来
adc_ch_config.Rank = rank;//序列-外界传进来
adc_ch_config.SamplingTime = stime;//取样时间-外界传进来
HAL_ADC_ConfigChannel(hadc, &adc_ch_config);//通道配置
}
//adc dma 初始化函数
void adc_dma_init(void)
{
adc_config();
//adc句柄,通道,序列号,ADC采样的时间
adc_channel_config(&adc_handle, ADC_CHANNEL_8, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
dma_config();
//启动adc,ADC转化完的数据通过DMA搬运出来,从ADC外设传输到存储器的长度
HAL_ADC_Start_DMA(&adc_handle, (uint32_t *)&adc_value, 1);//mar是目标缓冲区的地址
}
//定义一个函数:把获取电压的值给返回出来
float adc_get_smoke(void)
{
return (float)adc_value / 4096 * 3.3;
}