目录
一.项目概述
1.项目功能描述
本项目为基于STM32的环境监测预警防护系统,以STM32f103c8t6为控制芯片,通过DHT11温湿度传感器和MQ-2烟雾传感器实时监测环境的温湿度和烟雾值,将数据信息显示在LCD屏幕上,并通过蓝牙串口通信将信息传输给上位机。当环境中温湿度超过阈值时,系统将自动打开风扇进行通风,当烟雾值超过设定阈值时,将关闭风扇防止火灾蔓延,并打开警报器,同时在屏幕和上位机显示警报信息,用户可以修改温湿度阈值。
2.物料清单
实物图
清单
STM32F103C8T6开发板 | LCD1602 | 烟雾传感器MQ-2 | DHT11温湿度传感器 |
HC05蓝牙模块 | SUB转TTL | 蜂鸣器 | 继电器 |
电池 | 直流电机和风扇 | 面包板两个 | 杜邦线若干 |
LED等若干 | 按键三个 | 安卓手机 |
3.硬件接线和软件代码
1.接线
STM32 | LCD1602 | MQ-2 | DHT11 | HC05 | SUB转TTL | 蜂鸣器 | 继电器 | 按键1 | 按键2 | 按键3 | 按键4 | 按键5 |
5V | VCC | 5V | ||||||||||
3.3V | VCC,A | VCC | VCC | VCC | ||||||||
GND | VSS,V0,K | GND | GND | GND | GND | GND | √ | √ | √ | √ | √ | |
A0 | D0 | |||||||||||
A1 | D1 | |||||||||||
A2 | D2 | |||||||||||
A3 | D3 | |||||||||||
A4 | D4 | |||||||||||
A5 | D5 | |||||||||||
A6 | D6 | |||||||||||
A7 | D7 | |||||||||||
A8 | ||||||||||||
A9 | RXD | RX | ||||||||||
A10 | TXD | TX | ||||||||||
A11 | ||||||||||||
A12 | DATA | |||||||||||
A15 | ||||||||||||
B0 | A0 | |||||||||||
B1 | RS | |||||||||||
B3 | ||||||||||||
B4 | ||||||||||||
B5 | I/O | |||||||||||
B6 | ||||||||||||
B7 | ||||||||||||
B8 | ||||||||||||
B9 | ||||||||||||
B10 | RW | |||||||||||
B11 | E | |||||||||||
B12 | ||||||||||||
B13 | √ | |||||||||||
B14 | √ | |||||||||||
B15 | √ |
2.完整代码
完整代码链接如下(百度网盘):
链接:https://pan.baidu.com/s/177G1yuDBALkuub9O0EpqMA
提取码:5210
二.LCD1602
1.简介与功能描述
在LCD显示屏上实时显示环境的温湿度,烟雾值,警报器,电机风扇的状态,以及修改温湿度和烟雾的阈值时的修改页面,包括提示信息和实时阈值。
2.模块代码
#include "dht11.h"
#include "delay.h"
#include "string.h"
#include "stdio.h"
#include "led.h"
char dht11_data[5] = {0};
uint8_t dht11_data_humidity_left = 0;
uint8_t dht11_data_humidity_after = 0;
uint8_t dht11_data_temperature_left = 0;
uint8_t dht11_data_temperature_after = 0;
float dht11_data_humidity = 0.0;
float dht11_data_temperature = 0.0;
extern float limit_value; //串口打印烟雾阈值时使用
void dht11_gpio_input(void)
{
GPIO_InitTypeDef gpio_initstruct;
DHT11_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_INPUT; //输入
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
void dht11_gpio_output(void)
{
GPIO_InitTypeDef gpio_initstruct;
DHT11_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
void dht11_start(void)
{
dht11_gpio_output();
DHT11_DQ_OUT(1);
DHT11_DQ_OUT(0);
delay_ms(20);
DHT11_DQ_OUT(1);
dht11_gpio_input();
while(DHT11_DQ_IN); //等待DHT11拉低电平
while(!DHT11_DQ_IN); //等待DHT11拉高电平
while(DHT11_DQ_IN); //等待DHT11拉低电平
}
uint8_t dht11_read_byte(void)
{
uint8_t temp = 0;
uint8_t i = 0;
uint8_t read_data = 0;
for(i = 0;i < 8;i ++ )
{
while(!DHT11_DQ_IN);
delay_us(50);
if(DHT11_DQ_IN == 1)
{
temp = 1;
while(DHT11_DQ_IN);
}
else
temp = 0;
read_data = read_data << 1;
read_data |= temp;
}
return read_data;
}
void dht11_read(uint8_t *result)
{
//printf("hhh");
uint8_t i = 0;
//printf("aaa\r\n");
dht11_start();
//printf("bbb\r\n");
dht11_gpio_input();
//printf("ccc\r\n");
for(i = 0;i < 5; i++)
dht11_data[i] = dht11_read_byte();
//printf("ddd\r\n");
if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] == dht11_data[4])
{
memcpy(result, dht11_data, 4);
dht11_data_humidity_left = 0;
dht11_data_humidity_after = 0;
dht11_data_temperature_left = 0;
dht11_data_temperature_after = 0;
//将温湿度信息传给4个变量,在main函数中调用对应get函数
dht11_data_humidity_left = dht11_data[0];
dht11_data_humidity_after = dht11_data[1];
dht11_data_temperature_left = dht11_data[2];
dht11_data_temperature_after = dht11_data[3];
dht11_data_humidity = dht11_data_humidity_left + dht11_data_humidity_after / 10.0;
dht11_data_temperature = dht11_data_temperature_left + dht11_data_temperature_after / 10.0;
}
}
uint8_t dht11_data_humidity_left_get(void)
{
return dht11_data_humidity_left;
}
uint8_t dht11_data_humidity_after_get(void)
{
return dht11_data_humidity_after;
}
uint8_t dht11_data_temperature_left_get(void)
{
return dht11_data_temperature_left;
}
uint8_t dht11_data_temperature_after_get(void)
{
return dht11_data_temperature_after;
}
float dht11_data_humidity_get(void)
{
return dht11_data_humidity;
}
float dht11_data_temperature_get(void)
{
return dht11_data_temperature;
}
三.烟雾传感器
1.简介与功能描述
通过ADC获取环境中烟雾传感器的值,并在模块代码中给出调用函数,抛出标志位。
2.模块代码
#include "adc.h"
ADC_HandleTypeDef adc_handle = {0};
DMA_HandleTypeDef dma_handle = {0};
uint16_t adc_value = 0;
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;
adc_handle.Init.DiscontinuousConvMode = DISABLE;
adc_handle.Init.NbrOfDiscConversion = 0;
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
GPIO_InitTypeDef gpio_init_struct = {0};
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
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_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
void dma_config(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;
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_handle.Init.Mode = DMA_CIRCULAR;
HAL_DMA_Init(&dma_handle);
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
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);
}
void adc_dma_init(void)
{
adc_config();
adc_channel_config(&adc_handle, ADC_CHANNEL_8, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
dma_config();
HAL_ADC_Start_DMA(&adc_handle, (uint32_t *)&adc_value, 1);
}
float adc_get_smoke(void)
{
return (float)adc_value / 4096 * 3.3;
}
四.DHT11
1.简介与功能描述
通过DHT11模块,获取环境中的温湿度值
2.模块代码
#include "dht11.h"
#include "delay.h"
#include "string.h"
#include "stdio.h"
#include "led.h"
char dht11_data[5] = {0};
uint8_t dht11_data_humidity_left = 0;
uint8_t dht11_data_humidity_after = 0;
uint8_t dht11_data_temperature_left = 0;
uint8_t dht11_data_temperature_after = 0;
float dht11_data_humidity = 0.0;
float dht11_data_temperature = 0.0;
extern float limit_value; //串口打印烟雾阈值时使用
void dht11_gpio_input(void)
{
GPIO_InitTypeDef gpio_initstruct;
DHT11_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_INPUT; //输入
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
void dht11_gpio_output(void)
{
GPIO_InitTypeDef gpio_initstruct;
DHT11_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
void dht11_start(void)
{
dht11_gpio_output();
DHT11_DQ_OUT(1);
DHT11_DQ_OUT(0);
delay_ms(20);
DHT11_DQ_OUT(1);
dht11_gpio_input();
while(DHT11_DQ_IN); //等待DHT11拉低电平
while(!DHT11_DQ_IN); //等待DHT11拉高电平
while(DHT11_DQ_IN); //等待DHT11拉低电平
}
uint8_t dht11_read_byte(void)
{
uint8_t temp = 0;
uint8_t i = 0;
uint8_t read_data = 0;
for(i = 0;i < 8;i ++ )
{
while(!DHT11_DQ_IN);
delay_us(50);
if(DHT11_DQ_IN == 1)
{
temp = 1;
while(DHT11_DQ_IN);
}
else
temp = 0;
read_data = read_data << 1;
read_data |= temp;
}
return read_data;
}
void dht11_read(uint8_t *result)
{
//printf("hhh");
uint8_t i = 0;
//printf("aaa\r\n");
dht11_start();
//printf("bbb\r\n");
dht11_gpio_input();
//printf("ccc\r\n");
for(i = 0;i < 5; i++)
dht11_data[i] = dht11_read_byte();
//printf("ddd\r\n");
if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] == dht11_data[4])
{
memcpy(result, dht11_data, 4);
dht11_data_humidity_left = 0;
dht11_data_humidity_after = 0;
dht11_data_temperature_left = 0;
dht11_data_temperature_after = 0;
//将温湿度信息传给4个变量,在main函数中调用对应get函数
dht11_data_humidity_left = dht11_data[0];
dht11_data_humidity_after = dht11_data[1];
dht11_data_temperature_left = dht11_data[2];
dht11_data_temperature_after = dht11_data[3];
dht11_data_humidity = dht11_data_humidity_left + dht11_data_humidity_after / 10.0;
dht11_data_temperature = dht11_data_temperature_left + dht11_data_temperature_after / 10.0;
}
}
uint8_t dht11_data_humidity_left_get(void)
{
return dht11_data_humidity_left;
}
uint8_t dht11_data_humidity_after_get(void)
{
return dht11_data_humidity_after;
}
uint8_t dht11_data_temperature_left_get(void)
{
return dht11_data_temperature_left;
}
uint8_t dht11_data_temperature_after_get(void)
{
return dht11_data_temperature_after;
}
float dht11_data_humidity_get(void)
{
return dht11_data_humidity;
}
float dht11_data_temperature_get(void)
{
return dht11_data_temperature;
}
五.HC-05
1.简介与功能描述
使用蓝牙模块进行串口通信,打印实时状态信息,并可以通过串口透传,将信息上传至上位机。
2.模块代码(使用串口代码模块
#include "sys.h"
#include "uart1.h"
#include "string.h"
UART_HandleTypeDef uart1_handle; /* UART1句柄 */
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; /* UART1接收缓冲区 */
uint16_t uart1_rx_len = 0; /* UART1接收字符长度 */
/**
* @brief 重定义fputc函数
* @note printf函数最终会通过调用fputc输出字符串到串口
*/
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void uart1_init(uint32_t baudrate)
{
/*UART1 初始化设置*/
uart1_handle.Instance = USART1; /* USART1 */
uart1_handle.Init.BaudRate = baudrate; /* 波特率 */
uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&uart1_handle); /* HAL_UART_Init()会使能UART1 */
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */
{
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */
__HAL_RCC_USART1_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_9; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10; /* 串口RX脚 模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */
HAL_NVIC_EnableIRQ(USART1_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART1接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART1总线空闲中断 */
}
}
/**
* @brief UART1接收缓冲区清除
* @param 无
* @retval 无
*/
void uart1_rx_clear(void)
{
memset(uart1_rx_buf, 0, sizeof(uart1_rx_buf)); /* 清空接收缓冲区 */
uart1_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口1中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(uart1_rx_len >= sizeof(uart1_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
uart1_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&uart1_handle, &receive_data, 1, 1000); /* 接收一个字符 */
uart1_rx_buf[uart1_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
printf("recv: %s\r\n", uart1_rx_buf); /* 将接收到的数据打印出来 */
uart1_rx_clear();
__HAL_UART_CLEAR_IDLEFLAG(&uart1_handle); /* 清除UART总线空闲中断 */
}
}
六.SUBtoTTL
1.简介与功能描述
用于单片机与电脑连接,方便将程序下载到单片机。
2.所用型号和实物图
本项目中我使用的是CH340模块,实物图如下:
七.防护模块
1.简介与功能描述
当环境中的温湿度值超过设定的阈值时,系统会自动打开风扇通风,以降低温湿度。
2.模块代码
if(smoke_value >= smoke_limit_value) //电机驱动程序开启前提下,超过烟雾传感器阈值,则警报并关闭电机 ,抛出标志位
{
fan_off();
lcd1602_display_fan_off();
fan_on_off_flag_main = OFF;
beep_on();
lcd1602_display_beep_on();
lcd1602_show_line(2, 10 ," ");
beep_on_off_flag_main = ON;
}
if(smoke_value < smoke_limit_value) //电机驱动程序开启前提下,未超过烟雾传感器阈值,则不警报,并抛出标志位
{
fan_on();
lcd1602_display_fan_on();
lcd1602_show_line(2, 15 ," ");
fan_on_off_flag_main = ON;
beep_off();
lcd1602_display_beep_off();
beep_on_off_flag_main = OFF;
}
八.报警模块
1.简介与功能描述
在打开风扇时,如果监测到环境中烟雾值超过设定的烟雾阈值时,为防止火灾蔓延,系统会自动关闭电机驱动的风扇,并打开报警器持续鸣响,直至烟雾值低于设定的阈值或者温湿度降低到设定的阈值以下。
2.模块代码(与防护模块代码写在一起
if(smoke_value >= smoke_limit_value) //电机驱动程序开启前提下,超过烟雾传感器阈值,则警报并关闭电机 ,抛出标志位
{
fan_off();
lcd1602_display_fan_off();
fan_on_off_flag_main = OFF;
beep_on();
lcd1602_display_beep_on();
lcd1602_show_line(2, 10 ," ");
beep_on_off_flag_main = ON;
}
if(smoke_value < smoke_limit_value) //电机驱动程序开启前提下,未超过烟雾传感器阈值,则不警报,并抛出标志位
{
fan_on();
lcd1602_display_fan_on();
lcd1602_show_line(2, 15 ," ");
fan_on_off_flag_main = ON;
beep_off();
lcd1602_display_beep_off();
beep_on_off_flag_main = OFF;
}
九.显示及控制
1.简介与功能描述
在LCD1602上实时显示环境中的温湿度值,烟雾值,电机风扇和报警器的开关状态。实时发送给串口助手显示,并通过蓝牙串口透传实时发送给上位机APP显示。当修改温湿度阈值和烟雾阈值时,也会在LCD,串口助手,上位机APP显示相关信息。当风扇或报警器打开时,也同样会在三者显示风扇和警报的状态信息。
2.模块代码(具体的数据信息需要调用其他模块的显示函数)
#include "lcd1602.h"
#include "delay.h"
#include "stdio.h"
#include "led.h"
//RS引脚定义
#define RS_GPIO_Port GPIOB
#define RS_GPIO_Pin GPIO_PIN_1
#define RS_HIGH HAL_GPIO_WritePin(RS_GPIO_Port, RS_GPIO_Pin, GPIO_PIN_SET);
#define RS_LOW HAL_GPIO_WritePin(RS_GPIO_Port, RS_GPIO_Pin, GPIO_PIN_RESET);
//RW引脚定义
#define RW_GPIO_Port GPIOB
#define RW_GPIO_Pin GPIO_PIN_10
#define RW_HIGH HAL_GPIO_WritePin(RW_GPIO_Port, RW_GPIO_Pin, GPIO_PIN_SET);
#define RW_LOW HAL_GPIO_WritePin(RW_GPIO_Port, RW_GPIO_Pin, GPIO_PIN_RESET);
//EN引脚定义
#define EN_GPIO_Port GPIOB
#define EN_GPIO_Pin GPIO_PIN_11
#define EN_HIGH HAL_GPIO_WritePin(EN_GPIO_Port, EN_GPIO_Pin, GPIO_PIN_SET);
#define EN_LOW HAL_GPIO_WritePin(EN_GPIO_Port, EN_GPIO_Pin, GPIO_PIN_RESET);
void lcd1602_init(void)
{
//初始化GPIO
lcd1602_gpio_init();
//上电初始化
lcd1602_start();
}
void lcd1602_gpio_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4
| GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
gpio_initstruct.Pin = GPIO_PIN_1 | GPIO_PIN_10 | GPIO_PIN_11; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
}
void lcd1602_start(void)
{
//(1) 延时 15ms
delay_ms(15);
//(2) 写指令 38H(不检测忙信号)
lcd1602_write_cmd(0x38);
//(3) 延时 5ms
delay_ms(5);
//(4) 以后每次写指令,读/写数据操作均需要检测忙信号
//(5) 写指令 38H:显示模式设置
lcd1602_write_cmd(0x38);
//(6) 写指令 08H:显示关闭
lcd1602_write_cmd(0x08);
//(7) 写指令 01H:显示清屏
lcd1602_write_cmd(0x01);
//(8) 写指令 06H:显示光标移动设置
lcd1602_write_cmd(0x06);
//(9) 写指令 0CH:显示开及光标设置
lcd1602_write_cmd(0x0C);
}
void lcd1602_write_cmd(char cmd)
{
RS_LOW;
RW_LOW;
EN_LOW;
GPIOA->ODR = cmd;
delay_ms(5);
EN_HIGH;
delay_ms(5);
EN_LOW;
}
void lcd1602_write_data(char dataShow)
{
RS_HIGH;
RW_LOW;
EN_LOW;
GPIOA->ODR = dataShow;
delay_ms(5);
EN_HIGH;
delay_ms(5);
EN_LOW;
}
void lcd1602_show_char()
{
//在哪里显示?
lcd1602_write_cmd(0x80 + 0x02);
//显示什么?
lcd1602_write_data('L');
}
void lcd1602_show_line(char row, char col, char *string)
{
switch(row)
{
case 1:
lcd1602_write_cmd(0x80 + col);
while(*string)
{
lcd1602_write_data(*string);
string++;
}
break;
case 2:
lcd1602_write_cmd(0x80 + 0x40 + col);
while(*string)
{
lcd1602_write_data(*string);
string++;
}
break;
}
}
void lcd1602_display_smoke(float smoke_value)
{
char msg[16] = {0};
sprintf(msg, "S:%0.1fV",smoke_value);
lcd1602_show_line(2, 0, msg);
}
void lcd1602_display_humidity(float dht11_data_humidity)
{
char msg[16] = {0};
sprintf(msg, "H:%0.1fRH", dht11_data_humidity);
lcd1602_show_line(1, 0, msg);
// "湿度:%d.%dRH,",dht11_data[0],dht11_data[1]
}
void lcd1602_display_temperature(float dht11_data_temperature)
{
char msg[16] = {0};
sprintf(msg, "T:%0.1fC", dht11_data_temperature);
lcd1602_show_line(1, 9, msg);
}
void lcd1602_display_smoke_limit(float limit_value)
{
char msg[16] = {0};
sprintf(msg, "Limit: %0.1f V",limit_value);
lcd1602_show_line(2, 1, msg);
sprintf(msg, "Smoke");
lcd1602_show_line(1, 5, msg);
}
//获取温度数值
void lcd1602_display_temperature_limit(float limit_value)
{
char msg[16] = {0};
sprintf(msg, "Limit: %0.1f V",limit_value);
lcd1602_show_line(2, 1, msg);
sprintf(msg, "temperature");
lcd1602_show_line(1, 2, msg);
}
//获取湿度数值
void lcd1602_display_humidity_limit(float limit_value)
{
char msg[16] = {0};
sprintf(msg, "Limit: %0.1f V",limit_value);
lcd1602_show_line(2, 1, msg);
sprintf(msg, "Humidity");
lcd1602_show_line(1, 4, msg);
}
void lcd1602_display_beep_on(void)
{
lcd1602_show_line(2, 7, "bon");
}
void lcd1602_display_beep_off(void)
{
lcd1602_show_line(2, 7, "boff");
}
void lcd1602_display_fan_on(void)
{
lcd1602_show_line(2, 12, "fon");
}
void lcd1602_display_fan_off(void)
{
lcd1602_show_line(2, 12, "foff");
}
十.多线程
1.描述
使用Systick系统滴答定时器模拟多线程,使得按键中断的引入不会破坏显示进程,实时获取数据进程的运行。本项目中设立了两条线程。
2.代码
#include "threads.h"
#include "led.h"
uint32_t thread1_cnt = 0; //每1ms进一次systic中断,计一次数,达到需要的ms数后清零并写操作
uint32_t thread2_cnt = 0;
uint8_t thread1_flag = 0;
uint8_t thread2_flag = 0;
void systick_threads(void)
{
if( thread1_cnt < 1000 )
thread1_cnt ++;
else
{
thread1_cnt = 0;
thread1_flag = 1;
//led1_toggle(); //测试线程1计数
}
if( thread2_cnt < 2000 )
thread2_cnt ++;
else
{
thread2_cnt = 0;
thread2_flag = 1;
//led2_toggle(); //测试线程2计数
}
}
uint8_t thread1_flag_get(void)
{
uint8_t temp = thread1_flag;
thread1_flag = 0;
return temp;
}
uint8_t thread2_flag_get(void)
{
uint8_t temp = thread2_flag;
thread2_flag = 0;
return temp;
}
十一.调试
1.描述
项目撰写调试时,难免会出现实验现象不如预期或出现错误,这是就需要找出程序哪里出了纰漏。
2.方法
我常用的检查方法有两种:
1.使用软件的自带调试工具
像keil5等代码编写软件都带有调试工具,通过打断点等方式控制程序运行,很方便得知道程序哪里出现纰漏。
2.使用led灯或屏幕,串口打印显示
通常情况下,程序现象与预期有偏差,只需在现象偏差模块使用显示信息就可以判断出哪写程序出现纰漏。
相比之下,第二种更直观,第一种更精确。大家根据自己习惯选择使用即可。
十二.项目总结
1.收获
对单片机运行原理的理解更加深刻,对相关模块的使用更熟练。建议初学者一定要亲手连接硬件、编写代码,这样才能对项目有更深刻的理解。
2.疑难及解决
不使用FreeRTOS的情况下,如果使用按键中断,但又不能破坏显示进程的正常运行,需要引入Systick模拟多线程,此项功能添加会导致按键中断产生时,显示模块的随机跳变,经过查找和思考,最后知问题出在中断优先级的考量和设定上,修改后也是终于把问题解决。
3.杂记点
通过项目的实操,我认为知识学习时一定要有全局观,不能各个知识点分别学习,而是要积极寻找各知识点之间的关系,这样在将知识运用于实践时,会有类似于清晰框架的胸有成竹的感觉。
当然,如果文章对你有帮助,记得给好兄弟点个赞,虽然没有收益,但却能给我大大的鼓励。