☰
蓝桥杯CT107D实战:DS18B20单总线驱动与避坑指南
2026/10/15 21:39:46 网站建设 项目流程

1. 为什么CT107D上的DS18B20值得单独拿出来讲

蓝桥杯单片机组的板子——CT107D,几乎是每个参赛选手绕不开的硬件平台。这块板子上集成了不少外设,数码管、独立按键、矩阵键盘、蜂鸣器、继电器、DS1302、AT24C02、DS18B20、超声波、红外接收等等,基本上把常见嵌入式外设都塞进去了。其中DS18B20这个温度传感器,看起来简单,一根线就能读温度,但实际在CT107D上跑通它,踩坑的人不在少数。

我自己第一次在CT107D上写DS18B20驱动的时候,读出来的温度要么是85,要么是0,要么干脆一直是-127。折腾了大半天才发现,问题出在时序延时上——51单片机的机器周期和DS18B20的微秒级时序要求之间,需要非常精确的配合。后来把延时函数调好了,温度一下子就出来了。

这篇文章就是把我自己在CT107D上调试DS18B20的完整过程整理出来,从单总线协议的底层原理,到具体的延时计算,再到完整的代码实现和常见问题排查,都会讲到。不管你是刚接触蓝桥杯单片机的新手,还是已经写过几遍DS18B20但总是读不准的老手,应该都能从里面找到有用的东西。

DS18B20的核心价值在于它用一根数据线就完成了供电、复位、读写全部通信,这在引脚资源紧张的单片机系统里非常实用。CT107D上把它接在P1.4口附近(具体引脚后面会详细说),通过单总线协议和单片机通信。理解了这个协议,你以后遇到DHT11、AM2302这类单总线传感器,上手会快很多。

2. DS18B20和单总线协议的核心原理拆解

2.1 DS18B20到底是怎么工作的

DS18B20是Dallas Semiconductor(现在归Maxim)出的数字温度传感器,测温范围-55°C到+125°C,在-10°C到+85°C范围内精度是±0.5°C。它内部有三个主要部件:温度敏感元件、ADC转换器和单总线接口逻辑。温度敏感元件把温度转换成电压信号,ADC把它变成数字量,然后通过单总线接口发出去。

它的核心寄存器有两个需要重点关注:温度寄存器和配置寄存器。温度寄存器是16位的,高5位是符号位,低4位是小数位。默认分辨率是12位,对应0.0625°C的分度值。配置寄存器用来设置分辨率,可以选9到12位,分辨率越低转换越快,但精度也越低。

DS18B20的供电方式有两种:寄生电源模式和外部电源模式。寄生电源模式下,DS18B20从数据线上"偷电"——当总线为高电平时,内部电容充电;当总线为低电平时,电容放电维持工作。CT107D上一般用的是外部电源模式,VDD直接接3.3V或5V,这样通信更稳定,也不需要在温度转换期间强拉总线。

2.2 单总线协议的时序本质

单总线协议(1-Wire)的核心思想是:用一根线,通过不同的拉低时长来区分不同的命令。所有通信都由主机(单片机)发起,DS18B20作为从机响应。

协议里定义了六种基本信号:

信号类型主机动作从机响应典型时长
复位脉冲拉低总线等待480-960μs
存在脉冲释放总线拉低总线60-240μs
写0拉低总线无60-120μs
写1拉低总线后快速释放无1-15μs拉低+释放
读0拉低总线后释放保持低电平整个时隙
读1拉低总线后释放释放总线整个时隙

关键点在于:所有的时隙(time slot)都必须由主机拉低总线来启动。写时隙和读时隙的区别在于,写时隙主机在整个时隙内控制总线电平,读时隙主机只在开始时拉低,然后释放,由从机决定总线电平。

这里最容易出错的地方是读时隙。主机拉低总线至少1μs,然后释放,必须在15μs内采样总线电平。如果采样太晚,DS18B20可能已经把数据发完了,读到的一直是1。我见过不少人读温度一直是0xFFFF,就是因为采样时机不对。

2.3 CT107D上的硬件连接细节

CT107D开发板上,DS18B20的数据引脚(DQ)连接到单片机的P1.4(不同批次的板子可能略有差异,但绝大多数是P1.4)。VDD接电源,GND接地。板上通常还有一个跳线帽,用来选择DS18B20是否接入总线。

这里有个细节需要注意:CT107D上的P1口还复用了其他功能,比如数码管段选、蜂鸣器、继电器等。P1.4除了接DS18B20,还可能和别的外设共享。所以在写代码的时候,操作P1口要特别小心,不能直接对整个P1赋值,否则会干扰其他外设。

正确的做法是位操作:用sbit DQ = P1^4;定义数据引脚,然后只对DQ进行读写,不影响P1口其他位。这一点在蓝桥杯比赛里非常关键,因为题目往往要求同时使用多个外设,一个不小心就会互相干扰。

3. 在CT107D上实现DS18B20驱动的完整过程

3.1 延时函数的精确计算

51单片机的时序完全依赖软件延时,所以延时函数的精度直接决定了DS18B20能不能正常工作。CT107D上用的晶振通常是12MHz(有些板子是11.0592MHz,但蓝桥杯官方板一般是12MHz)。12MHz晶振下,一个机器周期是1μs,也就是说一个NOP指令正好是1μs。

但C语言写的延时函数,实际执行时间还受到编译器优化、函数调用开销等因素影响。所以最可靠的方法是用示波器或者逻辑分析仪实测,或者根据经验值反复调整。

我常用的微秒级延时函数是这样的:

void Delay_us(unsigned int us) { while(us--) { _nop_(); _nop_(); } }

这个函数在12MHz晶振下,每次循环大约2μs(两个NOP加循环开销)。实际用的时候需要根据DS18B20的时序要求来调整。比如复位脉冲需要拉低480μs以上,那就调用Delay_us(240)左右。

更精确的做法是用STC-ISP软件里的延时计算器,或者直接写汇编。但在蓝桥杯比赛里,用C语言加NOP的方式足够了,关键是实测调整。

3.2 复位和存在脉冲的检测

复位脉冲是每次通信的开始。主机拉低总线至少480μs,然后释放,等待DS18B20拉低总线60-240μs作为存在脉冲。

bit DS18B20_Reset(void) { bit presence; DQ = 0; Delay_us(240); // 拉低约480μs DQ = 1; Delay_us(30); // 等待15-60μs presence = DQ; // 采样存在脉冲 Delay_us(200); // 等待存在脉冲结束 return presence; }

这段代码里,presence为0表示DS18B20有响应,为1表示没有检测到器件。如果一直返回1,说明硬件连接有问题,或者延时不对。

注意:存在脉冲的采样时机很关键。拉高总线后等待15-60μs再采样,太早或太晚都可能读错。我一般用30μs,实测比较稳。

3.3 写时隙和读时隙的实现

写一个字节需要8个写时隙,每个时隙至少60μs,时隙之间至少恢复1μs。

void DS18B20_WriteByte(unsigned char dat) { unsigned char i; for(i=0; i<8; i++) { DQ = 0; _nop_(); _nop_(); DQ = dat & 0x01; Delay_us(30); // 保持60μs左右 DQ = 1; dat >>= 1; } }

写0和写1的区别在于:写0时,DQ拉低后保持60μs;写1时,DQ拉低后很快释放(1-15μs内)。上面的代码里,DQ = dat & 0x01;这一句决定了是写0还是写1。如果dat的最低位是0,DQ保持0;如果是1,DQ被置1。

读时隙稍微复杂一点:

unsigned char DS18B20_ReadByte(void) { unsigned char i, dat = 0; for(i=0; i<8; i++) { dat >>= 1; DQ = 0; _nop_(); _nop_(); DQ = 1; _nop_(); _nop_(); if(DQ) dat |= 0x80; Delay_us(30); } return dat; }

读时隙的关键是:主机拉低至少1μs,然后释放,在15μs内采样。上面的代码里,拉低后两个NOP(约2μs),然后释放,再两个NOP后采样。这个时机需要根据实际晶振调整。

3.4 温度转换和读取的完整流程

DS18B20读温度的标准流程是:

  1. 复位
  2. 跳过ROM(0xCC)
  3. 启动温度转换(0x44)
  4. 等待转换完成(12位分辨率需要750ms)
  5. 复位
  6. 跳过ROM(0xCC)
  7. 读取暂存器(0xBE)
  8. 读两个字节的温度数据
  9. 转换成实际温度值
float DS18B20_GetTemp(void) { unsigned char L, H; int temp; float result; DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0x44); // 等待转换,可以用延时或者检查总线 Delay_ms(750); DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); L = DS18B20_ReadByte(); H = DS18B20_ReadByte(); temp = (H << 8) | L; // 处理负温度 if(temp & 0x8000) { temp = ~temp + 1; result = -(temp * 0.0625); } else { result = temp * 0.0625; } return result; }

这里有个细节:负温度的处理。DS18B20输出的是补码,如果最高位是1,表示负温度。需要先取反加一得到绝对值,再乘以0.0625,最后加上负号。

另外,12位分辨率下转换时间是750ms,如果比赛时时间紧张,可以把分辨率降到9位(转换时间93.75ms),但精度会降到0.5°C。具体怎么选,看题目要求。

4. 常见问题排查与实战避坑指南

4.1 温度一直读85或者-127怎么办

这是最经典的问题。85°C是DS18B20上电后的默认值,如果你读到的一直是85,说明温度转换根本没有启动,或者转换命令没有发出去。

排查步骤:

  • 检查复位函数是否返回0(有存在脉冲)
  • 检查写时隙的延时是否足够
  • 检查是否发送了0x44命令
  • 检查等待转换的时间是否足够

-127°C(0xFFF)通常表示读到的全是1,也就是总线一直是高电平。可能的原因:

  • DQ引脚定义错了
  • 硬件连接松动
  • 读时隙采样太晚,DS18B20已经释放总线

我遇到过一次,DQ定义成了P1^3,结果怎么读都是-127。后来改成P1^4就正常了。所以引脚定义一定要对照板子原理图确认。

4.2 温度跳动很大怎么处理

有时候读到的温度会在两个值之间跳,比如25.5和25.6来回变。这通常是分辨率设置和显示处理的问题。

12位分辨率下,温度的最小分度是0.0625°C。如果你直接显示两位小数,就会看到跳动。解决办法有两种:

  • 降低分辨率到11位或10位,减少跳动
  • 在显示前做滑动平均滤波,取最近几次读数的平均值

我在比赛里一般用滑动平均,取5次读数去掉最高最低再平均,效果不错。

4.3 和其他外设冲突怎么解决

CT107D上P1口复用严重,DS18B20用P1.4,数码管段选也用P1口,蜂鸣器用P1.5,继电器用P1.4(有些板子)。如果同时使用这些外设,很容易冲突。

解决办法:

  • 操作DS18B20时,先保存P1口状态,操作完恢复
  • 或者用位操作,只改变DQ位,不影响其他位
  • 数码管动态扫描和DS18B20读取分时进行,避免同时操作

我一般的做法是:DS18B20读取放在主循环里,数码管扫描放在定时器中断里,两者互不干扰。但要注意,DS18B20的延时函数不能被中断打断,否则时序会乱。所以读取DS18B20的时候,最好关一下总中断,读完再开。

4.4 常见问题速查表

现象可能原因解决方法
一直读85转换未启动检查0x44命令和复位
一直读-127总线一直高检查DQ定义和硬件连接
温度跳动大分辨率过高降低分辨率或滑动平均
读数为0读时隙采样太晚调整采样时机
偶尔读错中断干扰时序读取时关中断
负温度显示错误补码处理不对取反加一再加负号

提示:蓝桥杯比赛里,DS18B20的题目通常要求显示整数温度,这时候可以直接取温度值的高字节和低字节的高4位,拼成一个整数,省去浮点运算,速度更快。

5. 代码优化与比赛实战建议

5.1 用整数运算代替浮点

51单片机没有硬件浮点单元,浮点运算很慢。如果只需要显示整数温度,可以这样处理:

int temp_int; temp_int = ((H << 8) | L) >> 4; // 取整数部分

这样得到的就是整数温度,省去了浮点乘法和类型转换。如果题目要求一位小数,可以这样:

int temp_int, temp_dec; temp_int = ((H << 8) | L) >> 4; temp_dec = (((H & 0x0F) << 4) | (L >> 4)) * 10 / 16;

这样temp_int是整数部分,temp_dec是小数部分(0-9),显示的时候分开显示就行。

5.2 状态机方式读取温度

在比赛里,主循环往往要处理按键、显示、通信等多个任务。如果DS18B20用阻塞式延时750ms,整个系统会卡住。更好的方式是状态机:

  • 状态0:发送转换命令
  • 状态1:等待转换(用定时器计时)
  • 状态2:读取温度
  • 状态3:处理数据

这样主循环不会被阻塞,其他任务也能正常运行。我在省赛的时候就是用这种方式,按键响应和温度显示都很流畅。

5.3 比赛中的时间分配建议

蓝桥杯单片机组的比赛时间通常是5个小时,DS18B20相关的题目一般占10-15分。建议的时间分配:

  • 硬件确认和引脚定义:10分钟
  • 复位和读写函数调试:30分钟
  • 温度转换和显示:20分钟
  • 与其他外设整合:30分钟
  • 预留调试时间:30分钟

如果DS18B20调了超过1小时还没出来,先放一放,把其他部分做完再回来调。有时候换个思路,问题反而迎刃而解。

5.4 一个容易被忽略的细节:总线释放时间

每次通信结束后,总线需要至少1μs的恢复时间。如果连续操作DS18B20,比如写完命令马上读,中间没有足够的恢复时间,可能会导致通信失败。我在写读函数的时候,每个时隙结束后都会加一个短延时,确保总线恢复。

另外,DS18B20在温度转换期间,总线应该保持高电平。如果这时候有其他外设拉低总线,转换可能会中断。所以转换期间最好不要操作P1口其他位。

6. 从DS18B20延伸出去的单总线思维

DS18B20只是单总线协议的一个应用。理解了它的时序本质,你会发现很多传感器都用了类似的思想——用一根线,通过不同的电平持续时间来编码信息。比如DHT11用一根线传输40位数据,红外接收头用一根线输出NEC协议编码,超声波模块用两根线(触发和回响)完成测距。

单总线协议的精髓在于时间换空间:用精确的时序控制代替多根数据线,节省了引脚资源,但增加了软件复杂度。在引脚紧张的嵌入式系统里,这种取舍非常常见。

我在实际项目中用过DS18B20做多点测温,一条总线上挂8个传感器,每个传感器有唯一的64位ROM地址。通过匹配ROM命令,可以单独读取每个传感器的温度。这种方式在分布式测温系统里非常实用,比如机房温度监控、农业大棚测温等。

回到蓝桥杯,DS18B20的题目万变不离其宗:读温度、显示温度、温度控制(比如超过阈值报警)。把底层驱动写稳了,上层逻辑就是搭积木。我建议你在练习的时候,把DS18B20的驱动单独封装成两个文件(.c和.h),以后遇到类似的题目直接拿来用,省时省力。

最后分享一个我自己的小习惯:每次写完DS18B20的代码,我都会先用一个简单的测试程序,只读温度并通过串口打印出来,确认底层没问题了,再整合到完整的项目里。这样排查问题的时候,能快速定位是底层驱动的问题还是上层逻辑的问题。这个习惯帮我省了很多调试时间,你也可以试试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询