51单片机-外设温度计(DS18B20)知识总结

1. 外设温度计概述

外设温度计通常指通过单片机等主控芯片外接的温度传感器,用于实时采集环境或物体温度。在嵌入式开发中,DS18B20 是最常用的数字温度传感器之一,它采用单总线通信协议,只需一根数据线即可完成与主控的通信,接线简单、成本低廉,广泛应用于工业控制、智能家居、环境监测等场景。

2. DS18B20 传感器简介

DS18B20 是一款单总线的数字温度传感器,可以采集 9 到 12 bit 的温度数据,并支持设置温度上下限报警。它内部集成了温度采集、模数转换和通信逻辑,主控芯片只需通过单总线协议即可读取温度值。

  • 测温范围:-55 ℃ 到 +125 ℃。
  • 供电范围:3.0V 到 5.5V。
  • 采集时间:在 12 bit 分辨率下,采集一次温度耗时约 750ms。
  • 分辨率:可配置为 9 到 12 bit,默认上电为 12 bit。

3. 引脚说明

DS18B20 共有三个引脚,接线方式如下:

引脚 名称 说明
GND 地线 接单片机的 GND
DQ 数据线 通信传输引脚,接单片机的 P37
VCC 电源 接单片机的 VCC 引脚

4. 单总线通信时序

DS18B20 采用单总线协议,所有通信都由主机发起,时序要求较为严格,主要包括复位信号、写时序和读时序三类。

4.1 复位信号

  • 空闲时总线保持高电平。
  • 开始通信时,主机拉低总线至少 480us,让从机复位。
  • 从机会在 480us 之内发送一个 60 到 240us 的低电平脉冲,表示对主机复位信号的响应。

4.2 写时序

  • 主机写 0:拉低总线 60 到 120us,表示发送 0。
  • 主机写 1:先拉低总线至少 1us,必须在 15us 以内拉高总线到高电平,让 DS18B20 采样到高电平。
  • 发送数据时,位与位之间的间隔要大于 1us。

4.3 读时序

  • 读取数据时,主机先拉低总线至少 1us,随后 DS18B20 开始控制总线向主机发送数据。
  • 主机必须在 15us 内,在 13us 左右完成对总线电平的采样。
  • DS18B20 发送 0 时,主机采样到低电平;发送 1 时,主机采样到高电平。

5. 内部暂存器与指令

DS18B20 内部有暂存器用于存放温度转换结果,主机通过发送特定指令来控制传感器工作。常用指令如下:

指令 功能
0xCC 跳过 ROM,直接访问传感器
0x44 启动温度转换,转换结果存入暂存器
0xBE 读取暂存器中的 9 个字节数据

6. 温度读取整体流程

读取 DS18B20 温度数据的完整流程如下:

  1. 发送 DS18B20 复位信号。
  2. 发送 0xCC,跳过 ROM。
  3. 发送 0x44,启动温度转换。
  4. 等待约 800ms,等待转换完成。
  5. 再次发送复位信号。
  6. 发送 0xCC,跳过 ROM。
  7. 发送 0xBE,读取暂存器数据。
  8. 读取 2 个字节数据,第一个字节为 LSB,第二个字节为 MSB。
  9. 温度值 = 读取到的数值 × 0.0625。

7. 代码示例

下面给出基于 51 单片机读取 DS18B20 温度并通过串口输出的完整示例代码,可直接在 Keil 中编译运行。

c 复制代码
#include <reg52.h>
#include <intrins.h>
#include <stdio.h>

sbit DQ = P3^7;

volatile unsigned char RecvBuff[20] = {0};
volatile unsigned char RecvLen = 0;
volatile unsigned char IsCommandLine = 0;

void Delay800ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;
	_nop_();
	i = 6;
	j = 155;
	k = 91;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay480us(void)	//@11.0592MHz
{
	unsigned char data i;
	_nop_();
	i = 218;
	while (--i);
}

void Delay10us(void)	//@11.0592MHz
{
	unsigned char data i;
	i = 2;
	while (--i);
}

void UartHandler(void)  interrupt 4
{
	if (RI)
	{
		RecvBuff[RecvLen] = SBUF;
		RI = 0;
		RecvLen++;
		if ('\n' == RecvBuff[RecvLen-1])
		{
			IsCommandLine = 1;
		}
	}
	return;
}

void UartInit(void)
{
	//设置串口工作模式为模式1
	SCON &= ~(0x3 << 6);
	SCON |= (0x1 << 6);
	//使能接收
	REN = 1;
	//波特率不加倍
	PCON &= ~(0x1 << 7);
	//设置定时器1为8位自动重装载
	TMOD &= ~(0x3 << 4);
	TMOD |= 0x1 << 5;
	//设置波特率为2400,TH1初值为0xF4
	TH1 = 0xF4;
	//开启定时器1
	TR1 = 1;
	//开启串口中断
	ES = 1;
	EA = 1;
	return;
}

void UartSendByte(unsigned char TmpByte)
{
	SBUF = TmpByte;
	while (0 == TI);
	TI = 0;
	return;
}

void UartSendString(char *pstr)
{
	while (*pstr != '\0')
	{
		UartSendByte(*pstr);
		pstr++;
	}
	return;
}

//DS18B20复位,拉低480us
void DQReset(void)
{
	DQ = 0;
	Delay480us();
	DQ = 1;
	return;
}

//读取DS18B20的回复
void ReadDQRespone(void)
{
	Delay10us();
	//等待低电平信号到来
	while (DQ);
	//跳过低电平
	while (!DQ);
	return;
}

void SendByte(unsigned char TmpByte)
{
	int n = 0;
	for (n = 0; n < 8; n++)
	{
		if (TmpByte & (0x1 << n))
		{
			DQ = 0;
			_nop_();
			_nop_();
			DQ = 1;
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
		}
		else
		{
			DQ = 0;
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
			Delay10us();
			DQ = 1;
			_nop_();
			_nop_();
		}
	}
	return;
}

unsigned char RecvByte(void)
{
	unsigned char TmpByte = 0x00;
	unsigned char n = 0;
	for (n = 0; n < 8; n++)
	{
		DQ = 0;
		_nop_();
		_nop_();
		DQ = 1;
		Delay10us();
		if (DQ)
		{
			TmpByte |= (0x1 << n);
		}
		Delay10us();
		Delay10us();
		Delay10us();
		Delay10us();
		Delay10us();
		Delay10us();
	}
	return TmpByte;
}

int ReadTemp(void)
{
	unsigned char LSBVal = 0;
	unsigned char MSBVal = 0;
	int Val = 0;
	//上电1ms,等待电平平稳
	DQ = 1;
	Delay480us();
	Delay480us();
	DQReset();
	ReadDQRespone();
	SendByte(0xCC);
	SendByte(0x44);
	Delay800ms();
	DQReset();
	ReadDQRespone();
	SendByte(0xCC);
	SendByte(0xBE);
	LSBVal = RecvByte();
	MSBVal = RecvByte();
	Val = (MSBVal << 8) | LSBVal;
	Val >>= 4;
	return Val;
}

int main(void)
{
	int Temp = 0;
	char tmpbuff[20] = {0};
	UartInit();
	while (1)
	{
		Temp = ReadTemp();
		sprintf(tmpbuff, "%d", Temp);
		UartSendString(tmpbuff);
		Delay800ms();
	}
	return 0;
}

代码说明:主函数循环读取温度值,通过 sprintf 转换为字符串后经串口发送,每次发送后延时 800ms。下面按功能模块对代码进行详细分析。

7.1 延时函数

代码中提供了三个延时函数,均基于 11.0592MHz 晶振计算循环次数:Delay800ms 用于等待温度转换完成,Delay480us 用于 DS18B20 复位时拉低总线,Delay10us 用于读写时序中的位级延时。这些延时通过空循环实现,精度依赖晶振频率,实际使用时需根据主频调整循环次数。

7.2 串口初始化与收发

UartInit 将串口配置为模式 1(8 位 UART),使能接收,波特率设为 2400,并开启串口中断。串口接收中断 UartHandler 将收到的字节存入 RecvBuff,当收到换行符时置位 IsCommandLine,用于后续命令解析。发送函数 UartSendByteUartSendString 分别实现单字节和字符串发送,主函数通过它们把温度值输出到上位机。

7.3 单总线底层时序

DQReset 实现复位信号,拉低总线 480us 后释放;ReadDQRespone 等待从机的应答脉冲。SendByte 按位发送数据,写 1 时先拉低再快速拉高,写 0 时保持低电平约 60us;RecvByte 按位读取数据,在拉低总线后于 15us 内采样电平,从而还原每个 bit。这两组函数共同实现了 DS18B20 单总线协议的核心时序。

7.4 温度读取与主循环

ReadTemp 严格遵循第 6 节的流程:先复位并等待应答,发送 0xCC 跳过 ROM,发送 0x44 启动转换,延时 800ms 等待完成;再次复位后发送 0xBE 读取暂存器,取出 LSB 和 MSB 两个字节,合并后右移 4 位得到整数温度值。主函数 main 初始化串口后进入死循环,不断读取温度并通过串口发送,实现周期性上报。

7. 注意事项

  • 单总线通信对时序要求严格,主机在读写时要注意拉低和采样的时间窗口,避免误码。
  • DS18B20 上电默认工作在 12 bit 分辨率,温度转换一次约需 750ms,读取前需要留足等待时间。
  • 接线时务必保证 GND 共地,否则通信可能不稳定。
  • 如果使用寄生供电方式,数据线需要外接上拉电阻,具体以实际电路设计为准。
相关推荐
老当益壮梁奶奶5 小时前
ARM汇编学习笔记(四):i.MX6ULL裸机开发:从汇编点灯到C语言BSP工程管理
c语言·arm开发·单片机·嵌入式硬件·51单片机
myy-learn2 天前
51 单片机-串口通信
51单片机
kkkkkkkkkk_Z2 天前
单片机|学习日记:单片机底层架构与寄存器操作全解析
linux·笔记·单片机·学习·51单片机
辰域电子3 天前
STM32项目开源:智能插座控制系统(代码 + 原理图 + 仿真)
stm32·单片机·嵌入式硬件·开源·51单片机·proteus
ouynagda3 天前
DS18B20单总线温度传感器原理与51单片机驱动
51单片机
myy-learn4 天前
【51 单片机入门】点亮第一个 LED|模块化编程 led.c(完整源码 + 踩坑)
51单片机
llilian_164 天前
北斗授时卡同步解决方案 gnss授时卡 计算机时间同步板卡
大数据·网络·人工智能·功能测试·51单片机
老当益壮梁奶奶4 天前
ARM 汇编学习笔记(二)启动代码、指令、函数调用与 C 混合编程
c语言·arm开发·单片机·嵌入式硬件·51单片机
myy-learn4 天前
51单片机-中断-晶体管
51单片机