1. 蓝桥杯单片机与DS18B20温度传感器概述
在蓝桥杯单片机设计与开发竞赛中,DS18B20数字温度传感器是一个高频出现的考核点。这款由Dallas Semiconductor(现为Maxim Integrated)生产的单总线数字温度传感器,因其简单的硬件接口和精确的温度测量能力,成为嵌入式系统温度监测的理想选择。
对于参赛选手而言,掌握DS18B20的驱动原理和编程技巧至关重要。在实际比赛中,约75%的赛题会涉及温度测量功能,而其中90%以上都指定使用DS18B20传感器。这主要得益于它的三大特点:
- 单总线接口(仅需1根数据线)
- 高精度(±0.5℃精度,最高0.0625℃分辨率)
- 独特的64位序列号(支持多设备并联)
注意:虽然DS18B20本身精度很高,但在实际比赛中,由于开发板布线、电源噪声等因素,测量结果可能会有±1℃左右的波动,这属于正常现象。
2. 硬件连接与电气特性
2.1 典型电路连接
在蓝桥杯官方提供的CT107D开发板上,DS18B20通常采用标准连接方式:
code复制VCC —— 3.3V/5V电源
DQ —— P1^4(通过4.7K上拉电阻)
GND —— 接地
这种连接方式属于"寄生供电模式"的变种,虽然保留了外部供电,但仍需上拉电阻保证信号完整性。实测表明,当总线负载较重时(如连接线过长),将上拉电阻减小到2.2K可以提高通信稳定性。
2.2 关键时序参数
DS18B20的通信完全依赖精确的时序控制,主要时序参数如下表所示:
| 操作类型 | 时间参数 | 典型值(μs) | 允许范围(μs) |
|---|---|---|---|
| 复位脉冲 | 主机拉低时间 | 480 | 480-960 |
| 从机响应时间 | 15-60 | - | |
| 写时序 | 写0低电平时间 | 60 | 60-120 |
| 写1低电平时间 | 1 | <15 | |
| 读时序 | 采样窗口时间 | 15 | 必须在15内完成采样 |
在实际编程中,这些时序需要通过精确的延时函数实现。值得注意的是,不同主频的单片机需要调整延时参数。例如,在STC15F2K60S2@11.0592MHz下,一个_nop_()约耗时0.09μs。
3. 底层驱动开发详解
3.1 单总线复位序列实现
复位是单总线通信的起始条件,正确的复位操作应该包含以下步骤:
c复制bit init_ds18b20(void)
{
bit ack;
DQ = 1; // 先释放总线
Delay_OneWire(8); // 短暂延时
DQ = 0; // 主机拉低开始复位
Delay_OneWire(80); // 保持480μs以上低电平
DQ = 1; // 释放总线
Delay_OneWire(14); // 等待15-60μs
ack = DQ; // 读取应答信号
Delay_OneWire(20); // 等待完成整个时序
return ack; // 0=存在设备,1=无设备
}
常见问题排查:
- 如果始终检测不到设备,首先检查硬件连接,特别是上拉电阻
- 若ack偶尔为1,可能是时序不够精确,建议用示波器观察波形
- 在多设备系统中,需要先执行搜索ROM命令
3.2 数据读写时序实现
写时序实现要点
c复制void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++) {
DQ = 0; // 开始写时隙
DQ = dat & 0x01; // 写入数据位
Delay_OneWire(5); // 保持60-120μs
DQ = 1; // 释放总线
dat >>= 1; // 准备下一位
Delay_OneWire(5); // 恢复时间
}
}
读时序实现要点
c复制unsigned char Read_DS18B20(void)
{
unsigned char i, dat = 0;
for(i=0;i<8;i++) {
dat >>= 1; // 先右移准备接收
DQ = 0; // 开始读时隙
_nop_();_nop_(); // 保持1-2μs
DQ = 1; // 释放总线
if(DQ) dat |= 0x80; // 在15μs内采样
Delay_OneWire(5); // 完成整个时隙
}
return dat;
}
经验分享:读时序中最关键的参数是采样窗口时间。实测发现,在STC15单片机11.0592MHz下,从拉低到采样最佳间隔是5-6个_nop_()指令。
4. 温度转换与数据处理
4.1 温度转换流程
完整的温度读取流程应包含以下步骤:
- 初始化总线(复位)
- 发送跳过ROM命令(0xCC)
- 启动温度转换(0x44)
- 等待转换完成(典型延时750ms@12位分辨率)
- 再次初始化总线
- 发送跳过ROM命令
- 发送读取暂存器命令(0xBE)
- 读取前两个字节(温度值)
- 数据格式转换
4.2 温度数据格式解析
DS18B20的温度数据采用16位二进制补码格式存储:
| 位范围 | 含义 | 说明 |
|---|---|---|
| 15-11 | 符号位 | S=1表示负温度 |
| 10-4 | 整数部分 | 7位二进制表示 |
| 3-0 | 小数部分 | 每位代表0.0625℃ |
温度值计算公式:
code复制实际温度 = (原始数据) × 0.0625
示例代码:
c复制float Convert_Temperature(unsigned char LSB, unsigned char MSB)
{
short temp = (MSB << 8) | LSB;
if(temp & 0x8000) { // 负温度处理
temp = (~temp + 1); // 取补码
return -(temp * 0.0625);
}
return temp * 0.0625;
}
4.3 分辨率设置技巧
DS18B20支持9-12位分辨率设置,通过配置寄存器实现:
| 分辨率 | 转换时间 | 配置值 | 温度精度 |
|---|---|---|---|
| 9位 | 93.75ms | 0x1F | 0.5℃ |
| 10位 | 187.5ms | 0x3F | 0.25℃ |
| 11位 | 375ms | 0x5F | 0.125℃ |
| 12位 | 750ms | 0x7F | 0.0625℃ |
设置分辨率示例代码:
c复制void Set_Resolution(unsigned char resolution)
{
init_ds18b20();
Write_DS18B20(0xCC); // 跳过ROM
Write_DS18B20(0x4E); // 写暂存器
Write_DS18B20(0xFF); // TH报警上限
Write_DS18B20(0x00); // TL报警下限
Write_DS18B20(resolution); // 配置寄存器
}
5. 工程实践与优化技巧
5.1 多任务环境下的温度读取
在实时系统中,建议采用状态机方式管理温度转换:
c复制enum TEMP_STATE { IDLE, START_CONV, WAIT_CONV, READ_TEMP };
enum TEMP_STATE temp_state = IDLE;
float current_temp = 0.0;
void Temp_Task(void)
{
static unsigned int conv_time = 0;
switch(temp_state) {
case IDLE:
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0x44);
temp_state = START_CONV;
conv_time = 0;
break;
case START_CONV:
if(++conv_time > 750) { // 750ms@12bit
temp_state = WAIT_CONV;
}
break;
case WAIT_CONV:
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);
current_temp = rd_Temperature();
temp_state = IDLE;
break;
}
}
5.2 温度滤波算法
为消除测量噪声,可采用滑动平均滤波:
c复制#define FILTER_LEN 5
float temp_history[FILTER_LEN];
unsigned char filter_index = 0;
float Filter_Temperature(float new_temp)
{
float sum = 0;
temp_history[filter_index++] = new_temp;
if(filter_index >= FILTER_LEN) filter_index = 0;
for(int i=0; i<FILTER_LEN; i++) {
sum += temp_history[i];
}
return sum / FILTER_LEN;
}
5.3 低功耗优化
对于电池供电系统,可采取以下措施:
- 降低采样频率(如每分钟一次)
- 使用9位分辨率减少转换时间
- 在非采样期间关闭传感器电源
6. 常见问题与解决方案
6.1 温度读取异常排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 固定返回85℃ | 转换未完成就读取 | 增加转换等待时间 |
| 温度值跳变大 | 电源噪声干扰 | 增加电源滤波电容 |
| 偶尔通信失败 | 时序不精确 | 用示波器校准延时 |
| 负温度显示错误 | 未处理补码 | 检查符号位处理代码 |
| 多设备冲突 | 未使用ROM命令 | 实现搜索ROM算法 |
6.2 数码管显示同步问题
当温度值需要显示在数码管时,建议采用以下架构:
c复制void Display_Temperature(float temp)
{
unsigned char buf[4];
int temp_int = (int)(temp * 10); // 保留1位小数
buf[0] = temp_int / 100; // 百位
buf[1] = (temp_int / 10) % 10; // 十位
buf[2] = temp_int % 10; // 个位
buf[3] = 10; // 小数点位
Seg_Display(buf, 4); // 数码管显示函数
}
void main()
{
while(1) {
Display_Temperature(current_temp);
Temp_Task(); // 状态机方式获取温度
Key_Scan(); // 其他任务...
}
}
6.3 多传感器系统设计
虽然蓝桥杯比赛通常只需处理单个DS18B20,但了解多设备系统有助深入理解单总线协议:
- 使用Search ROM命令(0xF0)发现所有设备
- 记录每个设备的64位ROM码
- 通过Match ROM命令(0x55)选择特定设备
- 实现二叉树搜索算法遍历所有设备
7. 竞赛实战建议
根据多年指导经验,在蓝桥杯比赛中使用DS18B20时应注意:
- 代码复用:提前准备好经过验证的驱动代码,比赛时直接调用
- 时间管理:12位分辨率转换需750ms,合理安排其他任务
- 异常处理:添加超时检测,防止程序卡死在温度读取环节
- 显示优化:对温度值进行四舍五入处理,提高显示友好度
- 参数调整:根据具体题目要求调整分辨率,平衡速度与精度
一个经过优化的完整示例:
c复制// 温度读取封装函数
unsigned char Read_Temperature(float *temp)
{
static unsigned char state = 0;
static unsigned int timeout = 0;
switch(state) {
case 0: // 启动转换
if(!init_ds18b20()) return 0;
Write_DS18B20(0xCC);
Write_DS18B20(0x44);
state = 1;
timeout = 0;
break;
case 1: // 等待转换
if(++timeout > 800) { // 约750ms@12bit
state = 2;
}
break;
case 2: // 读取温度
if(!init_ds18b20()) {
state = 0;
return 0;
}
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);
*temp = rd_Temperature();
state = 0;
return 1;
}
return 0;
}
// 在主循环中调用
void main()
{
float temp;
while(1) {
if(Read_Temperature(&temp)) {
Display_Temperature(temp);
}
// 其他任务...
}
}
通过系统学习DS18B20的工作原理和编程技巧,结合多次实际调试经验,参赛选手可以建立起完整的温度测量解决方案,为蓝桥杯竞赛打下坚实基础。