一、DS18B20
1. DS18B20简介
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点
- 测温范围:-55°C 到 +125°C
- 通信接口:1-Wire(单总线)
- 其它特征:可形成总线结构、内置温度报警功能、可寄生供电
2. 电路原理图
其中1和3号引脚分别连接GND
和VCC
,而二号引脚则用于使用1-Wire(单总线)接口进行通信。
即:
3. 内部结构
内部完整结构框图
- 64-BIT ROM:作为器件地址,用于总线通信的寻址
- SCRATCHPAD(暂存器):用于总线的数据交互
- EEPROM:用于保存温度触发阈值和配置参数
其中配置寄存器可以配置温度变化的精度值。
存储器结构
当我们希望修改EEPROM中存储的内容时,我们需要先将数据写入到暂存器中,然后再发送一条指令使从机将暂存器中的数据写入到EEPROM中。
二、单总线(1-Wire BUS)
由于DS18B20使用的通信接口是1-Wire,因此我们需要学习1-Wire相关的通信协议,这样才能使单片机和它进行通信。
1. 单总线简介
单总线(1-Wire BUS)是由Dallas
公司开发的一种通用数据总线
- 一根通信线:DQ
- 异步、半双工
- 单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线
2. 电路规范
- 设备的DQ均要配置成开漏输出模式
- DQ添加一个上拉电阻,阻值一般为4.7KΩ左右
- 若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路
3. 单总线的时序结构
① 初始化:
- 主机将总线拉低至少480us
- 然后释放总线,等待15~60us
- 存在的从机拉低总线60~240us以响应主机
- 最后从机将释放总线
对应的信号时序图:
② 发送一位:
- 主机将总线拉低60~120us,然后释放总线,表示发送0;
- 主机将总线拉低1~15us,然后释放总线,表示发送1。
- 从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us
对应的信号时序图:
③ 接收一位:
- 主机将总线拉低1~15us,然后释放总线
- 然后主机在拉低后15us内读取总线电平(尽量贴近15us的末尾)
- 读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us
对应的信号时序图:
④ 发送一个byte和接收一个byte:
这个过程和使用I2C发送和接收一个字节的数据的过程类似,都是重复发送一位或接受一位8次即可发送或接受一个byte的数据了:
注意发送和接收到的数据都是低位在前的。
4. DS18B20操作流程
- 初始化:从机复位,主机判断从机是否响应
- ROM操作:ROM指令+本指令需要的读写操作
- 功能操作:功能指令+本指令需要的读写操作
对应的ROM操作和功能操作的指令如下:
ROM指令 | 功能指令 |
---|---|
SEARCH ROM [0xF0] | CONVERT T [0x44] |
READ ROM [0x33] | WRITE SCRATCHPAD [0x4E] |
MATCH ROM [0x55] | READ SCRATCHPAD [0xBE] |
SKIP ROM [0xCC] | COPY SCRATCHPAD [0x48] |
ALARM SEARCH [0xEC] | RECALL E2 [0xB8] |
READ POWER SUPPLY [0xB4] |
各个功能指令的作用:
- CONVERT T:使用温度传感器更新温度值
- WRITE SCRATCHPAD:将各个EEPROM中的值写入到暂存器中
- READ SCRATCHPAD:读取暂存器中的值
- COPY SCRATCHPAD:将暂存器中的内容复制到EEPROM中
- RECALL E2:将EEPROM中的内容复制到暂存器中
- READ POWER SUPPLY:读取设备的供电模式【独立供电|寄生供电】
5. DS18B20数据帧
① 温度变换:初始化→跳过ROM →开始温度变换
② 温度读取:初始化→跳过ROM →读暂存器→连续的读操作
6. 温度的存储格式
里面的温度是使用补码形式存储的,具体的例子如下:
温度表示的范围是-55到+125度
三、编码实现
1. 单总线部分
我们将这部分编写到OneWire
模块中
首先是DQ引脚,根据电路原理图我们可以知道他对应的是芯片的P37
引脚,因此先把它定义出来:
sbit OneWire_DQ = P3 ^ 7;
① 初始化:
unsigned char OneWire_Init() {
unsigned char i;
unsigned char Ack;
EA=0;
{
OneWire_DQ = 1;
OneWire_DQ = 0; // 拉低
i = 227; while (--i); // delay 500μs
OneWire_DQ = 1; // 释放
i = 29; while (--i); // delay 70μs,等待从机将电平拉低
// 获取从机应答
Ack = OneWire_DQ;
i = 227; while (--i); // delay 500μs 等待时序走完
}
EA=1;
// 将从机应答返回,0表示有应答,1表示无应答
return Ack;
}
可以发现,我们的代码中出现了EA=0;
和EA=1;
,这两句代码分别是操作终端系统的总开关进行关闭和开启中断系统的,因为在单总线发送信号的过程中,如果突然来了中断,CPU转而运行中断程序,那么延时就会受到巨大的影响:
例如当前运行到delay 70μs的位置,此时来了一个中断信号,当运行完中断程序回来的时候,可能就已经延时了10ms了,这对单总线通信的影响是致命的,故我们在使用单总线发送或接受信号时都需要先关闭中断。
tips:其中的延时代码都是使用stc-isp软件进行生成的,在生成代码时需要注意晶振和8051指令集的选择:
② 发送一位:
void OneWire_SendBit(unsigned char Bit) {
unsigned char i;
EA=0;
{
OneWire_DQ = 0; // 拉低
i = 3; while (--i); // delay 10μs
// 若为1则释放总线,代表发送1;若为0一直为低电平,代表发送0
OneWire_DQ = Bit;
i = 22; while (--i); // delay 50μs
OneWire_DQ = 1; // 最后释放总线
}
EA=1;
}
③ 接收一位:
unsigned char OneWire_ReceiveBit() {
unsigned char i;
unsigned char Bit;
EA=0;
{
OneWire_DQ = 0; // 拉低
i = 1; while (--i); // delay 5μs
OneWire_DQ = 1; // 释放
i = 1; while (--i); // delay 5μs
Bit = OneWire_DQ; // 采样
i = 22; while (--i); // delay 50μs
}
EA=1;
return Bit;
}
④ 发送一个byte和接收一个byte:
void OneWire_SendByte(unsigned char byte) {
unsigned char i;
for(i = 0; i < 8; i++) {
OneWire_SendBit(byte & (0x01 << i));
}
}
unsigned char OneWire_ReceiveByte() {
unsigned char i;
unsigned char byte = 0x00;
for(i = 0; i < 8; i++) {
if(OneWire_ReceiveBit()) {
byte |= (0x01 << i);
}
}
return byte;
}
2. DS18B20模块
首先我们把需要的命令和依赖的模块(OneWire模块)添加进来:
#include "OneWire.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
① 温度转换:
void DS18B20_ConvertT() {
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
② 温度读取:
float DS18B20_ReadT() {
unsigned char TLSB, THSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
TLSB = OneWire_ReceiveByte(); // 接收低八位
THSB = OneWire_ReceiveByte(); // 接收高八位
Temp = (THSB << 8) | TLSB;
T = Temp / 16.0; // 转换成float类型
return T;
}
3. main.c部分
void main() {
float T, TShow;
LCD_Init();
DS18B20_ConvertT();
Delay(1000);
while (1) {
DS18B20_ConvertT(); // 温度转换
T = DS18B20_ReadT(); // 读取温度
// 显示符号位
if (T < 0) {
LCD_ShowString(2, 0, "-");
TShow = -T;
} else {
LCD_ShowString(2, 0, "+");
TShow = T;
}
LCD_ShowNum(2, 2, TShow, 3); // 整数部分
LCD_ShowString(2, 5, "."); // 小数点
LCD_ShowNum(2, 6, (unsigned long) (TShow * 10) % 10, 1); // 小数部分,一位小数
}
}
这样我们就可以实时地展示温度了。
tips:在开头进行一次温度转换并
Delay(1000);
的原因是:温度转换是需要一定的时间的,如果我们不进行延时而直接取出温度值就会得到初始值25.0。