一、多定时器分工架构
系统同时需要驱动蜂鸣器、数码管、串口,三件事各需独立计时,用三个定时器分别负责:
|------------|--------------|-----------------------------------------------|
| 定时器 | 负责功能 | 配置要点 |
| Timer0 | 驱动蜂鸣器(PWM方波) | 模式1,16位;中断中翻转 P2.5;初值 g_i 可变控制频率 |
| Timer1 | UART 波特率发生器 | 模式2,8位自动重装;TH1=TL1=232(2400bps);不触发中断 |
| Timer2 | 数码管动态刷新(1ms) | 初值 64613;中断中调用 num_to_buff() + digiter_show() |
|---------------|------------------------------------------------------------------------------|
| 注意 Timer2 | STC89C52 才有 Timer2;标准 8051 只有 Timer0/Timer1。Timer1 被 UART 占用后,蜂鸣器必须用 Timer0。 |
二、DS18B20 传感器规格
|--------------|-----------------------------------------------------------|
| 参数 | 规格 |
| 量程 | −55°C ~ +125°C |
| 精度(误差) | ±0.5°C |
| 分辨率(可配置) | 9位=0.5°C / 10位=0.25°C / 11位=0.125°C / 12位=0.0625°C(默认12位) |
| 接口类型 | GPIO 单总线(1-Wire),DQ 一根线完成双向通信 |
| 工作电压 | 3V ~ 5.5V |
三、单总线(1-Wire)协议原理
3.1 硬件连接
- DQ 线上接 4.7kΩ 上拉电阻到 VCC,空闲时总线保持高电平
- 51 单片机是主机,DS18B20 是从机,主机发起所有通信
- 同一条 DQ 线可挂多个 DS18B20(靠 ROM 地址区分)
3.2 线与特性(Open-Drain 开漏)
|--------------|--------------|----------|---------------|
| A 操作 | B 操作 | 总线结果 | 说明 |
| 拉低 | 释放 | 低电平 | A 把总线拉低,B 不阻止 |
| 释放 | 拉低 | 低电平 | B 把总线拉低 |
| 拉低 | 拉低 | 低电平 | 两个都拉低 |
| 释放 | 释放 | 高电平 | 都释放,上拉电阻把总线拉高 |
|-------------|------------------------------------------------------------------------|
| 关键 释放总线 | 作为数据接收方时,必须先释放总线(DQ_HIGH),让上拉电阻把总线拉高,再去检测从机发来的电平变化。否则主机一直拉低,永远读不到从机数据。 |
四、DS18B20 通信时序
4.1 宏定义(P3.7 接 DQ)
#define DQ_HIGH (P3 |= (1 << 7)) // 释放总线(高电平/高阻态)
#define DQ_DOWN (P3 &= ~(1 << 7)) // 拉低总线
#define DQ_CHECK ((P3 & (1 << 7)) != 0) // 读总线:非0=高,0=低
4.2 复位时序

|--------------------------------------------------|
| 复位流程 ds18b20_reset() |
| 1. 主机将 DQ 拉低 480~960us → 发出「复位脉冲」 |
| 2. 主机释放 DQ,上拉电阻将总线拉高 |
| 3. DS18B20 检测到上升沿后,等待 15~60us |
| 4. DS18B20 将 DQ 拉低 60~240us → 发出「存在脉冲」(我在!) |
| 5. DS18B20 释放 DQ,总线恢复高电平 |
| 6. 主机在释放后 60~240us 内检测到低电平 → 复位成功,返回 1 |
cs
int ds18b20_reset(void)
{
int time = 0;
// 发复位脉冲:拉低 700us(超过最小480us)
DQ_DOWN; Delay10us(70);
DQ_HIGH; Delay10us(6); // 释放,等60us让总线稳定
// 等待 DS18B20 把总线拉低(存在脉冲)
while (DQ_CHECK && time < 30) { Delay10us(1); time++; }
if (time >= 30) return -1; // 超时:无设备
// 等待 DS18B20 释放总线(恢复高)
time = 0;
while (!DQ_CHECK && time < 30) { Delay10us(1); time++; }
if (time >= 30) return -2; // 超时:通信异常
return 1; // 复位成功
}
4.3 写时序(51 向 DS18B20 发送1字节)

|-----------|------------------|--------------------|------------|
| 写的bit | 主机动作 | DS18B20 动作 | 时间要求 |
| 写 1 | 拉低 >1us 后立即释放 | 45us内采样,检测到高 = 收到1 | 总时隙 >60us |
| 写 0 | 拉低 60~120us 后释放 | 60us内采样,检测到低 = 收到0 | 总时隙 > |
cs
void write_ds18b20(unsigned char dat)
{
int i;
for (i = 0; i < 8; i++)
{
if (dat & 1)
{ // 当前bit=1(低位先行)
DQ_DOWN; _nop_(); _nop_(); // 拉低约2us
DQ_HIGH; Delay10us(5); // 释放,等50us
}
else
{ // 当前bit=0
DQ_DOWN; Delay10us(6); // 拉低60us
DQ_HIGH; Delay10us(6); // 释放,等60us
}
dat >>= 1; // 移到下一位
}
}
4.4 读时序(DS18B20 向 51 发送1字节)

|------------------------------------|
| 读时隙(每个bit执行一次) |
| 1. 主机将 DQ 拉低 >1us(触发读时隙) |
| 2. 主机立即释放 DQ |
| 3. DS18B20 控制总线:发送0则拉低,发送1则保持高 |
| 4. 主机在释放后 15us 内采样 DQ:高=1,低=0 |
| 5. 等待约 60us,完成此bit,进入下一位 |
cs
unsigned char read_ds18b20(void)
{
int i;
unsigned char dat = 0;
for (i = 0; i < 8; i++)
{
DQ_DOWN; _nop_(); _nop_(); // 拉低约2us,触发读时隙
DQ_HIGH; _nop_(); _nop_(); _nop_(); // 释放,等约3us
if (DQ_CHECK) // 采样:高=1
dat |= (1 << i); // 低位先收,逐位存入
Delay10us(6); // 等60us,完成此bit
}
return dat;
}
五、DS18B20 温度采集完整流程
|------------------------------------------------------------------------|
| get_temp() 完整步骤 |
| 1. ds18b20_reset() 复位,确认设备在线 |
| 2. write_ds18b20(0xCC) Skip ROM,跳过64位ROM匹配(总线只有一个设备时使用) |
| 3. write_ds18b20(0x44) Convert T,命令DS18B20开始ADC温度转换 |
| 4. Delay1ms(1000) 等待1s,12位精度转换最长需要750ms |
| 5. ds18b20_reset() 再次复位,开始读数据 |
| 6. write_ds18b20(0xCC) 再次 Skip ROM |
| 7. write_ds18b20(0xBE) Read Scratchpad,读取暂存器 |
| 8. temp_low = read_ds18b20() 读取温度低字节 |
| 9. temp_high = read_ds18b20() 读取温度高字节 |
| 10. temp = (temp_high<<8)|temp_low,return temp * 0.0625 换算为摄氏度 |
5.1 温度数据16位格式
|---------------------------|----------|-------------------|
| 位 | 权重 | 说明 |
| bit15~bit11 (5位) | 符号位 S | 全0=正温度;全1=负温度(补码) |
| bit10~bit4 (7位) | 整数部分 | 温度的整数位 |
| bit3 (1位) | 0.5°C | 小数第1位 |
| bit2 (1位) | 0.25°C | 小数第2位 |
| bit1 (1位) | 0.125°C | 小数第3位 |
| bit0 (1位) | 0.0625°C | 小数第4位(12位精度最小单位) |
换算公式:摄氏度 = (short)原始16位值 × 0.0625
例:原始值 = 0x01AC = 428(十进制),428 × 0.0625 = 26.75°C
|------------------|------------------------------------------------------------------------------------------|
| 负温度 short 类型 | 使用有符号 short(而非 unsigned short),是因为 DS18B20 负温度时高字节符号位为1,short 能正确处理补码,乘以 0.0625 后自动得到负值。 |
5.2 get_temp() 完整代码
cs
float get_temp(void)
{
unsigned char temp_low = 0, temp_high = 0;
short temp = 0;
// 第一阶段:触发温度转换
ds18b20_reset();
write_ds18b20(0xCC); // Skip ROM
write_ds18b20(0x44); // 开始转换
Delay1ms(1000); // 等待转换完成
// 第二阶段:读取温度数据
ds18b20_reset();
write_ds18b20(0xCC); // Skip ROM
write_ds18b20(0xBE); // Read Scratchpad
temp_low = read_ds18b20(); // 先读低字节
temp_high = read_ds18b20(); // 再读高字节
// 第三阶段:拼合 & 换算
temp = temp_high << 8; // 高字节移到高8位
temp |= temp_low; // 低字节填入低8位
return temp * 0.0625; // 乘以分辨率 = 摄氏度
}
六、延时函数 delay.c
|------------------|-------------|-------------------------------|
| 函数 | 用途 | 说明 |
| delay(n) | 粗略软件延时 | 空循环n次,不精确,用于非时序场合 |
| Delay10us(n) | 精确延时 10us×n | 用 nop() 填充,1-Wire时序必用 |
| Delay1ms(n) | 精确延时 1ms×n | 调用 Delay10us(100) 共n次,等待温度转换用 |
|-----------------|---------------------------------------------------------------------------------------------------|
| 知识点 nop() | 来自 intrins.h,执行一个空操作(NOP指令),占一个机器周期(约1.085us @11.0592MHz)。多个 nop() 叠加可精确控制微秒级延时,是1-Wire时序的关键手段。 |
七、main.c 温度采集与串口上报
cs
#include <stdio.h> // 提供 sprintf
int main(void)
{
float t = 0;
char ds_temp[32]; // 存放格式化后的字符串
uart_init(); // 初始化串口(2400bps)
while (1) {
t = get_temp(); // 读温度(约需1秒)
sprintf(ds_temp, "temp:%.2f\r\n", t); // 格式化为字符串
uart_sendstr(ds_temp); // 串口发送给上位机
}
}
|------------------------|-----------------------------------------------------------------------------------------------------------------|
| sprintf 格式化字符串 | sprintf(buf, "temp:%.2f\r\n", t) 把浮点数 t 格式化为 "temp:26.75\r\n" 存入 buf。%.2f 表示保留2位小数。\r\n 是回车换行,串口助手正确换行必须。 |
八、DS18B20 命令速查
|----------|------------------|-------------------------------|
| 命令码 | 名称 | 用途 |
| 0xCC | Skip ROM | 总线只有一个设备时使用,跳过64位ROM匹配,直接操作 |
| 0x44 | Convert T | 触发ADC开始采样,12位精度最长需等待750ms |
| 0xBE | Read Scratchpad | 读取暂存器,先低字节后高字节(共9字节,一般只读前2字节) |
| 0x4E | Write Scratchpad | 配置分辨率(写入 TH/TL报警值 + 配置寄存器) |
| 0x55 | Match ROM | 总线多设备时,指定某个设备的64位ROM地址通信 |
| 0xF0 | Search ROM | 枚举总线上所有DS18B20的ROM地址 |