项目需求:
使用温湿度传感器模块(DHT11)获取温度及湿度,并将值显示在LCD1602上,同时通过蓝牙模块可手动控制继电器开关。
LCD1602概述:
LCD1602(Liquid Crystal Display)是一种工业字符型液晶,能够同时显示 16×02 即 32 字符(16列两行
第 1 脚: VSS 为电源地
第 2 脚: VDD 接 5V 正电源
第 3 脚: VL 为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度 过高时会产生“鬼影”,使用时可以通过一个 10K 的电位器调整对比度。
第 4 脚:RS 为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。
第 5 脚:R/W 为读写信号线,高电平时进行读操作,低电平时进行写操作。当 RS 和 R/W 共 同为低电平时可以写入指令或者显示地址,当 RS 为低电平 R/W 为高电平时可以读忙信号, 当 RS 为高电平 R/W 为低电平时可以写入数据。
第 6 脚:E 端为使能端,当 E 端由高电平跳变成低电平时,液晶模块执行命令。
第 7-14 脚:D0~D7 为 8 位双向数据线。
第 15 脚:背光源正极。
第 16 脚:背光源负极。
引脚封装:三个重要引脚:RS,RW,EN。
LCD1602函数封装:
DHT11 温湿度传感器概述:
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,应用领域:暖通 空调;汽车;消费品;气象站;湿度调节器;除湿器;家电;医疗;自动控制。
特点:
相对湿度和温度测量
全部校准,数字输出
长期稳定性 超长的信号传输距离:20米
超低能耗:休眠
4 引脚安装:可以买封装好的
完全互换 : 直接出结果,不用转化
数据传送逻辑:
只有一根数据线DATA,单片机发送序列指令给DHT11模块,模块一次完整的数据传输为40bit,高位先出。
数据格式:
检测模块是否存在:void DHT11_Start();
模块检测时序逻辑分析:
a : dht = 1 ,b :dht = 0 ,延时30ms, c: dht = 1 ,在60us后读d点,如果d点是低电平(被模块拉低),说明模块存在!
DHT11传输0的时序:
DHT11传输1的时序:
硬件接线:
引脚封装:
函数封装:
us级延时函数:
printf函数重写:记得要勾选MicoreLIB库
对PB7引脚做函数封装,使它既能为输入引脚,也能为输出引脚:
检测DHT11模块是否存在,作为开始信号:
读取DHT模块数据:
在while函数中不断读取数据,用printf函数通过uart1打印到串口助手:
最后加上继电器:
项目最终代码:
lcd1602.c
#include "lcd1602.h"
#include "gpio.h"
#define RS_GPIO_Port GPIOB
#define RW_GPIO_Port GPIOB
#define EN_GPIO_Port GPIOB
#define RS_GPIO_PIN GPIO_PIN_1
#define RW_GPIO_PIN GPIO_PIN_2
#define EN_GPIO_PIN GPIO_PIN_10
#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)
#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)
#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 Write_Cmd_Func(char cmd) //写命令
{
RS_LOW;
RW_LOW;
EN_LOW;
HAL_Delay(5);
GPIOA->ODR = cmd;
HAL_Delay(5);
EN_HIGH;
HAL_Delay(5);
EN_LOW;
}
void Write_Data_Func(char dataShow) //写数据
{
RS_HIGH;
RW_LOW;
EN_LOW;
HAL_Delay(5);
GPIOA->ODR = dataShow;
HAL_Delay(5);
EN_HIGH;
HAL_Delay(5);
EN_LOW;
}
void LCD1602_INIT(void) //LCD1602初始化
{
HAL_Delay(15); //(1)延时 15ms
Write_Cmd_Func(0x38);//(2)写指令 38H(不检测忙信号)
HAL_Delay(15); //(3)延时 5ms
Write_Cmd_Func(0x38);//(4)以后每次写指令,读/写数据操作均需要检测忙信号
//(5)写指令 38H:显示模式设置
Write_Cmd_Func(0x08);//(6)写指令 08H:显示关闭
Write_Cmd_Func(0x01);//(7)写指令 01H:显示清屏
Write_Cmd_Func(0x06);//(8)写指令 06H:显示光标移动设置
Write_Cmd_Func(0x0c);//(9)写指令 0CH:显示开及光标设置
}
void LCD1602_showLine(char row,char col,char *string) //LCD1602显示字符串,参数一:要显示的行(0~15),参数二:要显示的列(1~2)
{ //参数三:要显示的字符或者字符串
switch(row)
{
case 1:
Write_Cmd_Func(0x80+col);
while(*string)
{
Write_Data_Func(*string);
string++;
}
break;
case 2:
Write_Cmd_Func(0x80+0x40+col);
while(*string)
{
Write_Data_Func(*string);
string++;
}
break;
}
}
lcd1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__
void LCD1602_INIT(void);
void LCD1602_showLine(char row,char col,char *string);
#endif
dht.c
#include "dht.h"
#include "gpio.h"
#include "stdio.h"
#include "usart.h"
//作为输出的引脚
#define DHT_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET)
#define DHT_LOW HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_RESET)
//作为输入引脚读数据
#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)
unsigned char datas[5];
void delay_us(uint16_t cnt)
{
uint8_t i;
while(cnt)
{
for (i = 0; i < 10; i++)
{
}
cnt--;
}
}
void DHT_GPIO_Init(uint32_t mode)//对PB7口做函数封装,既能做输入引脚也能输出引脚
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
/*Configure GPIO pin : PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = mode;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void DHT11_Start(void)
{
DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP); //把PB7口设置为推挽输出模式
DHT_HIGH;
DHT_LOW;
HAL_Delay(30);
DHT_HIGH;
DHT_GPIO_Init(GPIO_MODE_INPUT); //把PB7口设置为输入模式 ,数据线作为输入还是输出看时序图
while(DHT_VALUE);
while(!DHT_VALUE);
while(DHT_VALUE);
}
void Read_Data_From_DHT(void) //读取DHT数据,PB7作为输入口
{
unsigned char i,j;
unsigned char tmp=0;
//char flag;
DHT11_Start();
DHT_GPIO_Init(GPIO_MODE_INPUT);
for(i=0;i<5;i++)
{
for(j=0;j<8;j++)
{
while(!DHT_VALUE);
delay_us(40);
if(DHT_VALUE==1) //获取DHT11数据,高位先出
{
tmp=tmp<<1;
tmp|=0x01;
//flag=1;
while(DHT_VALUE);//等待电平拉低
}
else
{
tmp=tmp<<1;
tmp|=0x00;
//flag=0;
while(DHT_VALUE);
}
//tmp=tmp<<1;
//tmp|=flag;
}
datas[i]=tmp;
}
}
dht.h
#ifndef __DHT_H__
#define __DHT_H__
void Read_Data_From_DHT(void);
#endif
usart.c
串口.c文件主要是插入接收完成中断回调函数,还有重写的printf函数(int fput函数),在回调函数中判断继电器开关指令,用于手动开关继电器。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.c
* @brief This file provides code for the configuration
* of the USART instances.
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
/* USER CODE BEGIN 0 */
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
//#define SIZE 12
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
#include "stdio.h"
#include "string.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
//灯控指令
if(!strcmp(UART1_RX_Buffer,"L-1"))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
}
if(!strcmp(UART1_RX_Buffer,"L-0"))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
}
memset(UART1_RX_Buffer,'\0',UART1_REC_LEN);
UART1_RX_STA = 0; //
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
main.c
主要是写两个外部变量,main函数中的lcd1602初始化,开启串口1的中断,以及while循环中读取dht数据并显示到lcd上,
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "lcd1602.h"
#include "dht.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern unsigned char datas[5];
extern uint8_t buf;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
char massage[16];
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
LCD1602_INIT(); //初始化lcd1602
HAL_UART_Receive_IT(&huart1, &buf, 1); //在main函数中把串口中断打开
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000); //延时一段时间让硬件上电,同时每隔1s读取一次dht数据
Read_Data_From_DHT(); //读取dht数据
if(datas[2] > 26) //如果温度高于26度,打开继电器
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
}
else //否则关闭继电器
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
}
memset(massage,0,sizeof(massage)); //清空massage,memset以字节为单位进行赋值,赋值范围是0~255
sprintf(massage,"Temp:%d.%d ",datas[2],datas[3]); //sprintf第一个参数是char*型
LCD1602_showLine(1,0,massage); //在lcd1602上显示温度
memset(massage,0,sizeof(massage));
sprintf(massage,"Humi:%d.%d",datas[0],datas[1]);
LCD1602_showLine(2,0,massage); //在lcd1602上显示湿度
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */