目录
一、引言
DS18B20的原理图
单总线简介:
编辑暂存器简介:
DS18B20的温度转换与读取流程
二、代码配置
maic文件
疑问
关于不同格式化输出符号的使用
为什么要rd_temperature()/16.0?
onewire.h文件
这个配置为什么要先读low,如果反过来读会怎么样?
一、引言
DS18B20是单线接口数字温度传感器,测量范围是-55°C~+125°C,-10°C~+85°C的范围精度是±0.5°C,还是精度很高的呢。
DS18B20的原理图
外部结构长这样
符号 | 说明 |
GND | 接地 |
DQ | 数据输入/输出引脚。当工作在寄生电源模式时用来提供电源。 |
VDD | 可选的VDD引脚。工作与寄生电源模式是VDD必须接地。 |
这是内部结构
DS18B20包括很多东西,有寄生电源电路,64位ROM和单线接口电路、暂存器、EEPROM、8位CRC生成器和温度传感器。寄生电源电路可以实现外部电源供电和单线寄生供电,64位ROM中存放的48位序列号用于识别同一单线上连接的多个DS18B20,以实现多点测温。
单总线简介:
单总线是一种通用数据总线他只有一根通信线:DQ,单总线只需要一根通信线即可实现数据的双向传输。
单总线的具体时序:
初始化:
主机将总线拉低至少480us,然后释放总线,当DS18B20探测到I/O引脚上的上升沿侯,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线
发送一位数据:
主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60u
接收一位:
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us
具体的单总线完整的操作时序如下:
温度变换:初始化→跳过ROM →开始温度变换:
温度读取:初始化→跳过ROM →读暂存器→连续的读操作
其中ROM命令如下:
暂存器简介:
其中的暂存器很重要,有九个字节,最上面的两个字节是温度低位和高位。如下图
在LSB,MSB中BIT15~BIT11是符号位,控制符号;BIT10~BIT4是整数位;BIT3~BIT0是小数位。
这是一些例子说明,前五位是符号位,5个都为0是正数,5个都为1是负数,其他的就按二进制,十六进制常规操作算。
DS18B20的温度转换与读取流程
[1] 初始化总线
[2] 写入字节0xcc,跳过rom。
[3] 写入字节0x44,进行温度转换。
[4] 初始化总线
[5] 写入字节0xcc,跳过rom。
[6] 写入字节0xbe,读取高速暂存器。(将后面的high和low的值存放到这里面)
[7] 读取暂存器的第0字节,即温度数据的LSB(low)。
[8] 读取暂存器的第1字节,即温度数据的MSB(high)。
[9] 返回high+low的值
————————————————
二、代码配置
maic文件
#include "bsp_seg.h"
#include "Timer0.h"
#include "bsp_key.h"
#include "STDIO.H"
#include <STC15F2K60S2.H>
#include "bsp_init.h"
#include "bsp_led.h"
#include "bsp_1302.h"
#include "bsp_onewire.h"
/* 函数声明 */
//三个主体循环,基本上不变
void Key_Proc(void);//按键处理
void Seg_Proc(void);//显示处理
void Led_Proc(void);//LED处理
/* 全局变量声明 */
//显示专用,基本上永远不变
unsigned char seg_buf[8];//放置字符串转换后的段码到数组
unsigned char seg_string[10];//放置字符串
unsigned char pos = 0;//中断显示专用
//LED显示专用,基本上永远不变
unsigned char ucLed;
//按键专用,基本上永远不变
unsigned char Key_Value;//读取按键的数值存储变量
unsigned char Key_Down,Key_Old;
//按键和显示函数减速专用,基本上永远不变
unsigned int Key_Slow_Down;//按键减速
unsigned int Seg_Slow_Down;
//DS1302专用,当使用DS1302时,基本不变
unsigned char ucRtc[3] = {23,59,55};//设置RTC时间
unsigned int ms_count;
unsigned char s_count;
unsigned char Running_State;//记录运行状态
void main()
{
Cls_Peripheral();
Timer0Init(); //1毫秒@12.000MHz
EA = 1;
Set_Rtc(ucRtc);//设置RTC时间,23-59-55
while(1)
{
Key_Proc();//按键处理
Seg_Proc();//显示处理
Led_Proc();
}
}
/* Timer0 interrupt routine */
void tm0_isr() interrupt 1
{
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
if(++Seg_Slow_Down == 10) Seg_Slow_Down = 0;
if(++ms_count == 1000) //记录·运行时间
{
s_count++;
ms_count = 0;
}
Seg_Disp(seg_buf, pos);
if(++pos ==8) pos = 0;
Led_Disp(ucLed);//LED显示
}
/* Key_Proc */
void Key_Proc(void)//按键处理,底层数据变更
{
if(Key_Slow_Down) return;
Key_Slow_Down = 1;
Key_Value = Key_Read();//读取按键按下的编号
Key_Down = Key_Value & (Key_Old ^ Key_Value);//^异或(0000^0101)= 0101 0101 & 0101 = 0101//如果按键发生了下降沿的变化,输出结果和本次按键数值相同
//^异或(0101^0101)= 0000 0101 & 0000 = 0000//如果按键发生了下降沿的变化,输出结果和本次按键数值相同
Key_Old = Key_Value;
if(Key_Down)//如果捕捉到下降沿跳变
{
if(++Running_State == 3)
Running_State = 0;//保证Running_State在0-2之间翻滚
}
}
/* Seg_Proc */
//Seg_Proc,准备数码管要显示的内容,Seg_Tran把字符串转成数码管段码,存进seg_buf[]
void Seg_Proc(void)//显示处理,显示信息生成
{
if(Seg_Slow_Down) return;
Seg_Slow_Down = 1;
switch(Running_State)
{
case 0:
//读取18B20的数值
//seg_string是一个字符数组,理解为“数码管要显示的文字内容”
sprintf(seg_string, "----%04.2f",rd_temperature()/16.0);//这个代码的效果是当ucRtc是[23,59,55],seg_string =“23-59-55”
break;
%d
%2d
%02d
%2.2f
%02.2f
case 1:
Read_RTC(ucRtc);//读取1302内部
//seg_string是一个字符数组,理解为“数码管要显示的文字内容”
sprintf(seg_string, "%02d-%02d-%02d",(unsigned int)ucRtc[0],(unsigned int)ucRtc[1],(unsigned int)ucRtc[2]);//这个代码的效果是当ucRtc是[23,59,55],seg_string =“23-59-55”
break;
case 2:
sprintf(seg_string, "-----%03d",(unsigned int)s_count);
break;
}
//seg_buf是一个存储段码的数组。因为数码管不能直接显示“字符”,它要的是“段码”-告诉它点亮哪几段
Seg_Tran(seg_string, seg_buf);//Seg_Tran作用——把字符串seg_string转换成段码,放入数组seg_buf中
}
void Led_Proc(void)
{
switch(Running_State)
{
case 0:
ucLed = 0x03;//让L1,L2两个亮 0000 0011
break;
case 1:
ucLed =0x0c;//让L3,L4两个亮 0000 1100
break;
case 2:
ucLed =0x30;//让L5,L6两个亮 0011 0000
break;
}
}
//温度,时钟,系统运行时
疑问
sprintf(seg_string, "----%04.2f",rd_temperature()/16.0);
break; 在这个代码里有"----%04.2f",为什么这里要这样写呢,引起我的思考
关于不同格式化输出符号的使用
%d:输出一个十进制整数,无特别格式限制。
%2d:输出一个整数,占用至少2个字符宽度,右对齐。若数字不足2位,用空格补在左侧
%02d:输出一个整数,占用2 位宽度,不足的用 0 补左边。
%2.2f:整体宽度至少 2 位。小数点后保留2 位小数。整数部分和小数点也算在宽度里,但如果不够宽度会自动扩展。示例:printf("%2.2f", 3.1); → 输出:3.10。实际宽度超过 2 位(共 4 位),所以宽度不限制实际输出。
%02.2f::整体至少 2 位宽(但不包含小数位数限制时会自动扩展)。小数点后保留 2 位。前面不足时补 0(但一般无效) 。
示例:printf("%02.2f", 3.1); → 输出:3.10
实际上宽度会扩展以容纳整个数字,0补位不会生效,因为 3.10 就已经超出了 2 的宽度
为什么要rd_temperature()/16.0?
假设温度传感器(比如DS18B20)的测量范围是 -55°C 到 +125°C,但它内部存储温度数据时,把温度值放大了16倍,相当于把温度的小数部分用整数来记录。
onewire.h文件
#include "bsp_onewire.h"
#include <STC15F2K60S2.H>
void Delay_OneWire(unsigned int t) //STC89C52RC
{
t *=12;
while(t--);
}
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(5);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(5);
}
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;
for(i=0;i<8;i++)
{
DQ = 0;
dat >>= 1;
DQ = 1;
if(DQ)
{
dat |= 0x80;
}
Delay_OneWire(5);
}
return dat;
}
bit init_ds18b20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80);
DQ = 1;
Delay_OneWire(10);
initflag = DQ;
Delay_OneWire(5);
return initflag;
}
//unsigned char check_1[7] = {0};
unsigned int rd_temperature(void)
{
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xcc);//Ìø¹ýROM
Write_DS18B20(0x44);//ת»»Î¶È
init_ds18b20();
Write_DS18B20(0xcc);//Ìø¹ýROM
Write_DS18B20(0xbe);//¶ÁȡζÈ
low = Read_DS18B20();
high = Read_DS18B20();//¶ÁÈ¡¸ßλ
return (high<<8)|low;
}
unsigned int rd_temperature(void)
{
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0x44);//转换温度init_ds18b20();
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0xbe);//读取温度low = Read_DS18B20();
high = Read_DS18B20();//读取高位
return (high<<8)|low;
}
low = Read_DS18B20();
high = Read_DS18B20();//读取高位
这个配置为什么要先读low,如果反过来读会怎么样?
因为ds18b20中,是先读低位再读高位,并且这里将high左移8位是为了正确对齐两个字节的二进制位,确保高字节占据16位整数的高8位,低字节占据低8位。这都是DS18B20数据格式的强制要求。如果反过来读,那得到的数据就是错误的。
这是我配置的效果,显示了环境的温度,如果想让温度变高,可以拿手指捏住温度传感器[黑色帽子一样的,在右上角]大家可以试试。