单片机练习 - DS18B20温度转换与显示
最近都在学习和写单片机的程序, 今天有空又模仿DS18B20温度测量显示实验写了一个与DS18B20基于单总线通信的程序.
DS18B20 数字温度传感器(参考:智能温度传感器DS18B20的原理与应用)是DALLAS 公司生产的1-Wire,即单总线器件,具有线路简单,体积小的特点。因此用它来组成一个测温系统,具有线路简单,在一根通信线,可以挂很多这样的数字温度计。DS18B20 产品的特点:
(1)、只要求一个I/O 口即可实现通信。
(2)、在DS18B20 中的每个器件上都有独一无二的序列号。
(3)、实际应用中不需要外部任何元器件即可实现测温。
(4)、测量温度范围在-55 到+125℃之间; 在-10 ~ +85℃范围内误差为±5℃;
(5)、数字温度计的分辨率用户可以从9 位到12 位选择。将12位的温度值转换为数字量所需时间不超过750ms;
(6)、内部有温度上、下限告警设置。
DS18B20引脚分布图
DS18B20 详细引脚功能描述:
1、GND 地信号;
2、DQ数据输入出引脚。开漏单总线接口引脚。当被用在寄生电源下,此引脚可以向器件提供电源;漏极开路, 常太下高电平. 通常要求外接一个约5kΩ的上拉电阻.
3、VDD可选择的VDD 引脚。电压范围:3~5.5V; 当工作于寄生电源时,此引脚必须接地。
DS18B20存储器结构图
暂存储器的头两个字节为测得温度信息的低位和高位字节;
第3, 4字节是TH和TL的易失性拷贝, 在每次电复位时都会被刷新;
第5字节是配置寄存器的易失性拷贝, 同样在电复位时被刷新;
第9字节是前面8个字节的CRC检验值.
配置寄存器的命令内容如下:
| 0 | R1 | R0 | 1 | 1 | 1 | 1 | 1 |
R0和R1是温度值分辨率位, 按下表进行配置.默认出厂设置是R1R0 = 11, 即12位.
温度值分辨率配置表
| R1 | R0 | 分辨率 | 最大转换时间(ms) |
| 0 | 0 | 9bit | 93.75(tconv/8) |
| 0 | 1 | 10bit | 183.50(tconv/4) |
| 1 | 0 | 11bit | 375(tconv/2) |
| 1 | 1 | 12bit | 750 (tconv) |
12位分辨率时的两个温度字节的具体格式如下:
低字节:
| 2^3 | 2^2 | 2^1 | 2^0 | 2^-1 | 2^-2 | 2^-3 | 2^-4 |
高字节:
| S | S | S | S | S | 2^6 | 2^5 | 2^4 |
其中高字节前5位都是符号位S, 若分辨率低于12位时, 相应地使最低为0, 如: 当分辨率为10位时, 低字节为:
| 2^3 | 2^2 | 2^1 | 2^0 | 2^-1 | 2^-2 | 0 | 0 |
, 高字节不变....
一些温度与转换后输出的数字参照如下:
| 温度 | 数字输出 | 换成16进制 |
| +125℃ | 00000111 11010000 | 07D0H |
| +85℃ | 00000101 01010000 | 0550H |
| +25.0625℃ | 00000001 10010001 | 0191H |
| +10.125℃ | 00000000 10100010 | 00A2H |
| +0.5℃ | 00000000 00001000 | 0008H |
| 0℃ | 00000000 00000000 | 0000H |
| -0.5℃ | 11111111 11111000 | FFF8H |
| -10.125℃ | 11111111 01011110 | FFE5H |
| -25.0625℃ | 11111110 01101111 | FF6FH |
| -55℃ | 11111100 10010000 | FC90H |
DS18B20 的使用方法:
由于DS18B20 采用的是1-Wire 总线协议方式,即在一根数据线实现数据的双向传输,而对单片机来说,我们必须采用软件的方法来模拟单总线的协议时序来完成对DS18B20芯片的访问。
由于DS18B20是在一根I/O线上读写数据,因此,对读写的数据位有着严格的时序要求。
DS18B20有严格的通信协议来保证各位数据传输的正确性和完整性。
该协议定义了几种信号的时序:初始化时序(dsInit()实现)、读时序(readByte())、写时序(writeByte())。
所有时序都是将主机作为主设备,单总线器件作为从设备。而每一次命令和数据的传输都是从主机主动启动写时序开始,如果要求单总线器件回送数据,在进行写命令后,主机需启动读时序完成数据接收。数据和命令的传输都是低位在先。
DS18B20与单片机连接电路图:

利用软件模拟DS18B20的单线协议和命令:主机操作DS18B20必须遵循下面的顺序
1. 初始化
单线总线上的所有操作都是从初始化开始的. 过程如下:
1)请求: 主机通过拉低单线480us以上, 产生复位脉冲, 然后释放该线, 进入Rx接收模式. 主机释放总线时, 会产生一个上升沿脉冲.
DQ : 1 -> 0(480us+) -> 1
2)响应: DS18B20检测到该上升沿后, 延时15~60us, 通过拉低总线60~240us来产生应答脉冲.
DQ: 1(15~60us) -> 0(60~240us)
3)接收响应: 主机接收到从机的应答脉冲后, 说明有单线器件在线. 至此, 初始化完成.
DQ: 0
2. ROM操作命令
当主机检测到应答脉冲, 便可发起ROM操作命令. 共有5类ROM操作命令, 如下表
| 命令类型 | 命令字节 | 功能 |
| Read Rom 读ROM | 33H | 读取激光ROM中的64位,只能用于总线上单个DS18B20器件情况, 多挂时会发生数据冲突 |
| Match Rom匹配ROM | 55H | 此命令后跟64位ROM序列号,寻址多挂总线上的对应DS18B20.只有序列号完全匹配的DS18B20才能响应后面的内存操作命令,其他不匹配的将等待复位脉冲.可用于单挂或多挂两种情况. |
| Skip Rom 跳过ROM | CCH | 可无须提供64位ROM序列号即可运行内存操作命令, 只能用于单挂. |
| Search Rom搜索ROM | F0H | 通过一个排除法过程, 识别出总线上所有器件的ROM序列号 |
| Alarm Search告警搜索 | ECH | 命令流程与Search Rom相同, 但DS18B20只有最近的一次温度测量时满足了告警触发条件的, 才会响应此命令. |
3. 内存操作命令
在成功执行ROM操作命令后, 才可使用内存操作命令. 共有6种内存操作命令:
| 命令类型 | 命令字节 | 功能 |
|
Write Scratchpad |
4EH | 写暂存器中地址2~地址4的3个字节(TH,TL和配置寄存器)在发起复位脉冲之前,3个字节都必须要写. |
|
Read Scratchpad |
BEH | 读取暂存器内容,从字节0~一直到字节8, 共9个字节,主机可随时发起复位脉冲,停止此操作,通常我们只需读前5个字节. |
|
Copy Scratchpad |
48H | 将暂存器中的内容复制进EERAM, 以便将温度告警触发字节存入非易失内存. 如果此命令后主机产生读时隙, 那么只要器件还在进行复制都会输出0, 复制完成后输出1. |
|
Convert T |
44H | 开始温度转换操作. 若在此命令后主机产生时隙, 那么只要器件还在进行温度转换就会输出0, 转换完成后输出1. |
|
Recall E2 |
B8H | 将存储在EERAM中的温度告警触发值和配置寄存器值重新拷贝到暂存器中,此操作在DS18B20加电时自动产生. |
|
Read Power Supply |
B4H | 主机发起此命令后每个读数时隙内,DS18B20会发信号通知它的供电方式:0寄生电源, 1外部供电. |
4. 数据处理
DS18B20要求有严格的时序来保证数据的完整性. 在单线DQ上, 有复位脉冲, 应答脉冲, 写0, 写1, 读0, 读1这6种信号类型. 除了应答脉冲外, 其它都由主机产生. 数据位的读和写是通过读、写时隙实现的.
1) 写时隙: 当主机将数据线从高电平拉至低电平时, 产生写时隙.所有写时隙都必须在60us以上, 各写时隙间必须保证1us的恢复时间.
写"1" : 主机将数据线DQ先拉低, 然后释放15us后, 将数据线DQ拉高;
写"0" : 主机将DQ拉低并至少保持60us以上.
2)读时隙: 当主机将数据线DQ从高电平拉至低电平时, 产生读时隙. 所有读时隙最短必须持续60us, 各读时隙间必须保证1us的恢复时间.
读: 主机将DQ拉低至少1us,. 此时主机马上将DQ拉高, 然后就可以延时15us后, 读取DQ即可.
源代码: (测量范围: 0 ~ 99度)
1
#include <reg51.H>2
//通过DS18B20测试当前环境温度, 并通过数码管显示当前温度值3
sbit wela = P2^7; //数码管位选4
sbit dula = P2^6; //数码管段选5
sbit ds = P2^2;6
//0-F数码管的编码(共阴极)7
unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,8
0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};9
//0-9数码管的编码(共阴极), 带小数点10
unsigned char code tableWidthDot[]={0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 11
0x87, 0xff, 0xef};12

13
//延时函数, 例i=10,则大概延时10ms.14
void delay(unsigned char i)15
{16
unsigned char j, k;17
for(j = i; j > 0; j--)18
{19
for(k = 125; k > 0; k--);20
}21
}22

23
//初始化DS18B2024
//让DS18B20一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动25
void dsInit()26
{27
//一定要使用unsigned int型, 一个i++指令的时间, 作为与DS18B20通信的小时间间隔28
//以下都是一样使用unsigned int型29
unsigned int i; 30
ds = 0;31
i = 103;32
while(i>0) i--;33
ds = 1;34
i = 4;35
while(i>0) i--;36
}37

38
//向DS18B20读取一位数据39
//读一位, 让DS18B20一小周期低电平, 然后两小周期高电平, 40
//之后DS18B20则会输出持续一段时间的一位数据41
bit readBit()42
{43
unsigned int i;44
bit b;45
ds = 0;46
i++;47
ds = 1; 48
i++; i++;49
b = ds;50
i = 8; 51
while(i>0) i--;52
return b;53
}54

55
//读取一字节数据, 通过调用readBit()来实现56
unsigned char readByte()57
{58
unsigned int i;59
unsigned char j, dat;60
dat = 0;61
for(i=0; i<8; i++)62
{63
j = readBit();64
//最先读出的是最低位数据65
dat = (j << 7) | (dat >> 1);66
}67
return dat;68
}69

70
//向DS18B20写入一字节数据71
void writeByte(unsigned char dat)72
{73
unsigned int i;74
unsigned char j;75
bit b;76
for(j = 0; j < 8; j++)77
{78
b = dat & 0x01;79
dat >>= 1;80
//写"1", 让低电平持续2个小延时, 高电平持续8个小延时81
if(b) 82
{83
ds = 0;84
i++; i++;85
ds = 1;86
i = 8; while(i>0) i--;87
}88
else //写"0", 让低电平持续8个小延时, 高电平持续2个小延时89
{90
ds = 0;91
i = 8; while(i>0) i--;92
ds = 1;93
i++; i++;94
}95
}96
}97

98
//向DS18B20发送温度转换命令99
void sendChangeCmd()100
{101
dsInit(); //初始化DS18B20102
delay(1); //延时1ms103
writeByte(0xcc); //写入跳过序列号命令字104
writeByte(0x44); //写入温度转换命令字105
}106

107
//向DS18B20发送读取数据命令108
void sendReadCmd()109
{110
dsInit();111
delay(1);112
writeByte(0xcc); //写入跳过序列号命令字113
writeByte(0xbe); //写入读取数据令字114
}115

116
//获取当前温度值117
unsigned int getTmpValue()118
{119
unsigned int value; //存放温度数值120
float t;121
unsigned char low, high;122
sendReadCmd();123
//连续读取两个字节数据124
low = readByte(); 125
high = readByte();126
//将高低两个字节合成一个整形变量127
value = high;128
value <<= 8;129
value |= low;130
//DS18B20的精确度为0.0625度, 即读回数据的最低位代表0.0625度131
t = value * 0.0625;132
//将它放大10倍, 使显示时可显示小数点后一位, 并对小数点后第二2进行4舍5入133
//如t=11.0625, 进行计数后, 得到value = 111, 即11.1 度134
value = t * 10 + 0.5;135
return value;136
}137

138
//显示当前温度值, 精确到小数点后一位139
void display(unsigned int v) 140
{141
unsigned char count;142
unsigned char datas[] = {0, 0, 0};143
datas[0] = v / 100;144
datas[1] = v % 100 / 10;145
datas[2] = v % 10;146
for(count = 0; count < 3; count++)147
{148
//片选149
wela = 0; 150
P0 = ((0xfe << count) | (0xfe >> (8 - count))); //选择第(count + 1) 个数码管151
wela = 1; //打开锁存, 给它一个下降沿量152
wela = 0;153
//段选154
dula = 0;155
if(count != 1)156
{157
P0 = table[datas[count]]; //显示数字158
}159
else160
{161
P0 = tableWidthDot[datas[count]]; //显示带小数点数字162
}163
dula = 1; //打开锁存, 给它一个下降沿量164
dula = 0;165
delay(5); //延时5ms, 即亮5ms166

167
//清除段先, 让数码管灭, 去除对下一位的影响, 去掉高位对低位重影168
//若想知道影响效果如何, 可自行去掉此段代码169
//因为数码管是共阴极的, 所有灭的代码为: 00H170
dula = 0;171
P0 = 0x00; //显示数字172
dula = 1; //打开锁存, 给它一个下降沿量173
dula = 0;174
}175
}176

177
void main()178
{179
unsigned char i;180
unsigned int value;181
while(1)182
{183
//启动温度转换184
sendChangeCmd();185
value = getTmpValue();186
//显示3次187
for(i = 0; i < 3; i++)188
{189
display(value);190
}191
}192
}显示效果:
流程图:
改进代码: 扩大测量范围, 使可测量范围为: -55度 ~ +125度, 严格按照上面的流程进行软件设计
3.15 1:34 修正display()函数中的下一位显示对上一位的影响
1
#include <reg51.H>2
#include<intrins.h>3
#include <math.H> //要用到取绝对值函数abs()4
//通过DS18B20测试当前环境温度, 并通过数码管显示当前温度值, 目前显示范围: -55~ +125度5
sbit wela = P2^7; //数码管位选6
sbit dula = P2^6; //数码管段选7
sbit ds = P2^2;8
int tempValue;9

10
//0-F数码管的编码(共阴极)11
unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,12
0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};13
//0-9数码管的编码(共阴极), 带小数点14
unsigned char code tableWidthDot[]={0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 15
0x87, 0xff, 0xef};16

17
//延时函数, 对于11.0592MHz时钟, 例i=10,则大概延时10ms.18
void delay(unsigned int i)19
{20
unsigned int j;21
while(i--)22
{23
for(j = 0; j < 125; j++);24
}25
}26

27
//初始化DS18B2028
//让DS18B20一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动29
void dsInit()30
{31
//对于11.0592MHz时钟, unsigned int型的i, 作一个i++操作的时间大于为8us32
unsigned int i; 33
ds = 0;34
i = 100; //拉低约800us, 符合协议要求的480us以上35
while(i>0) i--;36
ds = 1; //产生一个上升沿, 进入等待应答状态37
i = 4;38
while(i>0) i--;39
}40

41
void dsWait()42
{43
unsigned int i;44
while(ds); 45
while(~ds); //检测到应答脉冲46
i = 4;47
while(i > 0) i--;48
}49

50
//向DS18B20读取一位数据51
//读一位, 让DS18B20一小周期低电平, 然后两小周期高电平, 52
//之后DS18B20则会输出持续一段时间的一位数据53
bit readBit()54
{55
unsigned int i;56
bit b;57
ds = 0;58
i++; //延时约8us, 符合协议要求至少保持1us59
ds = 1; 60
i++; i++; //延时约16us, 符合协议要求的至少延时15us以上61
b = ds;62
i = 8; 63
while(i>0) i--; //延时约64us, 符合读时隙不低于60us要求64
return b;65
}66

67
//读取一字节数据, 通过调用readBit()来实现68
unsigned char readByte()69
{70
unsigned int i;71
unsigned char j, dat;72
dat = 0;73
for(i=0; i<8; i++)74
{75
j = readBit();76
//最先读出的是最低位数据77
dat = (j << 7) | (dat >> 1);78
}79
return dat;80
}81

82
//向DS18B20写入一字节数据83
void writeByte(unsigned char dat)84
{85
unsigned int i;86
unsigned char j;87
bit b;88
for(j = 0; j < 8; j++)89
{90
b = dat & 0x01;91
dat >>= 1;92
//写"1", 将DQ拉低15us后, 在15us~60us内将DQ拉高, 即完成写193
if(b) 94
{95
ds = 0;96
i++; i++; //拉低约16us, 符号要求15~60us内97
ds = 1; 98
i = 8; while(i>0) i--; //延时约64us, 符合写时隙不低于60us要求99
}100
else //写"0", 将DQ拉低60us~120us101
{102
ds = 0;103
i = 8; while(i>0) i--; //拉低约64us, 符号要求104
ds = 1;105
i++; i++; //整个写0时隙过程已经超过60us, 这里就不用像写1那样, 再延时64us了106
}107
}108
}109

110
//向DS18B20发送温度转换命令111
void sendChangeCmd()112
{113
dsInit(); //初始化DS18B20, 无论什么命令, 首先都要发起初始化114
dsWait(); //等待DS18B20应答115
delay(1); //延时1ms, 因为DS18B20会拉低DQ 60~240us作为应答信号116
writeByte(0xcc); //写入跳过序列号命令字 Skip Rom117
writeByte(0x44); //写入温度转换命令字 Convert T118
}119

120
//向DS18B20发送读取数据命令121
void sendReadCmd()122
{123
dsInit();124
dsWait();125
delay(1);126
writeByte(0xcc); //写入跳过序列号命令字 Skip Rom127
writeByte(0xbe); //写入读取数据令字 Read Scratchpad128
}129

130
//获取当前温度值131
int getTmpValue()132
{133
unsigned int tmpvalue;134
int value; //存放温度数值135
float t;136
unsigned char low, high;137
sendReadCmd();138
//连续读取两个字节数据139
low = readByte(); 140
high = readByte();141
//将高低两个字节合成一个整形变量142
//计算机中对于负数是利用补码来表示的143
//若是负值, 读取出来的数值是用补码表示的, 可直接赋值给int型的value144
tmpvalue = high;145
tmpvalue <<= 8;146
tmpvalue |= low;147
value = tmpvalue;148
149
//使用DS18B20的默认分辨率12位, 精确度为0.0625度, 即读回数据的最低位代表0.0625度150
t = value * 0.0625;151
//将它放大100倍, 使显示时可显示小数点后两位, 并对小数点后第三进行4舍5入152
//如t=11.0625, 进行计数后, 得到value = 1106, 即11.06 度153
//如t=-11.0625, 进行计数后, 得到value = -1106, 即-11.06 度154
value = t * 100 + (value > 0 ? 0.5 : -0.5); //大于0加0.5, 小于0减0.5155
return value;156
}157

158
unsigned char const timeCount = 3; //动态扫描的时间间隔159
//显示当前温度值, 精确到小数点后一位160
//若先位选再段选, 由于IO口默认输出高电平, 所以当先位选会使数码管出现乱码161
void display(int v) 162
{163
unsigned char count;164
unsigned char datas[] = {0, 0, 0, 0, 0};165
unsigned int tmp = abs(v);166
datas[0] = tmp / 10000;167
datas[1] = tmp % 10000 / 1000;168
datas[2] = tmp % 1000 / 100;169
datas[3] = tmp % 100 / 10;170
datas[4] = tmp % 10;171
if(v < 0)172
{173
//关位选, 去除对上一位的影响174
P0 = 0xff; 175
wela = 1; //打开锁存, 给它一个下降沿量176
wela = 0;177
//段选178
P0 = 0x40; //显示"-"号179
dula = 1; //打开锁存, 给它一个下降沿量180
dula = 0;181

182
//位选183
P0 = 0xfe; 184
wela = 1; //打开锁存, 给它一个下降沿量185
wela = 0;186
delay(timeCount); 187
}188
for(count = 0; count != 5; count++)189
{190
//关位选, 去除对上一位的影响191
P0 = 0xff; 192
wela = 1; //打开锁存, 给它一个下降沿量193
wela = 0;194
//段选195
if(count != 2)196
{197
/* if((count == 0 && datas[count] == 0) 198
|| ((count == 1 && datas[count] == 0) && (count == 0 && datas[count - 1] == 0)))199
{200
P0 = 0x00; //当最高位为0时, 不作显示201
}202
else*/203
P0 = table[datas[count]]; //显示数字204
}205
else206
{207
P0 = tableWidthDot[datas[count]]; //显示带小数点数字208
}209
dula = 1; //打开锁存, 给它一个下降沿量210
dula = 0;211

212
//位选 213
P0 = _crol_(0xfd, count); //选择第(count + 1) 个数码管214
wela = 1; //打开锁存, 给它一个下降沿量215
wela = 0;216
delay(timeCount); 217
}218
}219

220
void main()221
{222
unsigned char i;223
224
while(1)225
{226
//启动温度转换227
sendChangeCmd();228
//显示5次229
for(i = 0; i < 40; i++)230
{231
display(tempValue);232
}233
tempValue = getTmpValue();234
}235
}改进后的效果图:
只有一位小数
两位小数, 并消除下一位对上一位的影响

(PS: 写这篇文章期间, 07年迟来的冬天过去了, 温度上升了5℃....温暖^_^)
其它参考资料:
1. 《51单片机C语言应用程序设计实例精讲》, 戴佳,戴卫恒编著,电子工业出版社。




浙公网安备 33010602011771号