☰
蓝桥杯CT107D DS18B20单总线驱动实战:时序、代码与避坑指南
2026/10/15 18:00:23 网站建设 项目流程

1. 赛前准备与DS18B20核心认知

1.1 为什么CT107D平台上的DS18B20值得单独拿出来说

蓝桥杯单片机组的竞赛板CT107D上,DS18B20几乎是一个绕不开的考点。它不像LED、数码管、按键这些外设,只要会写GPIO就能跑起来。DS18B20是单总线器件,一根线既要发命令又要收数据,时序全靠软件模拟,稍微有一点偏差就读出85℃或者直接读不出来。很多选手在赛场上其他模块都调通了,偏偏卡在温度读取上,最后只能放弃这道题。

我前后带过几届参加蓝桥杯的学生,也自己用CT107D做过不少练习项目。DS18B20这个模块,说难其实也不难,核心就是三件事:单总线时序的精确控制、ROM命令与功能命令的正确发送、温度数据的解析与转换。但说简单也不简单,因为CT107D上的DS18B20是通过跳线帽连接到P2.7还是其他引脚,不同版本的板子有差异,而且官方提供的底层驱动往往不能直接拿来用,需要根据实际硬件做适配。

这篇文章我会从硬件连接讲起,把单总线的复位、写时隙、读时隙的时序参数一个个拆开算给你看,然后给出经过实测可用的完整代码,最后把我踩过的坑和排查方法整理成速查表。不管你是第一次接触DS18B20,还是之前调过但没调通,应该都能从里面找到有用的东西。

1.2 CT107D上DS18B20的硬件连接方式

CT107D这块板子的外设布局比较紧凑,DS18B20的接口通常位于板子边缘,标着“DS18B20”或者“U6”之类的丝印。它有三个引脚:VCC、GND、DQ。DQ就是单总线的数据线,通过一个跳线帽或者直接走线连接到单片机的某个IO口。

在大多数CT107D的版本中,DS18B20的DQ引脚连接到P2.7,但这个不是绝对的。有些版本用的是P1.0或者P3.7。所以拿到板子第一件事,不是急着写代码,而是用万用表蜂鸣档测一下DS18B20的DQ脚和哪个IO口导通。这个步骤花不了两分钟,但能避免你后面花两个小时怀疑人生。

另外要注意,DS18B20的DQ引脚需要上拉电阻,典型值是4.7kΩ到10kΩ。CT107D板子上一般已经焊好了这个上拉电阻,所以你不需要外接。但如果你是用杜邦线把DS18B20引出来用,那就得自己加一个上拉电阻,否则总线空闲时无法保持高电平,读出来的数据全是乱的。

供电方面,DS18B20支持寄生电源模式,也就是VCC和GND都接地,靠DQ线在总线高电平时偷电。但CT107D上一般是正常三线供电,VCC接3.3V或5V都可以,DS18B20的工作电压范围是3.0V到5.5V,所以不用纠结。

注意:如果你用的是自己买的DS18B20模块,有些模块板上已经集成了上拉电阻,有些没有。买回来先用万用表量一下DQ和VCC之间的电阻,如果是4.7kΩ左右就说明有上拉,可以直接用;如果是无穷大,就得自己补一个。

1.3 单总线协议的核心概念:一根线怎么完成双向通信

单总线,英文叫1-Wire,顾名思义就是只用一根数据线完成所有通信。这根线既要传命令,又要传数据,还要给器件供电(寄生模式下)。那它怎么区分什么时候是主机说话、什么时候是从机说话?答案就是严格的时序划分。

整个通信过程由主机(单片机)发起,从机(DS18B20)响应。每一次操作都从一个复位脉冲开始,然后主机发送ROM命令,再发送功能命令,最后进行数据读写。所有的数据位,无论是0还是1,都是通过拉低总线的持续时间来区分的。

具体来说,写一个位的时候,主机先把总线拉低,然后根据要写的是0还是1,决定拉低多长时间。写0就拉低至少60微秒,写1就拉低1到15微秒然后释放。DS18B20会在拉低开始后的15到60微秒之间采样总线电平,如果这时候还是低,就认为是0;如果已经释放为高,就认为是1。

读一个位的时候,主机先拉低总线至少1微秒,然后释放,接着在15微秒内采样总线电平。DS18B20如果要发0,就把总线拉低;如果要发1,就释放总线让它被上拉电阻拉高。主机必须在15微秒内完成采样,否则DS18B20可能已经释放了总线,读到的就是错误数据。

这些时间参数看起来很短,但对于STC15系列单片机来说,完全可以用软件延时实现。CT107D上用的单片机主频一般是11.0592MHz或者12MHz,一个机器周期是1微秒左右(12分频)或者更短(1T模式)。所以延时函数需要根据实际主频来调整。

2. 时序参数计算与底层驱动编写

2.1 复位脉冲与存在脉冲的时序要求

复位脉冲是每一次通信的起点。主机需要把总线拉低至少480微秒,然后释放。DS18B20检测到这个长低电平后,会等待15到60微秒,然后把总线拉低60到240微秒作为应答,这个应答就叫存在脉冲。

在CT107D上,我用的是STC15F2K60S2,主频11.0592MHz,工作在1T模式,也就是一个时钟周期约0.09微秒。但实际写延时的时候,我不会去精确算到每一个时钟周期,而是用STC-ISP软件自带的延时函数生成器,生成微秒级和毫秒级的延时,然后根据实测微调。

复位函数的逻辑是这样的:

bit ds18b20_reset(void) { bit presence; DS18B20_DQ = 0; // 拉低总线 delay_500us(); // 保持至少480us DS18B20_DQ = 1; // 释放总线 delay_70us(); // 等待15-60us,让DS18B20拉低 presence = DS18B20_DQ; // 采样总线,如果为0说明有器件应答 delay_400us(); // 等待存在脉冲结束 return presence; }

这里有个细节:释放总线后,不能立刻采样,因为DS18B20需要15到60微秒才会拉低。如果你释放后马上读,读到的肯定是高电平,就会误判为器件不存在。我一般延时70微秒左右再采样,这个时间落在15到60微秒的窗口之后,但又不会太晚以至于错过存在脉冲。

实操心得:如果你发现复位函数总是返回1(无器件),先检查延时是否足够。有些同学用delay_10us()来凑500微秒,结果实际延时只有300多微秒,DS18B20根本来不及响应。用示波器或者逻辑分析仪抓一下波形是最直接的,没有仪器的话就逐步加大延时试。

2.2 写时隙的两种类型与代码实现

写时隙分为写0和写1两种。主机拉低总线开始写时隙,所有写时隙至少持续60微秒,相邻两个写时隙之间至少间隔1微秒的恢复时间。

写0的流程:拉低总线,保持60到120微秒,然后释放。 写1的流程:拉低总线,保持1到15微秒,然后释放,再保持至少45微秒让总线恢复高电平。

代码实现如下:

void ds18b20_write_bit(bit dat) { DS18B20_DQ = 0; if (dat) { delay_5us(); // 拉低约5us,满足1-15us要求 DS18B20_DQ = 1; delay_60us(); // 保持高电平,凑够60us时隙 } else { delay_60us(); // 拉低60us DS18B20_DQ = 1; delay_5us(); // 恢复时间 } } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { ds18b20_write_bit(dat & 0x01); dat >>= 1; } }

注意写字节的时候是低位先发,所以每次取dat & 0x01,然后右移。这个顺序不能搞反,否则DS18B20收到的命令就是错的。

2.3 读时隙的采样窗口与代码实现

读时隙和写时隙类似,也是主机拉低总线开始,但拉低时间很短,只有1到15微秒。然后主机释放总线,在15微秒内采样。DS18B20会在主机拉低后的15微秒内把总线拉低(发0)或释放(发1)。

bit ds18b20_read_bit(void) { bit dat; DS18B20_DQ = 0; delay_2us(); // 拉低约2us DS18B20_DQ = 1; // 释放总线 delay_2us(); // 等待DS18B20输出数据 dat = DS18B20_DQ; // 采样 delay_60us(); // 等待读时隙结束 return dat; } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; if (ds18b20_read_bit()) { dat |= 0x80; } } return dat; }

读字节的时候是低位先读,所以每次把dat右移,如果读到1就把最高位置1。这样8次之后,第一个读到的位在最低位,最后一个读到的位在最高位,正好组成一个完整的字节。

注意:读时隙的采样点非常关键。如果你在释放总线后延时太久才采样,DS18B20可能已经释放了总线,你读到的就是上拉电阻拉高后的1,即使它实际发的是0。我一般延时2微秒左右就采样,这个时间足够DS18B20把总线拉低,又不会太晚。

2.4 延时函数的精度校准方法

上面所有的时序都依赖延时函数。CT107D上常用的STC15系列单片机,可以用STC-ISP软件生成延时函数。但生成的函数是理论值,实际运行时会受到中断、编译器优化等因素影响。

我的做法是:先用STC-ISP生成一个大概的延时函数,然后写一个测试程序,让一个IO口在循环里翻转,用示波器或者逻辑分析仪测量实际周期,再反推延时函数的误差。如果没有仪器,可以用一个简单的方法:让单片机通过串口每隔一秒发送一次数据,用手机秒表粗略计时,如果偏差在5%以内,对于DS18B20来说就够用了。

另外,DS18B20的时序要求虽然严格,但也不是卡到纳秒级。官方数据手册给出的时间范围都有一定的余量,比如写0要求拉低60到120微秒,你拉低70微秒或者80微秒都能正常工作。所以延时函数不需要追求极致精确,只要落在允许范围内就行。

3. 完整驱动流程与温度数据解析

3.1 DS18B20的ROM命令与功能命令

每一次完整的温度转换和读取,都需要经过以下步骤:

  1. 复位
  2. 发送跳过ROM命令(0xCC)
  3. 发送温度转换命令(0x44)
  4. 等待转换完成(最多750毫秒)
  5. 再次复位
  6. 再次发送跳过ROM命令(0xCC)
  7. 发送读取暂存器命令(0xBE)
  8. 读取两个字节的温度数据

如果总线上只挂了一个DS18B20,就可以用跳过ROM命令,不用读64位ROM编码。CT107D上一般只有一个DS18B20,所以直接用0xCC跳过ROM是最简单的。

温度转换命令0x44启动一次转换,转换时间取决于分辨率。默认是12位分辨率,转换时间最长750毫秒。在蓝桥杯的赛场上,你不可能真的等750毫秒,一般延时200到300毫秒就够读了,因为实际转换时间往往比最大值短。但为了保险,我一般延时750毫秒,或者用循环等待的方式,读暂存器的第9个字节(配置寄存器)来判断转换是否完成。

3.2 温度数据的格式与转换公式

DS18B20的温度数据是16位,分为高字节和低字节。低字节的低4位是小数部分,高字节的低4位是符号位,其余是整数部分。

数据位含义
低字节bit0-3小数部分
低字节bit4-7整数部分低4位
高字节bit0-3整数部分高4位
高字节bit4-7符号位(0正1负)

正温度时,直接把高字节左移8位加上低字节,然后乘以0.0625就是摄氏度。负温度时,需要先取反加一,再乘以0.0625,最后加上负号。

float ds18b20_read_temp(void) { unsigned char tl, th; int temp; float result; ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0x44); delay_750ms(); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); tl = ds18b20_read_byte(); th = ds18b20_read_byte(); temp = (th << 8) | tl; if (temp < 0) { temp = ~temp + 1; result = -(temp * 0.0625); } else { result = temp * 0.0625; } return result; }

在蓝桥杯的题目中,通常要求显示整数温度,比如25℃。这时候可以直接取temp的高字节和低字节的高4位,组成整数部分。如果要显示小数,就保留一位或两位小数。

3.3 在CT107D上整合数码管显示温度

CT107D的数码管是共阳极的,通过74HC138译码器选择位选,通过P0口发送段码。要把温度显示出来,需要把浮点数转换成数码管能显示的段码。

我一般先把温度乘以10或者100,转成整数,然后逐位提取。比如25.6℃,乘以10得到256,然后百位是2,十位是5,个位是6,分别查表得到段码,送到数码管。

void display_temp(float temp) { unsigned int t; unsigned char i; unsigned char code seg[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; t = (unsigned int)(temp * 10); for (i = 0; i < 3; i++) { switch (i) { case 0: P0 = seg[t / 100]; break; case 1: P0 = seg[(t / 10) % 10]; break; case 2: P0 = seg[t % 10]; break; } // 位选和锁存操作 // ... delay_1ms(); } }

这里要注意,数码管的刷新频率要足够高,否则会闪烁。一般每位数码管显示1到2毫秒,三位就是3到6毫秒,刷新率在150Hz以上,人眼就感觉不到闪烁了。

实操心得:在蓝桥杯赛场上,如果温度显示不稳定,比如跳变很大,先检查是不是每次读取都重新启动了转换。DS18B20的转换需要时间,如果你连续快速读取,读到的可能是上一次的残留数据或者85℃的默认值。正确的做法是每次读取前都发一次0x44,然后等足够的时间再读。

4. 常见问题排查与赛场避坑指南

4.1 读出来总是85℃怎么办

85℃是DS18B20上电后的默认值,表示温度转换还没有完成,或者你读到的就是上电初始值。出现这个问题的原因通常有三个:

第一,转换命令没有成功发送。检查你的写时序,特别是写0和写1的拉低时间是否正确。如果写1的时候拉低时间太长,DS18B20会把它当成写0,命令就错了。

第二,转换时间不够。12位分辨率下,转换最长需要750毫秒。如果你只延时了100毫秒就去读,读到的就是85℃。解决办法是延时足够长,或者读配置寄存器判断转换是否完成。

第三,复位函数有问题。如果复位时DS18B20没有正确应答,后面的命令都不会被执行。用示波器看一下复位脉冲和存在脉冲的波形,确认DS18B20确实拉低了总线。

4.2 温度值跳变或者偶尔读错

温度值偶尔跳变,比如从25℃跳到0℃或者-55℃,通常是时序不稳定导致的。可能的原因包括:

  • 延时函数受到中断干扰。如果你在读取DS18B20的时候开了定时器中断,中断服务程序可能会打乱时序。解决办法是在读取期间关闭中断,读完再开。
  • 总线上的上拉电阻太大。如果上拉电阻是10kΩ,总线从低电平恢复到高电平的时间会比较长,可能导致读时隙采样错误。换一个4.7kΩ的电阻试试。
  • 电源不稳定。DS18B20在转换期间电流会增大,如果电源滤波不好,电压会跌落,导致复位或者通信失败。在VCC和GND之间并一个0.1uF的电容。

4.3 常见问题速查表

现象可能原因排查方法解决办法
复位无应答延时不足示波器看复位脉冲宽度加大延时到500us以上
读出来总是85℃转换未完成检查延时是否够750ms延时750ms或读配置寄存器
温度值跳变中断干扰读取时关中断读取期间关闭全局中断
温度值固定不变命令发送错误检查写时序确认写0和写1的拉低时间
偶尔读错上拉电阻太大测量DQ和VCC间电阻换成4.7kΩ上拉
数码管显示闪烁刷新频率低计算刷新周期每位数码管显示1-2ms

4.4 赛场上的时间分配与调试策略

蓝桥杯单片机组的比赛时间通常是5个小时,DS18B20这个模块一般会和其他模块一起考,比如温度显示、温度报警、温度控制等。我的建议是:

先把其他模块调通,比如数码管、按键、LED,这些相对简单,能快速拿到基础分。然后再攻DS18B20,因为它的调试时间可能比较长。如果DS18B20实在调不出来,也不要死磕,先把其他功能做完,最后再回来检查。

调试DS18B20的时候,我习惯先写一个最简单的测试程序:只做复位、发0xCC、发0x44、延时、复位、发0xCC、发0xBE、读两个字节,然后把读到的原始数据通过串口打印出来。这样能快速判断是时序问题还是数据解析问题。如果原始数据是0x0550(85℃的原始值),说明转换没完成;如果是0x0000或者0xFFFF,说明通信有问题。

注意:CT107D上的DS18B20引脚可能和其他外设有冲突,比如数码管的位选或者蜂鸣器。在写代码之前,先确认跳线帽的连接,避免引脚被其他外设占用。如果发现读出来的数据完全不对,先检查跳线帽。

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

DS18B20是单总线协议的典型代表,掌握了它的时序控制,其他单总线器件比如DHT11、DS2431等也能触类旁通。单总线的核心思想就是用时间换引脚,通过精确的时序划分来实现双向通信。这种思路在引脚资源紧张的单片机项目中非常实用。

我在实际项目中遇到过需要同时接多个传感器的场景,如果每个传感器都用独立的IO口,引脚很快就不够用了。这时候单总线就能派上用场,多个DS18B20可以挂在同一根总线上,通过64位ROM编码区分。虽然CT107D上一般只用一个,但理解多器件挂载的原理,对以后做项目是有帮助的。

最后再分享一个小技巧:如果你在赛场上发现DS18B20的读数总是比实际温度高几度,先检查一下单片机是不是离DS18B20太近,单片机自身的发热会影响测量。把DS18B20用杜邦线引出来,远离发热源,读数就会准很多。这个细节在平时练习中不容易发现,但在赛场上如果题目对温度精度有要求,就很重要了。

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

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

立即咨询