单片机 :STM32F407
开发板:DMF407电机开发板
平台:keil V5.31
HSE 为8MHZ
HSI为16MHZ
原理图:

一、DS18B20数字温度传感器实验
传感器资料:
DS18B20的信号波形遵循单总线(1-Wire)协议,核心分为复位、写、读三类典型波形,所有时序都要求微秒级精度。以下是各类波形的关键参数和特征说明:
1. 初始化复位波形(通信第一步)
初始化是通信启动的必备环节,由主机发起复位、从机回应应答,完整波形分为三个阶段:
- 主机复位脉冲 :主机拉低总线,保持 **480~960μs(典型值500μs)**,之后释放总线(拉高)
- 从机存在脉冲 :总线释放后,DS18B20等待 16~60μs ,随后主动拉低总线 60~240μs 作为应答,最后释放总线
- 总线恢复期 :应答结束后需要至少 480μs 的恢复时间,才能开始后续操作
常见异常波形:无应答脉冲(总线持续高电平,一般是硬件断路或上拉电阻异常)、应答脉冲过短(电源不稳定,可加去耦电容解决)。
2. 写操作波形(写0/写1)
写操作按位传输,每一位对应一个时间隙,通过低电平持续时间区分写0和写1:
- 写1波形 :主机拉低总线后,15μs内必须释放总线(典型保持低电平5μs),之后保持高电平直到该时隙结束(整个时隙至少60μs,两位之间至少1μs恢复时间)
- 写0波形 :主机拉低总线后,保持 **60~120μs(典型值60μs)** 低电平,再释放总线,同样需要至少1μs的恢复时间
3. 读操作波形(读0/读1)
读操作由主机发起读时隙,DS18B20输出数据位:
- 主机先拉低总线至少 1μs 发起读请求,随后必须在 15μs内完成总线状态采样
- 若DS18B20输出0:总线会被持续拉低到整个时隙结束,采样得到低电平
- 若DS18B20输出1:DS18B20不拉低总线,采样得到高电平
- 单个读时隙至少需要60μs,两位之间保留至少1μs恢复时间
典型时序参数汇总
表格
| 操作类型 | 关键时间参数 | 允许范围(μs) | 典型值(μs) |
|---|---|---|---|
| 主机复位脉冲 | 总线拉低持续时间 | 480~960 | 500 |
| 从机应答脉冲 | 应答低电平持续时间 | 60~240 | 110 |
| 写1 | 总线拉低持续时间 | 1~15 | 5 |
| 写0 | 总线拉低持续时间 | 60~120 | 60 |
| 读操作采样点 | 主机发起后延迟采样 | 1~15 |
主函数:
int main(void)
{
uint8_t t = 0;
short temperature;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DS18B20 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
while (ds18b20_init()) /* DS18B20初始化 */
{
lcd_show_string(30, 110, 200, 16, 16, "DS18B20 Error", RED);
delay_ms(200);
lcd_fill(30, 110, 239, 130 + 16, WHITE);
delay_ms(200);
}
lcd_show_string(30, 110, 200, 16, 16, "DS18B20 OK", RED);
lcd_show_string(30, 130, 200, 16, 16, "Temp: . C", BLUE);
while (1)
{
if (t % 10 == 0) /* 每100ms读取一次 */
{
temperature = ds18b20_get_temperature();
if (temperature < 0)
{
lcd_show_char(30 + 40, 130, '-', 16, 0, BLUE); /* 显示负号 */
temperature = -temperature; /* 转为正数 */
}
else
{
lcd_show_char(30 + 40, 130, ' ', 16, 0, BLUE); /* 去掉负号 */
}
lcd_show_num(30 + 40 + 8, 130, temperature / 10, 2, 16, BLUE); /* 显示正数部分 */
lcd_show_num(30 + 40 + 32, 130, temperature % 10, 1, 16, BLUE); /* 显示小数部分 */
}
delay_ms(10);
t++;
if (t == 20)
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 */
}
}
}
配置:
/* DS18B20引脚 定义 */
#define DS18B20_DQ_GPIO_PORT GPIOG
#define DS18B20_DQ_GPIO_PIN GPIO_PIN_15
#define DS18B20_DQ_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0) /* PG口时钟使能 */
/******************************************************************************************/
/* IO操作函数 */
#define DS18B20_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* 数据端口输出 */
#define DS18B20_DQ_IN HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN) /* 数据端口输入 */
初始化:
uint8_t ds18b20_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
DS18B20_DQ_GPIO_CLK_ENABLE(); /* 开启DQ引脚时钟 */
gpio_init_struct.Pin = DS18B20_DQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &gpio_init_struct); /* 初始化DS18B20_DQ引脚 */
/* DS18B20_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
ds18b20_reset();
return ds18b20_check();
}
传感器检查:
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DS18B20_DQ_IN && retry < 200) /* 等待DQ变低, 等待200us */
{
retry++;
delay_us(1);
}
if (retry >= 200)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ变高, 等待240us */
{
retry++;
delay_us(1);
}
if (retry >= 240) rval = 1;
}
return rval;
}
没有外接传感器,检测失败。
读数据:
short ds18b20_get_temperature(void)
{
uint8_t flag = 1; /* 默认温度为正数 */
uint8_t TL, TH;
short temp;
ds18b20_start(); /* ds1820 start convert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
TL = ds18b20_read_byte(); /* LSB */
TH = ds18b20_read_byte(); /* MSB */
if (TH > 7)
{/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
我们这里先暂时没有作+1的处理,这里需要留意 */
TH = ~TH;
TL = ~TL;
flag = 0;
}
temp = TH; /* 获得高八位 */
temp <<= 8;
temp += TL; /* 获得底八位 */
/* 转换成实际温度 */
if (flag == 0)
{/* 将温度转换成负温度,这里的+1参考前面的说明 */
temp = (double)(temp+1) * 0.625;
temp = -temp;
}
else
{
temp = (double)temp * 0.625;
}
return temp;
}
写操作:
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0); /* Write 1 */
delay_us(2);
DS18B20_DQ_OUT(1);
delay_us(60);
}
else
{
DS18B20_DQ_OUT(0); /* Write 0 */
delay_us(60);
DS18B20_DQ_OUT(1);
delay_us(2);
}
data >>= 1; /* 右移,获取高一位数据 */
}
}
读操作:
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}
测试结果:

二、DHT11数字温湿度传感器实验
DHT11数字温湿度传感器资料:
DHT11采用单总线通信协议,信号波形分为四个核心阶段:起始信号、应答信号、数据传输,所有时序要求严格,微秒级误差就可能导致通信失败。结合公开资料整理的各阶段波形特征如下:
- 起始信号波形(主机发起,单片机→DHT11)
通信由主机主动发起,波形特征:
总线空闲时为高电平,主机先拉低总线,保持至少18ms(典型值20ms),保证DHT11能稳定检测到起始请求
之后主机释放总线(拉高),等待DHT11回应,拉高后需等待20-40μs让DHT11响应
常见异常:拉低时间小于18ms会导致DHT11不响应,没有应答信号。
- 应答信号波形(DHT11→主机)
DHT11检测到起始信号后,会发送固定的应答波形:
先拉低总线,保持80μs作为响应信号
再拉高总线,保持80μs,准备开始发送40位数据
异常特征:应答脉冲超时或持续时间不对,大概率是接线错误或上拉电阻异常(DHT11的DATA线必须接4.7kΩ~10kΩ上拉电阻)。
- 数据传输波形(DHT11→主机,共40位)
每一位数据都以固定低电平开头,通过高电平的持续时间区分0和1:
表格
数据位 低电平时长 高电平时长 单比特总时长
逻辑0 固定50μs 26~28μs ~76μs
逻辑1 固定50μs 约70μs ~120μs
完整的40位数据格式为:8位湿度整数+8位湿度小数+8位温度整数+8位温度小数+8位校验和,高位先传输,DHT11实际输出的小数部分始终为0。
完整通信总时序
主机起始→2.DHT11应答→3.逐位输出40位数据→4.传输完成,DHT11自动返回低功耗模式,等待下一次起始信号
实际调试中,超过60%的读取失败都是时序偏差导致:如果出现数据校验错误、间歇性读取失败,优先检查延时函数精度,建议使用定时器或汇编级精确延时来保证时序准确。
主函数:
int main(void)
{
uint8_t t = 0;
uint8_t temperature;
uint8_t humidity;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DHT11 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
while (dht11_init()) /* DHT11初始化 */
{
lcd_show_string(30, 110, 200, 16, 16, "DHT11 Error", RED);
delay_ms(200);
lcd_fill(30, 110, 239, 130 + 16, WHITE);
delay_ms(200);
}
lcd_show_string(30, 110, 200, 16, 16, "DHT11 OK", RED);
lcd_show_string(30, 130, 200, 16, 16, "Temp: C", BLUE);
lcd_show_string(30, 150, 200, 16, 16, "Humi: %", BLUE);
while (1)
{
if (t % 10 == 0) /* 每100ms读取一次 */
{
dht11_read_data(&temperature, &humidity); /* 读取温湿度值 */
lcd_show_num(30 + 40, 130, temperature, 2, 16, BLUE); /* 显示温度 */
lcd_show_num(30 + 40, 150, humidity, 2, 16, BLUE); /* 显示湿度 */
}
delay_ms(10);
t++;
if (t == 20)
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 */
}
}
}
配置:
/* DHT11 引脚 定义 */
#define DHT11_DQ_GPIO_PORT GPIOG
#define DHT11_DQ_GPIO_PIN GPIO_PIN_15
#define DHT11_DQ_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0) /* PG口时钟使能 */
/******************************************************************************************/
/* IO操作函数 */
#define DHT11_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* 数据端口输出 */
#define DHT11_DQ_IN HAL_GPIO_ReadPin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN) /* 数据端口输入 */
初始化:
uint8_t dht11_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
DHT11_DQ_GPIO_CLK_ENABLE(); /* 开启DQ引脚时钟 */
gpio_init_struct.Pin = DHT11_DQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(DHT11_DQ_GPIO_PORT, &gpio_init_struct); /* 初始化DHT11_DQ引脚 */
/* DHT11_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
dht11_reset();
return dht11_check();
}
硬件检测:
uint8_t dht11_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DHT11_DQ_IN && retry < 100) /* DHT11会拉低40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100)
{
rval = 1;
}
else
{
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低后会再次拉高40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100) rval = 1;
}
return rval;
}
不接传感器,检测失败!
读数据:
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
dht11_reset();
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;
}
读操作:
uint8_t dht11_read_bit(void)
{
uint8_t retry = 0;
while (DHT11_DQ_IN && retry < 100) /* 等待变为低电平 */
{
retry++;
delay_us(1);
}
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */
{
retry++;
delay_us(1);
}
delay_us(40); /* 等待40us */
if (DHT11_DQ_IN) /* 根据引脚状态返回 bit */
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 从DHT11读取一个字节
* @param 无
* @retval 读到的数据
*/
static uint8_t dht11_read_byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++) /* 循环读取8位数据 */
{
data <<= 1; /* 高位数据先输出, 先左移一位 */
data |= dht11_read_bit(); /* 读取1bit数据 */
}
return data;
}
测试结果:
