这是一个使用STM32F103CxT6 HAL的项目,功能是使用DHT11获取当前温湿度然后显示到LCD屏幕上。
项目使用到了以下外设: GPIO, TIM, SPI, DMA;
其中SPI和USART使用了DMA,所以刷屏速度很快。
我近期在忙的项目分支之一,同时也作为一个“任务”。
在这我提供我使用F103CBT6写的项目地址,里面有我上传的代码但不是最新的,优化完之后便会上传。
WeACT_BulePill
https://github.com/chixiaoshu/WeAct_BluePill
目录
一、系统定义
引脚定义

定时器定义

SPI定义

我在这里说明一下,我的项目不是单一实现某个功能的,而是把我用到的引脚都定义好了。
二、源码部分
DHT11 模块
"dht11.c"
#include "dht11.h"
#include <stdint.h>
void Delay_us(uint8_t us)
{
uint16_t differ = 0xffff - us - 5;
__HAL_TIM_SET_COUNTER(&DHT11_TIMER, differ); // 设定TIM计数器起始值
HAL_TIM_Base_Start(&DHT11_TIMER); // 启动定时器
while (differ < 0xffff - 5)
{ // 判断
differ = __HAL_TIM_GET_COUNTER(&DHT11_TIMER); // 查询计数器的计数值
}
HAL_TIM_Base_Stop(&DHT11_TIMER);
}
void GPIO_Input(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*Configure GPIO pin : PB4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void GPIO_Output(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*Configure GPIO pin : PB4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void DHT11_Rst(void) // 主机开始信号
{
GPIO_Output();
DHT11_OUT_LOW;
HAL_Delay(18);
DHT11_OUT_HIGH;
Delay_us(30);
}
uint8_t DHT11_Check(void)
{
uint8_t retry = 0;
GPIO_Input();
while (DHT11_IN_Read && retry < 100) // 等待回应拉位低电平
{
retry++;
Delay_us(1);
}
if (retry >= 100)
return 1;
else
retry = 0; // 当变量值大于100 返回1 说明无响应 返回 0 则为正确响应
while (!DHT11_IN_Read && retry < 100) // 等待变为高电平
{
retry++;
Delay_us(1);
}
if (retry >= 100)
return 1;
return 0;
}
uint8_t DHT11_Init(void)
{
DHT11_Rst();
return DHT11_Check();
}
uint8_t DHT11_ReadBit(void) // 读取一个位
{
uint8_t retry = 0;
while (DHT11_IN_Read && retry < 100) // 等待变为低电平
{
retry++;
Delay_us(1);
}
retry = 0;
while (!DHT11_IN_Read && retry < 100) // 等待变为高电平
{
retry++;
Delay_us(1);
}
Delay_us(40); // 40us 后如果为低电平 数据为0 高电平数据为1
if (DHT11_IN_Read)
return 1;
else
return 0;
}
uint8_t DHT11_ReadByte(void) // 读取一个字节 返回值位采集值
{
uint8_t i, dat;
dat = 0;
for (i = 0; i < 8; i++)
{
dat <<= 1; // 数据左移一位
dat |= DHT11_ReadBit(); // 每读取到一个位 放到dat的最后一位
}
return dat;
}
uint8_t DHT11_ReadData(float *temp, float *hum)
{
uint8_t buf[5];
uint8_t i;
DHT11_Rst();
if (DHT11_Check() == 0)
{
for (i = 0; i < 5; i++)
{
buf[i] = DHT11_ReadByte();
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
// 湿度:buf[0]是整数部分,buf[1]是小数部分
*hum = buf[0] + buf[1] / 10.0;
// 温度:buf[2]是整数部分,buf[3]是小数部分
*temp = buf[2] + buf[3] / 10.0;
}
else
{
return 1; // 校验和错误
}
}
else
{
return 1; // 传感器检查失败
}
return 0; // 成功
}
"dht11.h"
#ifndef __DHT11_H__
#define __DHT11_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#define DHT11_TIMER htim3
extern TIM_HandleTypeDef DHT11_TIMER; // 引入变量
#define DHT11_OUT_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET)
#define DHT11_OUT_LOW HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_RESET)
#define DHT11_IN_Read HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
//函数原型
//void Delay_us(uint8_t us); //微秒级延时函数,启用了一个定时器。因为DHT11通讯过程涉及微秒延时
void GPIO_Input(void); //GPIO 状态转变的函数 CUBEMX默认的GPIO初始化我只开启了相关总线的使能
//把GPIO状态(输入 输出)封装成了两个函数
void GPIO_Output(void);
void DHT11_Rst(void); //主机开始采集的信号
uint8_t DHT11_Check(void); //检查DHT是否回应
uint8_t DHT11_Init(void); //初始化函数
uint8_t DHT11_ReadBit(void); //读取一位
uint8_t DHT11_ReadByte(void); //读取一个字节
uint8_t DHT11_ReadData(float *temp, float *hum); //读取数据(40个位)
#ifdef __cplusplus
}
#endif
#endif /* __DHT11_H__ */
这个DHT11需要将定时器打开,因为使用到了微秒级延时;
可以通过宏定义来修改为你用定义的定时器, TIM1,TIM2, TIM3;
然后这个ST7789屏幕的函数比较多我就不放了,接下来是用法;
三、代码实现
显示定义两个函数,一个是每秒获取一遍DHT11的数据,而另外一个是LCD显示函数;
其一, 时间处理函数用了系统滴答定时器以防止HAL_Delay阻塞;
其二, 这个延时1000ms可以去掉,我在这仅仅是调试使用;
其三, temperature和humidity是全局变量;
导入头文件

头文件只需要导入两个,"st7789.h"与"dht11.h" ;
看到我导入的头文件就知道我其实些了这几个相关的函数;
定义变量


然后在"stm32fxxx_it.c" 里写入上面这个一秒事件变量;
初始化LCD与DHT11模块
/* USER CODE BEGIN 2 */
ST7789_Init();
ST7789_Fill_Color(WHITE);
while (DHT11_Init())
{
sprintf((char *)temp_buffer, "DHT11 failed");
OLED_ShowString(0, 1, temp_buffer);
HAL_UART_Transmit(&huart1, temp_buffer, strlen((char *)temp_buffer), 0xFF);
HAL_Delay(500);
}
sprintf((char *)temp_buffer, "DHT11 Sucess");
HAL_UART_Transmit(&huart1, temp_buffer, strlen((char *)temp_buffer), 0xFF);
/* USER CODE END 2 */
说明: DHT11的初始化其实有返回值的,这边用while其实有点问题。比如我不接这个模块,那么会怀疑单片机是否坏了(并没有)。同时也需要注意,DHT11要是初始化不了,可以在串口1中查看。检查是模块损坏还是线没接好;
时间处理与LCD显示函数
/* USER CODE BEGIN 4 */
void time_process_event(void)
{
if (isTimerElapsed == 1)
{
LCD_process_event();
if (DHT11_ReadData(&temperature, &humidity) == 0)
{
// 使用 sprintf 将浮点数格式化为字符串
// sprintf((char *)temp_buffer, "Temp=%.1fC,Hum=%.0f%%\r\n", temperature, humidity);
// HAL_UART_Transmit(&huart1, temp_buffer, strlen((char *)temp_buffer), 0xFF);
isTimerElapsed = 0;
}
else
{
// 处理错误
// sprintf((char *)temp_buffer, "Error DHT11");
// HAL_UART_Transmit(&huart1, temp_buffer, strlen((char *)temp_buffer), 0xFF);
isTimerElapsed = 0;
HAL_Delay(1000);
}
}
}
void LCD_process_event(void)
{
uint8_t temp_buffer[128];
sprintf((char *)temp_buffer, "Temp=%.1fC ", temperature);
ST7789_WriteString(0, 18, (char *)temp_buffer, Font_11x18, BLACK, WHITE);
sprintf((char *)temp_buffer, "Hum=%.0f%% ", humidity);
ST7789_WriteString(0, 18 + 18, (char *)temp_buffer, Font_11x18, BLACK, WHITE);
}
/* USER CODE END 4 */
PS: 里面的注释内容纯纯调试,可以克隆或者下载我的项目,然后编译下载到单片机里面查看信息;

最后是将这个函数放到while这里,其实还有其它写法,就是取消下面的注释。然后将时间处理函数的LCD显示函数给注释掉。
四、结果展示

因为这里我加了个延时以及加上等待DHT11初始化完毕,所以开头就有些缓慢;
总是感觉这个模块又准又不准的。不清楚,能用就行。
五、结尾
这是我项目中使用到的模块,现在将其拆分并分享出来,供大家学习。
制作不易,有问题可以评论区交流。
最后,这个PCB底板是我使用嘉立创打板制作的,后期调整好之后可以将它开源?
566

被折叠的 条评论
为什么被折叠?



