目录
1 目标
通过HAL库操作GPIO口,控制单总线协议的温湿度传感器DHT11
2 背景知识
2.1 DHT11
为了与DHT11进行数据交换,我们需要知道交互的协议,查阅手册。注意,不同厂家对时序定义可能不同
一次传送40bit数据,8位湿度整数,8位湿度小数,8位温度整数,8位温度小数,8位校验和,高位先出,需要注意的是小数部分为保留字段无意
起始请求,注意是至少拉低18ms
数据0表示
数据1表示
2.2 定时器
STM32有非常多种类的定时器,这里选择通用定时器TIM2进行解析
查阅手册,可以发现TIM2时钟线挂载在于PCLK1上,并通过分频最终供给TIM2
回到CubeMX,查看时钟树,得到TIM2频率为120MHz
定时器在每个时钟周期,计数值会+1/-1直到产生溢出发生更新事件,时钟还可以通过预分频系数进一步降低频率
对于TIM2,通过在CubeMX中查看Counter Period得知为32bit,预分频系数支持16bit,支持向上计数或向下计数
3 过程
3.1 GPIO复用
单总线需要GPIO口在不同的时间充当输入和输出的角色,但CubeMX初始化只能初始输入或输出,所以需要对其重新进行初始化
挑选两个GPIO口分别设置为输入和输出,并在main.c中通过研究MX_GPIO_Init();,得到对应功能初始化代码
进行改写,改写完成以后,只保留所需引脚初始化(保留CubeMX初始化,可以在使用其他功能发生冲突时获得提醒)
void gpioToOutput () {
// de-init
HAL_GPIO_DeInit(DHT11_Port, DHT11_Pin);
// init
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = DHT11_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(DHT11_Port, &GPIO_InitStruct);
}
3.2 定时器
时序操作涉及到us,使用定时器来精准定时,由于TIM2频率为120MHz,可以设置预分频系数为120,预分频后频率为1MHz周期为1us
流程:设置计数值 -> 等待溢出 -> 清空标志位 -> 停止
根据流程,写出相应代码
void delay_us (int32_t us) {
__HAL_TIM_SET_COUNTER(&TIMER, us - 1); // 设置计数值
HAL_TIM_Base_Start(&TIMER); // 启动
while (__HAL_TIM_GET_FLAG(&TIMER, TIM_FLAG_UPDATE) == RESET); // 标志位检查
__HAL_TIM_CLEAR_FLAG(&TIMER, TIM_FLAG_UPDATE); // 标志位清除
HAL_TIM_Base_Stop(&TIMER); // 停止
}
3.3 单总线
初始化,空闲时总线上保持高电平
void DHT11_rest () {
gpioToOutput();
DHT11_High;
}
读取程序,虽然手册上写的小数部分保留,但实际存在数值,不太清楚这里
uint8_t DHT11_data[3] = {}; // 状态,湿度,温度
uint8_t *DHT11_read () {
uint8_t data[5] = {};
// 请求,响应
gpioToOutput();
DHT11_Low;
delay_us(27 * 1000);
DHT11_High;
delay_us(30);
gpioToInput();
delay_us(40);
if (DHT11_Is_High) {
DHT11_rest();
DHT11_data[0] = 0;
return DHT11_data;
}
while (!DHT11_Is_High);
while (DHT11_Is_High);
// 读取数据
for (int i = 0 ; i < 5 ; ++i) {
for (int j = 0 ; j < 8 ; ++j) {
while (!DHT11_Is_High);
delay_us(50);
if (DHT11_Is_High) { // 1:70us
data[i] |= (1 << (7 - j));
while (DHT11_Is_High);
}
else { // 0:26-28us
;
}
while (!DHT11_Is_High);
}
}
// 返回
if (data[0] + data[1] + data[2] + data[3] != data[4]) {
DHT11_rest();
DHT11_data[0] = 0;
return DHT11_data;
}
DHT11_rest();
DHT11_data[0] = 1;
DHT11_data[1] = data[0];
DHT11_data[2] = data[2];
return DHT11_data;
}
主函数
while (1) {
dht11Data = DHT11_read();
if (dht11Data[0]) {
OLED_ShowNum(0, 0, dht11Data[1], 2, 8, 1);
OLED_ShowNum(20, 0, dht11Data[2], 2, 8, 1);
}
}
3.4 不足
默认DHT11握手成功后不会挂掉(读取典型值4ms),所以用了很多while,但假如读取数据时DHT11挂掉,会造成死循环
可采用定时或看门狗解决
参考文献
附录
GPIO
定时器