第 20 章 18B20 温度检测
20.1 18B20 概述
20.1.1 简介
18B20 是一种常用的数字温度传感器,广泛应用于环境监测、工业控制、家居自动化
和设备温度监控等领域。
20.1.2 引脚功能
18B20 引脚功能如下图所示,需要特别强调的是,18B20 采用 1-wire 总线进行通信,
数据的输入输出仅靠一个 DQ 引脚完成。
20.1.3 基本工作原理
20.1.4 1-wire 通信协议
20.1.4.1 概述
1-Wire 协议是一种半双工、串行通信协议,主要用于与各种低成本传感器和存储设备
进行数据交换。同 I2C 协议类似,1-Wire 也采用主从架构,一个主设备可以连接多个从设
备。如下图所示。
由于 1-wire 总线也要求各设备以开漏(Open-Drain)模式接入,因此 DQ 信号线也要
使用上拉电阻。
另外,在 1-Wire 总线中,每个从设备都有一个唯一的 64 位地址,用于设备识别和通
信,这 64 位地址是由制造商在生产设备时烧录进去的,不可更改。
20.1.4.2 初始化
1-Wire 规定,每次通信主设备都要先发送一个复位脉冲,从设备接收到该脉冲后,会
进行复位(例如:从低功耗状态恢复到正常通信状态),然后回复一个存在脉冲,主设备可
根据该存在脉冲获知从设备的健康状态。
初始化操作的时序如下。
20.1.4.3 写操作
写操作的时序如下。
20.1.4.4 读操作
读操作的时序如下。
20.1.5 18B20 通信流程
18B20 规定,主设备与其的每次通信,都要遵循以下流程。
1)初始化
主设备发送复位脉冲,从设备回复存在脉冲。
2)发送 ROM 命令
每个 18B20 都有一个唯一个的 64 位序列号,该序列号存储于其内部的一个 64 位的
ROM 中。在一个主设备同时连接多个 18B20 的场景下,该序列号能起到唯一标识的作用。
通过发送 ROM 命令,主设备可以获取总线上所连接的 18B20 的序列号,并根据序列号,
与特定的 18B20 进行通信。具体 ROM 命令有:
(1)搜索 ROM 命令[0xF0]
功能:识别和列出所有连接在总线上的 18B20 设备。
(2)读取 ROM 命令[0x33]
功能:在单设备连接时,主机可以使用此命令直接读取设备的 ROM 代码。
(3)匹配 ROM 命令[0x55]
功能:该命令后边会跟随一个特定的 64 位 ROM 代码,用以选择总线上特定的设备进
行通信。
(4)跳过 ROM 命令[0xCC]
功能:在单设备环境中,跳过 ROM 步骤,直接与设备通信。
3)发送功能命令
功能命令用于控制 18B20 的具体操作,向前文提到的测温命令、读取温度命令等均属
于功能命令。 常用的命令如下。
(1)温度转换[44h]
该命令的作用是启动温度测量并将温度数据转换为数字格式。接收到该命令后,
18B20 会读取其内部的温度传感元件,将测量到的温度值转换为数字信号,并存储在内部
的暂存器中,以便后续读取。完成上述操作后,18B20 会自动恢复到低功耗状态。
(2)读取暂存寄存器 [BEh]
该命令用于读取 18B20 暂存器中存储的值。该命令会返回暂存器中的全部数据,共 9
个字节,温度值保存在前两个字节,所以一般情况下,只需接收前两个字节即可。
两个字节的结构如下。
关于这两个字节,有两点需要说明:
①18B20 测温 的精 度是可选的, 有 12 位(bit0-bit11)、 11( bit1-bit11 ) 位 、10 位
(bit2-bit11)和 9 位(bit3-bit11)可选,默认 12 位。
②上述两个字节中保存的是温度值的补码。
注意:上述命令或者数据的读写操作均为低位优先。
20.1.6 相关资料
实物图、原理图、封装
20.2 需求描述
使用 18B20 获取环境温度并将其显示在 OLED 上。
20.3 硬件设计
20.3.1 硬件原理图
20.4 软件设计
1)拷贝 OLED 相关代码
将 Dri_IIC.h、Dri_IIC.c、Int_Oled.h、Int_Oled.c 拷贝到当前项目。
2)增加延时函数
(1)Util.h
/**
* @brief 延时 10us
*
*/
void Delay10us();
/**
* @brief 延时 50us
*
*/
void Delay50us();
/**
* @brief 延时 60us
*
* @param n 倍数
*/
void Delay60us(u8 n);
(2)Util.c
void Delay60us(u8 n) //@11.0592MHz
{unsigned char data i;while (n--){i = 25;while (--i);}
}void Delay10us() //@11.0592MHz
{unsigned char data i;i = 2;while (--i);
}
void Delay50us() //@11.0592MHz
{unsigned char data i;_nop_();i = 20;while (--i);
}
3)编写 1-Wire 总线驱动
(1)Dri_1wire.h
#ifndef __DRI_1WRIE_H__
#define __DRI_1WRIE_H__
#include "Util.h"
#include <stc89c5xrc.h>
/**
* @brief 初始化
*
* @return bit 1:存在 0:不存在
*/
bit Dri_1wrie_Init();
/**
* @brief 发送一个字节
*
* @param byte 待发送字节
*/
void Dri_1wrie_SendByte(u8 byte);
/**
* @brief 接收一个字节
*
* @return u8 收到的字节
*/
u8 Dri_1wrie_ReceiveByte();
#endif /* __DRI_1WRIE_H__ */
(2)Dri_1wire.h
#include "Dri_1wrie.h"
#define DQ P44
bit Dri_1wrie_Init()
{bit result;// 主设备发送复位脉冲DQ = 0;Delay60us(8);DQ = 1;// 18B20 回复主设备Delay60us(1);result = ~DQ;Delay60us(7);return result;
}
void Dri_1wrie_SendByte(u8 byte)
{u8 i;for (i = 0; i < 8; i++) {// 拉低总线DQ = 0;// 从低位发送DQ = byte & 0x1;byte >>= 1;// 延时 60usDelay60us(1);// 释放总线DQ = 1;}
}
u8 Dri_1wrie_ReceiveByte()
{u8 byte = 0;u8 i;for (i = 0; i < 8; i++) {// 拉低总线DQ = 0;// 释放总线DQ = 1;// 延时 10usDelay10us();byte >>= 1;if (DQ == 1){byte |= 0x80;}// 等待 50usDelay50us();}return byte;
}
4)编写 DS18B20 接口
(1)Int_DS18B20.h
#ifndef __INT_DS18B20_H__
#define __INT_DS18B20_H__
#include <STC89C5xRC.H>
#include "Util.h"
/**
* @brief 获取温度
*
* @return u8
*/
char Int_DS18B20_GetTemperature()
#endif
(2)Int_DS18B20.c
#include "Int_DS18B20.h"
#include "Dri_1wrie.h"
#include "Util.h"
char Int_DS18B20_GetTemperature()
{u8 ls_byte, ms_byte;// 初始化Dri_1wrie_Init();// 发送跳过 ROM 命令Dri_1wrie_SendByte(0xCC);// 发送测温命令Dri_1wrie_SendByte(0x44);// 初始化Dri_1wrie_Init();// 发送跳过 ROM 命令Dri_1wrie_SendByte(0xCC);// 发送读取暂存器命令Dri_1wrie_SendByte(0xBE);// 读取结果ls_byte = Dri_1wrie_ReceiveByte();ms_byte = Dri_1wrie_ReceiveByte();ls_byte >>= 4;ms_byte <<= 4;return ms_byte | ls_byte;
}
5)Main.c
#include "Int_Oled.h"
#include "Int_DS18B20.h"
void main()
{Int_Oled_Init();Int_Oled_Clear();while (1) {Int_Oled_ShowNum(0, 0, Int_DS18B20_GetTemperature());Delay1ms(500);}
}
第 21 章 DS1302 时钟
21.1 DS1302 概述
21.1.1 简介
DS1302 是一个低功耗、实时时钟(RTC)芯片,如下图所示。
21.1.2 引脚功能
21.1.3 基本工作原理
21.1.4 通信协议
根据上述的工作流程可知,使用 DS1302 时,核心操作就是读写寄存器。
21.1.4.1 写操作
以下是向寄存器写入单个字节的时序图。
21.1.4.2 读操作
以下是从寄存器读取单个字节的时序图
21.1.4.3 常用寄存器
以下是各时间寄存器的地址和每位的定义。
1)中文版
2)英文版
注意:DS1302 内部有一个写保护寄存器,在向 DS1302 写入数据前需要先关闭写保护,
写完再开启写保护。
21.1.5 相关资料
实物图、原理图、封装
21.2 需求描述
将时间信息和温度信息实时地显示在 OLED 上,如下图所示。
21.3 硬件设计
21.3.1 硬件原理图
21.4 软件设计
1)拷贝 OLED 和 18B20 相关代码
2)Int_DS1302.h
#ifndef __INT_DS1302_H__
#define __INT_DS1302_H__
#include "Util.h"
#include <STC89C5xRC.H>
typedef struct
{u8 second;u8 minute;u8 hour;u8 day;u8 month;u8 year;u8 day_of_week;
} Struct_Date;void Int_DS1302_Init(void);
void Int_DS1302_SetDate(Struct_Date *p_st_date);
void Int_DS1302_GetDate(Struct_Date *p_st_date);
#endif /* __INT_DS1302_H__ */
3)Int_DS1302.c
#include "Int_DS1302.h"
#define SCLK P37
#define IO P40
#define CE P41
#define SECOND 0x80
#define MINUTE 0x82
#define HOUR 0x84
#define DAY 0x86
#define MONTH 0x88
#define DAY_OF_WEEK 0x8A
#define YEAR 0x8C
#define WP 0x8E
void Int_DS1302_Init(void)
{SCLK = 0;IO = 0;CE = 0;
}
static void Int_DS1302_WriteByte(u8 byte)
{u8 i;for (i = 0; i < 8; i++) {IO = byte & 0x01;SCLK = 1;SCLK = 0;byte >>= 1;}
}
static u8 Int_DS1302_ReadByte()
{u8 i, byte = 0;for (i = 0; i < 8; i++) {byte >>= 1;if (IO == 1) {byte = byte | 0x80;}SCLK = 1;SCLK = 0;}return byte;
}
static void Int_DS1302_SetRegister(u8 addr, u8 byte)
{CE = 1;Int_DS1302_WriteByte(addr);Int_DS1302_WriteByte(byte);CE = 0;
}
static u8 Int_DS1302_GetRegister(u8 addr)
{u8 byte;CE = 1;Int_DS1302_WriteByte(addr | 1);byte = Int_DS1302_ReadByte();CE = 0;return byte;
}
void Int_DS1302_SetDate(Struct_Date *p_st_date)
{u8 temp;// 关闭写保护Int_DS1302_SetRegister(WP, 0x00);// 设置秒temp = (p_st_date->second % 10) | ((p_st_date->second / 10) << 4);Int_DS1302_SetRegister(SECOND, temp);// 设置分钟temp = (p_st_date->minute % 10) | ((p_st_date->minute / 10) << 4);Int_DS1302_SetRegister(MINUTE, temp);// 设置小时temp = (p_st_date->hour % 10) | ((p_st_date->hour / 10) << 4);Int_DS1302_SetRegister(HOUR, temp);// 设置日temp = (p_st_date->day % 10) | ((p_st_date->day / 10) << 4);Int_DS1302_SetRegister(DAY, temp);// 设置月temp = (p_st_date->month % 10) | ((p_st_date->month / 10) << 4);Int_DS1302_SetRegister(MONTH, temp);// 设置年temp = (p_st_date->year % 10) | ((p_st_date->year / 10) << 4);Int_DS1302_SetRegister(YEAR, temp);// 设置星期Int_DS1302_SetRegister(DAY_OF_WEEK, p_st_date->day_of_week % 10);// 开启写保护Int_DS1302_SetRegister(WP, 0x80);
}
void Int_DS1302_GetDate(Struct_Date *p_st_date)
{u8 temp;// 获取秒temp = Int_DS1302_GetRegister(SECOND);p_st_date->second = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(MINUTE);p_st_date->minute = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(HOUR);p_st_date->hour = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(DAY);p_st_date->day = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(MONTH);p_st_date->month = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(YEAR);p_st_date->year = (temp >> 4) * 10 + (temp & 0x0F);temp = Int_DS1302_GetRegister(DAY_OF_WEEK);p_st_date->day_of_week = (temp >> 4) * 10 + (temp & 0x0F);
}
4)Main.c
#include <STDIO.H>
#include "Int_DS1302.h"
#include "Int_Oled.h"
#include "Int_DS18B20.h"
code char *WEEK_NAME[] = {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"};void main()
{u8 str[17];u8 temperature;Struct_Date st_date;st_date.year = 23;st_date.month = 12;st_date.day = 29;st_date.day_of_week = 5;st_date.hour = 11;st_date.minute = 32;st_date.second = 00;// 初始化时钟并设置时间Int_DS1302_Init();Int_Oled_Init();Int_DS1302_SetDate(&st_date);Int_Oled_Clear();while (1) {Int_DS1302_GetDate(&st_date);temperature = Int_DS18B20_GetTemperature();sprintf(str, "20%02d/%02d/%02d %s",(int)st_date.year,(int)st_date.month,(int)st_date.day,WEEK_NAME[st_date.day_of_week - 1]);Int_Oled_ShowStr(0, 0, str);sprintf(str, "%02d:%02d:%02d Temp:%d",(int)st_date.hour,(int)st_date.minute,(int)st_date.second,(int)temperature);Int_Oled_ShowStr(0, 1, str);Delay1ms(50);}
}