STM32温湿度测量与显示-单总线协议学习

本文最后更新于:2023年5月25日 凌晨

STM32温湿度测量与显示-单总线协议学习

为了验收个小玩意用一晚上搓了一个,比较氵。 那就氵篇博客算了。

手头正好有个ST的板子,型号是STM32G431KBT6,主频170M。刚好拿过来用了。(真好用啊,就是做这个有点浪费。

温湿度传感器使用 DHT11 (单总线协议),屏幕是个IIC的小OLED,还用到了串口来看数据是否正确。(没了,确实氵。

效果图:

效果图

准备

首先去官网下载 STM32G4xx 的包。(Keil.STM32G4xx_DFP.1.5.0.pack)。官网直连下载很慢,很慢……

然后去CubeMX里面新建工程,因为是官方的板子,所以可以直接选择这块板子的模板创建。时钟设置170M(默认的)。选择一个没有中文的路径生成工程。

接下来的流程是:

  • 设置串口
  • 驱动OLED屏幕
    • 重新配置延时函数
    • 添加软件IIC
    • 添加OLED驱动
  • 驱动DHT11温湿度传感器(单总线协议的学习

因为使用的OLED驱动比较特殊,添加之后无法使用原来的 HAL_Dealy() 函数,需要按照该驱动的要求重新配置延时函数,之后使用其提供的 delay_ms()delay_us() 两个函数代替 HAL_Dealy() 函数,当然实际上是方便不少的,因为 HAL_Dealy() 是毫秒级延时,所以直接使用上面两个函数可以让自己少写一个微秒的延时函数,具体原理可以参考之前的一篇博客

具体实现

串口设置

串口的具体实现参考我之前的博客步骤:STM32&UART串口通信(使用CubeMX) 。因为这次没有用到收的功能,所以只需要设置好发送功能即可。

本次使用USART1。其实 更推荐 使用USART2的,因为官方的板子上本身就有串口芯片,使用的USART2,这样用一根USB线连电脑上就可以同时供电,下载和查看串口数据了,不用自己再连一个USB转串口的模块了,还能腾出来两个引脚,这STM32G431KBT6的引脚是真的少。 (还是官方板子好用,就是贵


)晚上要去重庆,剩下的具体流程就等下周回来再写了。

回来了,发现实习二面寄了,悲)


驱动OLED屏幕

这一节可以略过。

本次的驱动选自 LCD wiki

因为 LCD wiki 的驱动为gb2312编码,为了以后写的舒服,我先转换为了utf-8,具体可以参考:

批量转换文件编码gb2312转utf-8 。但是注意,代码里有中文的地方,比如 test.coledfont.h等文件需要保持原来的gb2312。(看见编译时候的编码报错真是心累呐

LCD wiki 的驱动使用标准库编写,放入HAL库的项目中会狠狠报错,所以要进行修改)我自己修改了一下,以后写一下修改过程emmm(咕咕

重新配置延时函数

添加 sys.hsys.cdelay.hdelay.c ,注意修改里面包含的头文件为对应的 #include "stm32g4xx.h"

之后在 main.c 里面进行初始化: delay_init(); 初始化完成后以后的延时就用 delay_ms()delay_us()

添加软件IIC

添加 iic.ciic.h ,注意修改里面 SCLSDA 的引脚。本次分别使用 PA1PA0

添加OLED驱动

添加 bmp.hgui.hgui.coled.coled.holedfont.htest.ctest.h

这次所用的汉字取模保存至 oledfont.h 内,注意为gb2312编码。

取模使用 PCtoLCD2002 ,为逐行式,顺向。考虑到屏幕大小,取模的大小为16×16。添加在 const typFNT_GB16 cfont16[] 的后面。如:

"康",0x01,0x00,0x00,0x80,0x3F,0xFE,0x20,0x80,0x2F,0xF8,0x20,0x88,0x3F,0xFE,0x20,0x88,0x2F,0xF8,0x28,0x80,0x24,0xC4,0x22,0xA8,0x44,0x90,0x48,0x88,0x92,0x86,0x01,0x00,/*"康"*/
"师",0x08,0x00,0x0B,0xFE,0x48,0x20,0x48,0x20,0x48,0x20,0x49,0xFC,0x49,0x24,0x49,0x24,0x49,0x24,0x49,0x24,0x49,0x24,0x09,0x34,0x11,0x28,0x10,0x20,0x20,0x20,0x40,0x20,/*"师"*/
"傅",0x08,0x50,0x08,0x48,0x0F,0xFE,0x10,0x40,0x17,0xFC,0x34,0x44,0x37,0xFC,0x54,0x44,0x97,0xFC,0x14,0x44,0x10,0x08,0x1F,0xFE,0x12,0x08,0x11,0x08,0x11,0x28,0x10,0x10,/*"傅"*/

显示汉字使用 GUI_ShowCHinese(uint8_t x,uint8_t y,uint8_t hsize,uint8_t *str,uint8_t mode) ,使用实例如 GUI_ShowCHinese(40,2,16,"康师傅",1);

该函数说明:

/*****************************************************************************
 * @name       :void GUI_ShowCHinese(uint8_t x,uint8_t y,uint8_t hsize,uint8_t *str,uint8_t mode)
 * @date       :2018-08-27 
 * @function   :Display Chinese strings
 * @parameters :x:the bebinning x coordinate of the Chinese strings
                y:the bebinning y coordinate of the Chinese strings
								size:the size of Chinese strings
								str:the start address of the Chinese strings
								mode:0-white background and black character
								     1-black background and white character
 * @retvalue   :None
******************************************************************************/

显示其他内容可以 结合test.c 中的实例与 gui.c 里的函数说明学习。

为了分开gb2312与utf-8,我将显示中文的地方全部写在了一个文件里,我在 main.c 用到的时候直接调用。

驱动DHT11温湿度传感器(单总线协议的学习

)当时大二赶广和通项目的时候用的是 DS18B20 来着,是个单总线协议的温度传感器,网上找了段代码糊了一个,这次用 DHT11 学习学习单总线协议。

DHT11 是一款湿温度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个 NTC
测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够
实时的采集本地湿度和温度。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一
个 I/O 口。传感器内部湿度和温度数据 40Bit 的数据一次性传给单片机,数据采用校验和方式
进行校验,有效的保证数据传输的准确性。DHT11 功耗很低,5V 电源电压下,工作平均最大
电流 0.5mA。DHT11 的技术参数:工作电压范围:3.3V-5.5V;工作电流 :平均 0.5mA;输出:单总线数字信号;测量范围:湿度 2090%RH,温度 050℃;精度 :湿度±5%,温度±2℃;分辨率 :湿度 1%,温度 1℃;

DHT11 数据传输格式

DHT11 数字湿温度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 40Bit 组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。

DHT11 的数据格式为:8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验和。其中校验和数据为前四个字节相加。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如下表所示:

byte4 byte3 byte2 byte1 byte0
0010 1101 0000 0000 0001 1100 0000 0000 0100 1001
湿度 整数 部分 湿度 小数 部分 温度 整数 部分 温度 小数 部分 校验和

由以上数据就可得到湿度和温度的值,计算方法:

湿度= byte4 . byte3=45.0 (%RH)
温度= byte2 . byte1=28.0 ( ℃)
校验= byte4+ byte3+ byte2+ byte1=73(=湿度+温度)(校验正确)

DHT11 的数据格式是十分简单,DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。 下面,我们介绍一下 DHT11 的传输时序。

提前准备

本次使用数据传输脚:

// 数据传输脚 PA12
#define DHT11_DQ_OUT GPIO_PIN_12

设置数据传输脚为输出:

void DHT11_IO_OUT(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
	GPIO_InitStruct.Pin = DHT11_DQ_OUT;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 
}

设置数据传输脚为输入:

void DHT11_IO_IN(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DHT11_DQ_OUT;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 
}

DHT11 的数据发送流程

DHT11 的数据发送流程如下图所示:

DHT11 的数据发送流程

开始信号

首先主机发送 开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后 拉高数据线 t2(20~ 40us)时间,然后读取 DHT11 的响应。

DHT11 开始信号

复位DHT11 :

void DHT11_Rst(void) {                
	DHT11_IO_OUT(); //SET OUTPUT
    HAL_GPIO_WritePin(GPIOA, DHT11_DQ_OUT, GPIO_PIN_RESET);
    delay_ms(20); //拉低至少18ms
    HAL_GPIO_WritePin(GPIOA, DHT11_DQ_OUT, GPIO_PIN_SET); 
	delay_us(30); //主机拉高20~40us
}
响应信号

DHT11的 响应 :正常的话,DHT11 会 拉低 数据线,保持 t3(40~50us)时间,作为响应信号,然后 DHT11 拉高 数据线,保持 t4(40 ~50us)时间后,开始输出数据。

DHT11 开始信号

检测DHT11的回应

//返回1:未检测到DHT11的存在
//返回0:存在
uint8_t DHT11_Check(void) 	   
{   
	uint8_t retry=0;
	DHT11_IO_IN(); //SET INPUT
    //DHT11会拉低40~80us
    while (HAL_GPIO_ReadPin(GPIOA, DHT11_DQ_OUT) && retry<100)
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100) return 1;
	else retry=0;
    //DHT11拉低后会再次拉高40~80us
    while (!HAL_GPIO_ReadPin(GPIOA, DHT11_DQ_OUT)&&retry<100)
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100) return 1;	    
	return 0;
}
DHT11 发送数据

总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据。

每一bit数据都以50us低电平时隙 开始高电平的长短 定了数据位是0还是1。

如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。

DHT11 发送 0

高电平是26-28us就表示0。

DHT11 发送0

DHT11 发送1

高电平是70us表示1。

DHT11 发送0

接收数据

主机只要读取等待50us低电平过去,然后再判断高电平长短就能收集数据了。

从DHT11读取一个位:

//返回值:1/0
uint8_t DHT11_Read_Bit(void) 			 
{
 	uint8_t retry=0;
	while(HAL_GPIO_ReadPin(GPIOA, DHT11_DQ_OUT)&&retry<100)//等待变为低电平
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!HAL_GPIO_ReadPin(GPIOA, DHT11_DQ_OUT)&&retry<100)//等待变高电平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(HAL_GPIO_ReadPin(GPIOA, DHT11_DQ_OUT)) return 1;
	else return 0;		   
}

读取8次为一个字节:

//返回值:读到的数据
uint8_t DHT11_Read_Byte(void)    
{        
    uint8_t i = 0,dat = 0;
	for (i=0;i<8;i++) 
	{
		dat = dat<<1; 
		dat = dat | DHT11_Read_Bit();
    }						    
    return dat;
}

一共5个字节:

//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
void DHT11_Read_Data(uint8_t *temp, uint8_t *humi)    
{        
 	uint8_t i, buf[5];
	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;
}

)不过为了显示的更精确可以小数位也传过去。

完成

到此,完成了串口通信,OLED屏幕驱动,温湿度数据的获取。之后在OLED屏幕上的显示布局可以自己设计~


STM32温湿度测量与显示-单总线协议学习
https://blog.ksfu.top/posts/e0ca/
作者
康师傅
发布于
2023年5月18日
许可协议