一、DHT11 温湿度传感器
1.1 规格参数
|--------------|-----------------------------------------------|
| 参数 | 规格 |
| 温度范围 | 0°C ~ 50°C,精度 ±2°C,分辨率 1°C |
| 湿度范围 | 20%RH ~ 90%RH,精度 ±5%RH,分辨率 1%RH |
| 工作电压 | 3V ~ 5.5V |
| 接口类型 | GPIO 单总线(1根 DATA 线),高位先发(MSB First) |
| 数据格式 | 40 bit = 5 字节:湿度整数 + 湿度小数 + 温度整数 + 温度小数 + 校验和 |
|-----------------------|------------------------------------------------------------------------------------------------------------|
| 对比 vs DS18B20 | DS18B20:温度专测,-55~125°C,精度 ±0.5°C,12位分辨率(0.0625°C),高位先发 DHT11:温湿度双测,0~50°C,精度 ±2°C,1°C分辨率,高位先发(MSB First) |
1.2 数据格式(40 bit)
|-------------------|------------|----------------------------------------------|
| 字节 | 内容 | 示例 |
| byte[0] | 湿度整数部分 | 0x3C = 60(表示60%RH) |
| byte[1] | 湿度小数部分 | DHT11小数位恒为0x00 |
| byte[2] | 温度整数部分 | 0x1A = 26(表示26°C) |
| byte[3] | 温度小数部分 | DHT11小数位恒为0x00 |
| byte[4] | 校验和 | byte[0]+byte[1]+byte[2]+byte[3] 的低8位 |
1.3 DHT11 通信时序详解
① 主机发送起始信号
|-----------------------------------------------------|
| 起始信号(master_start) |
| ****1.****主机将 DATA 引脚拉低,持续至少 18ms(代码用 Delay_ms(20)) |
| ****2.****主机释放总线(拉高),等待 20~40us |
| ****3.****DHT11 检测到起始信号,准备响应 |
② DHT11 应答信号
|--------------------------------------------------|
| 应答信号(dht11_response) |
| ****1.****DHT11 将总线拉低约 80us(主机检测低电平,确认 DHT11 存在) |
| ****2.****DHT11 将总线拉高约 80us(主机检测高电平,准备接收数据) |
| ****3.****DHT11 再次将总线拉低,开始发送第1个bit |
③ DHT11 数据传输(高位先发)
|---------------|-----------------------|---------------|----------------------|
| 发送bit | DHT11动作 | 时间参数 | 判断方法 |
| 发送 0 | 拉低 50us + 拉高 26~28us | 高电平时间 < 50us | 延时60us后采样,若还是高=1,低=0 |
| 发送 1 | 拉低 50us + 拉高 60~70us | 高电平时间 > 50us | 同上,高电平持续更长 |
|------------------|-------------------------------------------------------------------------------------------|
| 关键 判断0或1 | 每bit先等低电平结束,然后延时60us,此时采样:若为高电平 → 1(高电平维持60~70us还未结束);若为低电平 → 0(高电平只持续26~28us,60us时已结束) |
1.4 宏定义(P2.0 接 DATA)
cs
#define DHT11_PIN_HIGH (P2 |= (1 << 0)) // 释放/拉高
#define DHT11_PIN_DOWN (P2 &= ~(1 << 0)) // 拉低
#define DHT11_PIN_CHECK ((P2 & (1 << 0)) != 0) // 读引脚:非0=高,0=低
1.5 完整代码详解
master_start() --- 发送起始信号
cs
void master_start(void)
{
DHT11_PIN_DOWN; // 拉低
Delay_ms(20); // 持续20ms(大于最小18ms)
DHT11_PIN_HIGH; // 释放总线
Delay_30us(1); // 等待30us让DHT11检测到起始
}
dht11_response() --- 等待应答
cs
int dht11_response(void)
{
int time = 0;
// 等待DHT11拉低(应答低电平,最多等4*30us=120us)
while (DHT11_PIN_CHECK && time < 4) { Delay_30us(1); time++; }
if (time >= 4) return -1; // 超时:DHT11无响应
// 等待DHT11拉高(应答高电平)
time = 0;
while (!DHT11_PIN_CHECK && time < 4) { Delay_30us(1); time++; }
if (time >= 4) return -2;
// 等待DHT11拉低,准备开始发数据
while (DHT11_PIN_CHECK);
return 1;
}
dht11_read() --- 读取40bit数据
cs
int dht11_read(void)
{
int i, j;
unsigned char dht_dat[5] = {0};
unsigned char checksum = 0;
master_start();
if (dht11_response() != 1) return -1;
for (j = 0; j < 5; j++)
{ // 5字节
for (i = 0; i < 8; i++)
{ // 每字节8位,高位先收
while (!DHT11_PIN_CHECK); // 等低电平结束(50us低)
Delay_30us(2); // 延时60us后采样
if (DHT11_PIN_CHECK) // 仍为高 = 1
dht_dat[j] |= (1 << (7 - i)); // 高位先存
while (DHT11_PIN_CHECK); // 等高电平结束
}
}
// 校验:前4字节之和 == 第5字节
cs
checksum = dht_dat[0]+dht_dat[1]+dht_dat[2]+dht_dat[3];
if (checksum != dht_dat[4]) return -2;
humity = dht_dat[0]; // 湿度整数
temp = dht_dat[2]; // 温度整数
return 1;
}
main.c --- 每秒上报温湿度
cs
int main(void)
{
uart_init();
while (1)
{
if (dht11_read() == 1)
{
uart_sendstr("temp:");
uart_sendnum(temp); // 发送温度数字
uart_sendstr(" humity:");
uart_sendnum(humity); // 发送湿度数字
uart_sendstr("\r\n");
}
Delay_ms(1000); // 每1秒读一次
}
}
二、DS1302 实时时钟芯片
2.1 芯片概述
|---------------|-------------------------------------------------|
| 特性 | 说明 |
| 芯片型号 | DS1302(Dallas 公司,低功耗实时时钟 RTC) |
| 工作电压 | 2V ~ 5.5V |
| 通信接口 | 3线同步串行:CE(使能)+ SCLK(时钟)+ I/O(数据,双向) |
| 时间功能 | 秒、分、时、日、月、周、年(带自动闰年补偿,最高2100年) |
| 数据格式 | BCD码(Binary Coded Decimal):如 26分钟 = 0x26,不是0x1A |
| 内置RAM | 31字节静态 RAM,掉电不丢失(备用电池供电) |
| 数据顺序 | LSB 先发(低位先行) |
2.2 引脚定义(接 P3 口)
|------------------|------------|------------|----------------------|
| 引脚 | 名称 | 连接 | 说明 |
| CE / RST | 芯片使能 | P3^5 | 高电平开始通信,低电平结束通信 |
| SCLK | 串行时钟 | P3^6 | 上升沿写入数据,下降沿读取数据 |
| I/O | 数据输入输出 | P3^4 | 双向,LSB先行,写命令+写数据/读数据 |
| VCC2 | 主电源 | VCC | 正常供电 |
| VBAT | 备用电池 | 锂电池 | 掉电后维持时钟运行 |
| GND | 地 | GND | |
| X1/X2 | 晶振 | 32.768kHz | 外接晶振,提供精准时钟源 |
cs
sbit DS1302_RST = P3^5; // CE 引脚
sbit DS1302_CLK = P3^6; // SCLK 引脚
sbit DS1302_IO = P3^4; // I/O 引脚
2.3 寄存器地址表
|----------------------|--------------|---------------|-------------------|
| 寄存器(读地址/写地址) | 存储内容 | BCD范围 | 说明 |
| 0x81 / 0x80 | 秒 Seconds | 00~59 | bit7=CH,CH=1时钟停止 |
| 0x83 / 0x82 | 分 Minutes | 00~59 | |
| 0x85 / 0x84 | 时 Hours | 1~12 / 0~23 | bit7=12/24模式 |
| 0x87 / 0x86 | 日 Date | 01~31 | |
| 0x89 / 0x88 | 月 Month | 01~12 | |
| 0x8B / 0x8A | 周 Day | 1~7 | 1=周日 |
| 0x8D / 0x8C | 年 Year | 00~99 | 如2025写0x25 |
| 0x8F / 0x8E | 写保护 WP | --- | 0x00=关保护;0x80=开保护 |
|-------------------|--------------------------------------------------------------------------------------------------|
| BCD码 格式说明 | 所有时间值均为BCD码格式。例:2025年3月18日 14:10:33 周二 写法为:{0x33, 0x10, 0x14, 0x18, 0x03, 0x02, 0x25}(秒分时日月周年顺序) |
2.4 写时序(ds1302_write_byte)
|------------------------------------------------------|
| 写操作流程(LSB先行) |
| ****1.****CE(RST)拉低,延时 nop()(确保初始状态) |
| ****2.****SCLK 拉低,延时 nop() |
| ****3.****CE(RST)拉高,使能芯片,开始通信 |
| ****4.****循环8次发送命令字节(地址),每位在 SCLK 下降沿前设置 I/O,然后产生上升沿 |
| ****5.****循环8次发送数据字节,同样在 SCLK 下降沿前设置,产生上升沿 |
| ****6.****CE(RST)拉低,结束通信 |
cs
void ds1302_write_byte(u8 addr, u8 dat)
{
u8 i;
DS1302_RST = 0; _nop_(); // CE拉低
DS1302_CLK = 0; _nop_(); // SCLK拉低
DS1302_RST = 1; _nop_(); // CE拉高,使能
// 发送命令字节(地址),LSB先行
for (i = 0; i < 8; i++)
{
DS1302_IO = addr & 0x01; // 取最低位
addr >>= 1; // 右移准备下一位
DS1302_CLK = 1; _nop_(); // 上升沿:DS1302 读取数据
DS1302_CLK = 0; _nop_(); // 下降沿:准备下一位
}
// 发送数据字节,方式相同
for (i = 0; i < 8; i++)
{
DS1302_IO = dat & 0x01;
dat >>= 1;
DS1302_CLK = 1; _nop_();
DS1302_CLK = 0; _nop_();
}
DS1302_RST = 0; _nop_(); // CE拉低,结束
}
2.5 读时序(ds1302_read_byte)
|--------------------------------------------------|
| 读操作流程 |
| ****1.****CE 拉低 → SCLK 拉低 → CE 拉高(同写操作初始化) |
| ****2.****先发送命令字节(地址,LSB先行),SCLK 产生8个时钟 |
| ****3.****切换为读模式:在 SCLK 上升沿之前读取 I/O 上的数据(MSB先移入) |
| ****4.****循环8次读取数据字节(每次读一位,拼合为完整字节) |
| ****5.****CE 拉低,结束 |
cs
u8 ds1302_read_byte(u8 addr)
{
u8 i, value = 0, temp;
DS1302_RST=0; _nop_(); DS1302_CLK=0; _nop_(); DS1302_RST=1; _nop_();
// 先发命令字节(写地址,LSB先行)
for (i = 0; i < 8; i++)
{
DS1302_IO = addr & 0x01; addr >>= 1;
DS1302_CLK = 1; _nop_(); DS1302_CLK = 0; _nop_();
}
// 读数据字节:上升沿前读取 I/O,串行移入 value
for (i = 0; i < 8; i++)
{
temp = DS1302_IO; // 读当前位
value = (temp << 7) | (value >> 1); // 从高位移入(LSB先出 → 右移拼接)
DS1302_CLK = 1; _nop_(); DS1302_CLK = 0; _nop_();
}
DS1302_RST = 0;
return value;
}
2.6 初始化与读取时间
cs
// 时间数组:{秒, 分, 时, 日, 月, 周, 年}(BCD格式)
u8 gWRITE_RTC_ADDR[7] = {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};
u8 gREAD_RTC_ADDR[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
u8 gDS1302_TIME[7] = {0x33,0x10,0x14,0x18,0x03,0x02,0x25};
// 秒33 分10 时14 日18 月03 周二 年25(2025)
void ds1302_init(void)
{
u8 i;
ds1302_write_byte(0x8E, 0x00); // 关闭写保护
for (i = 0; i < 7; i++) // 写入初始时间
{
ds1302_write_byte(gWRITE_RTC_ADDR[i], gDS1302_TIME[i]);
}
ds1302_write_byte(0x8E, 0x80); // 开启写保护
}
void ds1302_read_time(void)
{
u8 i;
for (i = 0; i < 7; i++)
{ // 读取当前时间到数组
gDS1302_TIME[i] = ds1302_read_byte(gREAD_RTC_ADDR[i]);
}
}
2.7 main.c --- 每秒串口打印时间
cs
int main(void)
{
ds1302_init(); // 初始化(写入初始时间)
uart_init();
while (1)
{
ds1302_read_time(); // 读取当前时间
uart_sendstr(gDS1302_TIME); // 串口发送
delay_ms(1000); // 每秒刷新
}
}
三、传感器协议横向对比
|-----------------|--------------|-------------|--------------|---------------|----------------------|
| 传感器 | 接口类型 | 信号线 | 数据顺序 | 数据格式 | 通信特点 |
| DS18B20 | GPIO 单总线 | 1根 DQ | LSB先行 | 原始值×0.0625=°C | 异步,需精确us延时 |
| DHT11 | GPIO 单总线 | 1根 DATA | MSB先行 | 直接整数,1°C精度 | 起始18ms低电平,高电平时长区分0/1 |
| DS1302 | 3线同步串行 | CE+SCLK+I/O | LSB先行 | BCD码 | SCLK上升沿锁存,写命令+写/读数据 |
| UART | 异步串行 | TX+RX | LSB先行 | 自定义 | 波特率对齐,无时钟线 |
四、知识树
第五天总结
├── DHT11 温湿度传感器
│ ├── 接口:GPIO单总线(1根DATA),MSB高位先发
│ ├── 数据:5字节(湿度整数+湿度小数+温度整数+温度小数+校验和)
│ ├── 起始:主机拉低18ms → 释放 → DHT11拉低80us → 拉高80us
│ ├── 数据0:低50us + 高26~28us
│ ├── 数据1:低50us + 高60~70us
│ └── 判断:等低结束 → 延时60us → 采样(高=1,低=0)
│
└── DS1302 实时时钟
├── 接口:3线同步串行(CE+SCLK+I/O),LSB先行
├── 数据:BCD码(如分钟26 = 0x26)
├── 初始化:写0x8E=0x00关保护 → 写7个时间寄存器 → 写0x8E=0x80开保护
├── 写时序:CE拉高 → 发命令字节 → 发数据字节 → CE拉低
└── 读时序:CE拉高 → 发命令字节 → SCLK上升沿读数据 → CE拉低