单总线协议-DS18B20原理与stm32F1以及51单片机程序
一、DS18B20技术性能特征
①、 独特的单总线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实现微处理器与DS18B20的双向通讯。大大提高了系统的抗干扰性。
② 、测温范围 -55℃~+125℃,精度为±0.5℃。
③、支持多点组网功能,多个DS18B20可以并联在唯一的三线上,最多只能并联8个,实现多点测温,如果数量过多,会使供电电源电压过低,从而造成信号传输的不稳定。
④、 工作电源: 3.0~5.5V/DC (可以数据线寄生电源)。
⑤ 、在使用中不需要任何外围元件。
⑥、 测量结果以9~12位数字量方式串行传送。
二、DS18B20特点:
(1)DS18B20共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。
(2)单总线是一种半双工通信方式
三、DS18B20的典型温度读取过程:
DS18B20的典型温度读取过程为:复位>>发SKIP ROM命令(0XCC)>>发开始转换命令(0X44)>>延时>>复位>>发送SKIP ROM命令(0XCC)>>发读存储器命令(0XBE)>>连续读出两个字节数据(即温度)>>结束。
1、如果总线上只有一个ds18b20,那么初始化时可以跳过ROM(CCH)
2、如果总线上只有多个ds18b20,那么初始化时需要搜索ROM(FOH)、读ROM(33H)读出64位序列号、发送匹配ROM指令(55H)以及64位序列号
四、原理与程序:
1、51单片机原理与程序:
(1)复位脉冲函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23sbit DQ=P1^7; unsigned char time; //设置全局变量,专门用于严格延时 /***************************************************** 函数功能:将DS18B20传感器初始化,读取应答信号 出口参数:flag 原理:主机将数据线DQ拉成低电平保持480us-960us后释放总线,等待15-60us后,在60us-240us内检测总线是否为 低电平,如果为低电平说明ds18b20器件功能正常 ***************************************************/ bit Init_DS18B20(void) { bit flag; //储存DS18B20是否存在的标志,flag=0,表示存在;flag=1,表示不存在 DQ = 1; //先将数据线拉高 for(time=0;time<2;time++); //略微延时约6微秒 DQ = 0; //再将数据线从高拉低,要求保持480~960us for(time=0;time<200;time++); //略微延时约600微秒 //以向DS18B20发出一持续480~960us的低电平复位脉冲 DQ = 1; //释放数据线(将数据线拉高) for(time=0;time<10;time++); //延时约30us(释放总线后需等待15~60us让DS18B20输出存在脉冲) flag=DQ; //让单片机检测是否输出了存在脉冲(DQ=0表示存在) for(time=0;time<200;time++); //延时足够长时间,等待存在脉冲输出完毕 return (flag); //返回检测成功标志 }
(2)写单字节函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/***************************************************** 函数功能:向DS18B20写入一个字节数据 入口参数:dat 原理: (1)主机读0时序:主机先把总线拉低大于1us,如果主机发送0,主机会把总线继续拉低至少60us直至写周期结束,然后释放总线为高电平 (2)主机读1时序:主机先把总线拉低大于1us,然后释放总线直至写周期结束 (3)作为从机的ds18b20在检测到总线被拉低后(写周期开始),等待15us,在15-45us内对总线采样 (4)数据传输:多字节低字节在前,高字节在后,一字节的低位在前,高位在后 ***************************************************/ void WriteOneChar(unsigned char dat) { unsigned char i=0; for (i=0; i<8; i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ=0; //将数据线从高拉低时即启动写时序 DQ=dat&0x01; //利用与运算取出要写的某位二进制数据, //并将其送到数据线上等待DS18B20采样 for(time=0;time<10;time++) ;//延时约30us,DS18B20在拉低后的约15~60us期间从数据线上采样 DQ=1; //释放数据线 for(time=0;time<1;time++) ;//延时3us,两个写时序间至少需要1us的恢复期 dat>>=1; //将dat中的各二进制位数据右移1位 } for(time=0;time<4;time++) ; //稍作延时,给硬件一点反应时间 }
(3)读单字节函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/***************************************************** 函数功能:从DS18B20读取一个字节数据 出口参数:dat 原理: (1)主机读0时序:主机先把总线拉低大于1us,然后释放总线,如果ds18b20发送0,ds18b20会把总线拉低15us,然后释放总线为高电平 (2)主机读1时序:主机先把总线拉低大于1us,然后释放总线,如果ds18b20发送1,则在主机释放总线后不拉低总线,也就是为高电平 (3)主机需在读周期开始(主机先把总线拉低大于1us,然后释放总线)后的15us内检测总线电平的高低。 ***************************************************/ unsigned char ReadOneChar(void) { unsigned char i=0; unsigned char dat; //储存读出的一个字节数据 for (i=0;i<8;i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ = 0; //单片机从DS18B20读书据时,将数据线从高拉低即启动读时序 _nop_(); //等待一个机器周期 _nop_(); //等待一个机器周期 DQ = 1; //将数据线"人为"拉高,为单片机检测DS18B20的输出电平作准备 for(time=0;time<2;time++); //延时约6us,使主机在15us内采样 dat>>=1; if(DQ==1) dat|=0x80; //如果读到的数据是1,则将1存入dat for(time=0;time<1;time++);//延时3us,两个读时序之间必须有大于1us的恢复期 } return(dat); //返回读出的十进制数据 }
(4)DS18B20温度转换函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14/***************************************************** 函数功能:做好读温度的准备 ***************************************************/ void ReadyReadTemp(void) { Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); // 跳过读序号列号的操作 WriteOneChar(0x44); // 启动温度转换 for(time=0;time<100;time++); //温度转换需要一点时间 Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); //跳过读序号列号的操作 WriteOneChar(0xBE); //读取温度寄存器,前两个分别是温度的低位和高位 }
转化后得到的12位数据,存储在18B20的两个8比特的RAM中,二进制中的前面5位是符号位,如果测得的温度大于0, 这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反加1再乘于0.0625即可得到实际 温度。 例如+125℃的数字输出为07D0H,,-25.0625℃的数字输出为FE6FH。
(5)主机温度转换函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14while(1) //不断检测并显示温度 { ReadyReadTemp(); //读温度准备 TL=ReadOneChar(); //先读的是温度值低位 TH=ReadOneChar(); //接着读的是温度值高位 TN=TH*16+TL/16; //实际温度值=(TH*256+TL)/16,即:TH*16+TL/16 //这样得出的是温度的整数部分,小数部分被丢弃了 TD=(TL%16)*10/16; //计算温度的小数部分,将余数乘以10再除以16取整, //这样得到的是温度小数部分的第一位数字(保留1位小数) display_temp1(TN); //显示温度的整数部分 display_temp2(TD); //显示温度的小数部分 delaynms(10); }
2、51单片机完整程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 unsigned char code digit[10]={"0123456789"}; //定义字符数组显示数字 unsigned char code Str[]={"Test by DS18B20"}; //说明显示的是温度 unsigned char code Error[]={"Error!Check!"}; //说明没有检测到DS18B20 unsigned char code Temp[]={"Temp:"}; //说明显示的是温度 unsigned char code Cent[]={"Cent"}; //温度单位 /******************************************************************************* 以下是对液晶模块的操作程序 *******************************************************************************/ sbit RS=P2^0; //寄存器选择位,将RS位定义为P2.0引脚 sbit RW=P2^1; //读写选择位,将RW位定义为P2.1引脚 sbit E=P2^2; //使能信号位,将E位定义为P2.2引脚 sbit BF=P0^7; //忙碌标志位,,将BF位定义为P0.7引脚 /***************************************************** 函数功能:延时1ms (3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒 ***************************************************/ void delay1ms() { unsigned char i,j; for(i=0;i<10;i++) for(j=0;j<33;j++) ; } /***************************************************** 函数功能:延时若干毫秒 入口参数:n ***************************************************/ void delaynms(unsigned char n) { unsigned char i; for(i=0;i<n;i++) delay1ms(); } /***************************************************** 函数功能:判断液晶模块的忙碌状态 返回值:result。result=1,忙碌;result=0,不忙 ***************************************************/ bit BusyTest(void) { bit result; RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态 RW=1; E=1; //E=1,才允许读写 _nop_(); //空操作 _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 result=BF; //将忙碌标志电平赋给result E=0; //将E恢复低电平 return result; } /***************************************************** 函数功能:将模式设置指令或显示地址写入液晶模块 入口参数:dictate ***************************************************/ void WriteInstruction (unsigned char dictate) { while(BusyTest()==1); //如果忙就等待 RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" _nop_(); _nop_(); //空操作两个机器周期,给硬件反应时间 P0=dictate; //将数据送入P0口,即写入指令或地址 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:指定字符显示的实际地址 入口参数:x ***************************************************/ void WriteAddress(unsigned char x) { WriteInstruction(x|0x80); //显示位置的确定方法规定为"80H+地址码x" } /***************************************************** 函数功能:将数据(字符的标准ASCII码)写入液晶模块 入口参数:y(为字符常量) ***************************************************/ void WriteData(unsigned char y) { while(BusyTest()==1); RS=1; //RS为高电平,RW为低电平时,可以写入数据 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" P0=y; //将数据送入P0口,即将数据写入液晶模块 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:对LCD的显示模式进行初始化设置 1602初始化原理: 延时 写指令38H 延时 写指令38H 延时 写指令38H (每次写指令、读/写数据操作之前均需检测信号) 写指令38H:显示模式设置 写指令08H:显示关闭 写指令01H:显示清屏 写指令06H:显示光标移动设置 写指令0CH:显示开及光标设置 1.1读状态:输入:RS=L,RW=H,E=H ----输出:D0~D7=状态字 1.2写指令:输入:E=L,RS=L,RW=L,D0~D7=指令码,E=H ----输出:无 1.3读数据:输入:RS=H,RW=H,E=H ----输出:D0~D7=数据 1.4写数据:输入:E=L,RS=H,RW=L,D0~D7=数据,E=H ----输出:无 HD44780内置DDRAM、CGROM和CGRAM。 DDRAM就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,地址和屏幕的对应关系如下: 显示位置 1 2 3 4 5 6 7 … … 40 第一行 00H 01H 02H 03H 04H 05H 06H … … 27H 第二行 40H 41H 42H 43H 44H 45H 46H … … 67H 也就是说想要在LCD1602屏幕上的第一行第一个位置显示一个“A”,就要向DDRAM的00H地址写“A”字的代码就OK了,但具体的写入是要按照LCD模块的指令格式来进行的。 但是我们发现每一行有40个地址,而我们们每行只能显示16个字符, 其实际多的位置可以实现字符的移动,我们在看大佬作品的时候可能会见到有的字符是从左面移过来,他的实现形式就用到了着些多的地址。将数据先写到未显示的地址然后使用指令进行左移就可以了。 ***************************************************/ void LcdInitiate(void) { delaynms(15); //延时15ms,首次写指令时应给LCD一段较长的反应时间 WriteInstruction(0x38); //显示模式设置:16×2显示,5×7点阵,8位数据接口 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); //连续三次,确保初始化成功 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x0c); //显示模式设置:显示开,无光标,光标不闪烁 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x06); //显示模式设置:光标右移,字符不移 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x01); //清屏幕指令,将以前的显示内容清除 delaynms(5); //延时5ms ,给硬件一点反应时间 } /************************************************************************ 以下是DS18B20的操作程序 ************************************************************************/ sbit DQ=P1^7; unsigned char time; //设置全局变量,专门用于严格延时 /***************************************************** 函数功能:将DS18B20传感器初始化,读取应答信号 出口参数:flag 原理:主机将数据线DQ拉成低电平保持480us-960us后释放总线,等待15-60us后,在60us-240us内检测总线是否为 低电平,如果为低电平说明ds18b20器件功能正常 ***************************************************/ bit Init_DS18B20(void) { bit flag; //储存DS18B20是否存在的标志,flag=0,表示存在;flag=1,表示不存在 DQ = 1; //先将数据线拉高 for(time=0;time<2;time++); //略微延时约6微秒 DQ = 0; //再将数据线从高拉低,要求保持480~960us for(time=0;time<200;time++); //略微延时约600微秒 //以向DS18B20发出一持续480~960us的低电平复位脉冲 DQ = 1; //释放数据线(将数据线拉高) for(time=0;time<10;time++); //延时约30us(释放总线后需等待15~60us让DS18B20输出存在脉冲) flag=DQ; //让单片机检测是否输出了存在脉冲(DQ=0表示存在) for(time=0;time<200;time++); //延时足够长时间,等待存在脉冲输出完毕 return (flag); //返回检测成功标志 } /***************************************************** 函数功能:从DS18B20读取一个字节数据 出口参数:dat 原理: (1)主机读0时序:主机先把总线拉低大于1us,然后释放总线,如果ds18b20发送0,ds18b20会把总线拉低15us,然后释放总线为高电平 (2)主机读1时序:主机先把总线拉低大于1us,然后释放总线,如果ds18b20发送1,则在主机释放总线后不拉低总线,也就是为高电平 (3)主机需在读周期开始(主机先把总线拉低大于1us,然后释放总线)后的15us内检测总线电平的高低。 ***************************************************/ unsigned char ReadOneChar(void) { unsigned char i=0; unsigned char dat; //储存读出的一个字节数据 for (i=0;i<8;i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ = 0; //单片机从DS18B20读书据时,将数据线从高拉低即启动读时序 _nop_(); //等待一个机器周期 _nop_(); //等待一个机器周期 DQ = 1; //将数据线"人为"拉高,为单片机检测DS18B20的输出电平作准备 for(time=0;time<2;time++); //延时约6us,使主机在15us内采样 dat>>=1; if(DQ==1) dat|=0x80; //如果读到的数据是1,则将1存入dat for(time=0;time<1;time++);//延时3us,两个读时序之间必须有大于1us的恢复期 } return(dat); //返回读出的十进制数据 } /***************************************************** 函数功能:向DS18B20写入一个字节数据 入口参数:dat 原理: (1)主机读0时序:主机先把总线拉低大于1us,如果主机发送0,主机会把总线继续拉低至少60us直至写周期结束,然后释放总线为高电平 (2)主机读1时序:主机先把总线拉低大于1us,然后释放总线直至写周期结束 (3)作为从机的ds18b20在检测到总线被拉低后(写周期开始),等待15us,在15-45us内对总线采样 (4)数据传输:多字节低字节在前,高字节在后,一字节的低位在前,高位在后 ***************************************************/ void WriteOneChar(unsigned char dat) { unsigned char i=0; for (i=0; i<8; i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ=0; //将数据线从高拉低时即启动写时序 DQ=dat&0x01; //利用与运算取出要写的某位二进制数据, //并将其送到数据线上等待DS18B20采样 for(time=0;time<10;time++) ;//延时约30us,DS18B20在拉低后的约15~60us期间从数据线上采样 DQ=1; //释放数据线 for(time=0;time<1;time++) ;//延时3us,两个写时序间至少需要1us的恢复期 dat>>=1; //将dat中的各二进制位数据右移1位 } for(time=0;time<4;time++) ; //稍作延时,给硬件一点反应时间 } /****************************************************************************** 以下是与温度有关的显示设置 ******************************************************************************/ /***************************************************** 函数功能:显示没有检测到DS18B20 ***************************************************/ void display_error(void) { unsigned char i; WriteAddress(0x00); //写显示地址,将在第1行第1列开始显示 i = 0; //从第一个字符开始显示 while(Error[i] != '') //只要没有写到结束标志,就继续写 { WriteData(Error[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(100); //延时100ms较长时间,以看清关于显示的说明 } while(1) //进入死循环,等待查明原因 ; } /***************************************************** 函数功能:显示说明信息 ***************************************************/ void display_explain(void) { unsigned char i; WriteAddress(0x00); //写显示地址,将在第1行第1列开始显示 i = 0; //从第一个字符开始显示 while(Str[i] != '') //只要没有写到结束标志,就继续写 { WriteData(Str[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(100); //延时100ms较长时间,以看清关于显示的说明 } } /***************************************************** 函数功能:显示温度符号 ***************************************************/ void display_symbol(void) { unsigned char i; WriteAddress(0x40); //写显示地址,将在第2行第1列开始显示 i = 0; //从第一个字符开始显示 while(Temp[i] != '') //只要没有写到结束标志,就继续写 { WriteData(Temp[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(50); //延时1ms给硬件一点反应时间 } } /***************************************************** 函数功能:显示温度的小数点 ***************************************************/ void display_dot(void) { WriteAddress(0x49); //写显示地址,将在第2行第10列开始显示 WriteData('.'); //将小数点的字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示温度的单位(Cent) ***************************************************/ void display_cent(void) { unsigned char i; WriteAddress(0x4c); //写显示地址,将在第2行第13列开始显示 i = 0; //从第一个字符开始显示 while(Cent[i] != '') //只要没有写到结束标志,就继续写 { WriteData(Cent[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(50); //延时1ms给硬件一点反应时间 } } /***************************************************** 函数功能:显示温度的整数部分 入口参数:x ***************************************************/ void display_temp1(unsigned char x) { unsigned char j,k,l; //j,k,l分别储存温度的百位、十位和个位 j=x/100; //取百位 k=(x%100)/10; //取十位 l=x%10; //取个位 WriteAddress(0x46); //写显示地址,将在第2行第7列开始显示 WriteData(digit[j]); //将百位数字的字符常量写入LCD WriteData(digit[k]); //将十位数字的字符常量写入LCD WriteData(digit[l]); //将个位数字的字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示温度的小数数部分 入口参数:x ***************************************************/ void display_temp2(unsigned char x) { WriteAddress(0x4a); //写显示地址,将在第2行第11列开始显示 WriteData(digit[x]); //将小数部分的第一位数字字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:做好读温度的准备 ***************************************************/ void ReadyReadTemp(void) { Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); // 跳过读序号列号的操作 WriteOneChar(0x44); // 启动温度转换 for(time=0;time<100;time++); //温度转换需要一点时间 Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); //跳过读序号列号的操作 WriteOneChar(0xBE); //读取温度寄存器,前两个分别是温度的低位和高位 } /***************************************************** 函数功能:主函数 ***************************************************/ void main(void) { unsigned char TL; //储存暂存器的温度低位 unsigned char TH; //储存暂存器的温度高位 unsigned char TN; //储存温度的整数部分 unsigned char TD; //储存温度的小数部分 LcdInitiate(); //将液晶初始化 delaynms(5); //延时5ms给硬件一点反应时间 if(Init_DS18B20()==1) display_error(); display_explain(); display_symbol(); //显示温度说明 display_dot(); //显示温度的小数点 display_cent(); //显示温度的单位 while(1) //不断检测并显示温度 { ReadyReadTemp(); //读温度准备 TL=ReadOneChar(); //先读的是温度值低位 TH=ReadOneChar(); //接着读的是温度值高位 TN=TH*16+TL/16; //实际温度值=(TH*256+TL)/16,即:TH*16+TL/16 //这样得出的是温度的整数部分,小数部分被丢弃了 TD=(TL%16)*10/16; //计算温度的小数部分,将余数乘以10再除以16取整, //这样得到的是温度小数部分的第一位数字(保留1位小数) display_temp1(TN); //显示温度的整数部分 display_temp2(TD); //显示温度的小数部分 delaynms(10); } }
3、proteus仿真图:
4、stm32F1原理与程序:
(1)复位脉冲:
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少480 us,,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~60 us,并进入接收模式(Rx)。接着DS18B20拉低总线60~240 us,以产生低电平应答脉冲。
1
2
3
4
5
6
7
8
9
10
11//复位DS18B20 void DS18B20_Rst(void) { DS18B20_IO_OUT(); //设置为输出模式 DS18B20_DQ_OUT=0; //拉低DQ delay_us(750); //拉低750us(至少480us) DS18B20_DQ_OUT=1; //DQ=1拉高释放总线 delay_us(15); //15US //进入接受模式,等待应答信号。 }
(2)应答信号:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//等待DS18B20的回应 //返回1:未检测到DS18B20的存在 返回0:存在 u8 DS18B20_Check(void) { u8 retry=0; DS18B20_IO_IN();//SET PA0 INPUT while (DS18B20_DQ_IN&&retry<200) { retry++; delay_us(1); }; if(retry>=200)return 1; else retry=0; while (!DS18B20_DQ_IN&&retry<240) { retry++; delay_us(1); }; if(retry>=240)return 1; return 0; }
(3)写单字节函数:
写时序:
写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。
写1时序:主机输出低电平,延时2us,然后释放总线,延时60us。
写0时序:主机输出低电平,延时60us,然后释放总线,延时2us。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//写一个字节到DS18B20 //dat:要写入的字节 void DS18B20_Write_Byte(u8 dat) { u8 j; u8 testb; DS18B20_IO_OUT();//设置PA0为输出 for (j=1;j<=8;j++) { testb=dat&0x01; dat=dat>>1; if (testb) //输出高 { DS18B20_DQ_OUT=0;// 主机输出低电平 delay_us(2); //延时2us DS18B20_DQ_OUT=1;//释放总线 delay_us(60); //延时60us } else //输出低 { DS18B20_DQ_OUT=0;//主机输出低电平 delay_us(60); //延时60us DS18B20_DQ_OUT=1;//释放总线 delay_us(2); //延时2us } } }
(4)读一个位函数:
读时序:
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。
所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态。
典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。
典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//从DS18B20读取一个位 //返回值:1/0 u8 DS18B20_Read_Bit(void) // read one bit { u8 data; DS18B20_IO_OUT();//设置为输出 DS18B20_DQ_OUT=0; //输出低电平2us delay_us(2); DS18B20_DQ_OUT=1; //拉高释放总线 DS18B20_IO_IN();//设置为输入 delay_us(12);//延时12us if(DS18B20_DQ_IN)data=1;//读取总线数据 else data=0; delay_us(50); //延时50us return data; }
(5)读单字节函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//从DS18B20读取一个字节 //返回值:读到的数据 u8 DS18B20_Read_Byte(void) // read one byte { u8 i,j,dat; dat=0; for (i=1;i<=8;i++) { j=DS18B20_Read_Bit(); dat=(j<<7)|(dat>>1); } return dat; }
(6)开始温度转换:
1
2
3
4
5
6
7
8
9
10//开始温度转换 void DS18B20_Start(void)// ds1820 start convert { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0x44);// convert }
转化后得到的12位数据,存储在18B20的两个8比特的RAM中,二进制中的前面5位是符号位,如果测得的温度大于0, 这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反加1再乘于0.0625即可得到实际 温度。 例如+125℃的数字输出为07D0H,,-25.0625℃的数字输出为FE6FH。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//从ds18b20得到温度值 //精度:0.1C //返回值:温度值 (-550~1250) short DS18B20_Get_Temp(void) { u8 temp; u8 TL,TH; short tem; DS18B20_Start (); // ds1820 start convert DS18B20_Rst();复位 DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0xbe);// convert TL=DS18B20_Read_Byte(); TH=DS18B20_Read_Byte(); if(TH>7) { TH=~TH; TL=~TL; temp=0;//温度为负 }else temp=1;//温度为正 tem=TH; //获得高八位 tem<<=8; tem+=TL;//获得底八位 tem=(float)tem*0.625;//转换 if(temp)return tem; //返回温度值 else return -tem; }
4、stm32F1完整程序:
(1)ds18b20.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137#include "ds18b20.h" #include "delay.h" //复位 DS18B20 void DS18B20_Rst(void) { DS18B20_IO_OUT(); //SET PA0 OUTPUT DS18B20_DQ_OUT=0; //拉低 DQ delay_us(750); //拉低 750us DS18B20_DQ_OUT=1; //DQ=1 delay_us(15); //15US } //等待 DS18B20 的回应 //返回 1:未检测到 DS18B20 的存在 //返回 0:存在 u8 DS18B20_Check(void) { u8 retry=0; DS18B20_IO_IN();//SET PA0 INPUT while (DS18B20_DQ_IN&&retry<200) { retry++; delay_us(1); }; if(retry>=200)return 1; else retry=0; while (!DS18B20_DQ_IN&&retry<240) { retry++; delay_us(1); }; if(retry>=240)return 1; return 0; } //从 DS18B20 读取一个位 //返回值:1/0 u8 DS18B20_Read_Bit(void) // read one bit { u8 data; DS18B20_IO_OUT();//SET PA0 OUTPUT DS18B20_DQ_OUT=0; delay_us(2); DS18B20_DQ_OUT=1; DS18B20_IO_IN();//SET PA0 INPUT delay_us(12); if(DS18B20_DQ_IN)data=1; else data=0; delay_us(50); return data; } //从 DS18B20 读取一个字节 //返回值:读到的数据 u8 DS18B20_Read_Byte(void) // read one byte { u8 i,j,dat; dat=0; for (i=1;i<=8;i++) { j=DS18B20_Read_Bit(); dat=(j<<7)|(dat>>1); } return dat; } //写一个字节到 DS18B20 //dat:要写入的字节 void DS18B20_Write_Byte(u8 dat) { u8 j; u8 testb; DS18B20_IO_OUT();//SET PA0 OUTPUT; for (j=1;j<=8;j++) { testb=dat&0x01; dat=dat>>1; if (testb) { DS18B20_DQ_OUT=0;// Write 1 delay_us(2); DS18B20_DQ_OUT=1; delay_us(60); } else { DS18B20_DQ_OUT=0;// Write 0 delay_us(60); DS18B20_DQ_OUT=1; delay_us(2); } } } //开始温度转换 void DS18B20_Start(void)// ds1820 start convert { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0x44);// convert } //初始化 DS18B20 的 IO 口 DQ 同时检测 DS 的存在 //返回 1:不存在 //返回 0:存在 u8 DS18B20_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE); //使能 PG 口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PORTG.11 推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化 GPIO GPIO_SetBits(GPIOG,GPIO_Pin_11); //输出 1 DS18B20_Rst(); return DS18B20_Check(); } //从 ds18b20 得到温度值 //精度:0.1C //返回值:温度值 (-550~1250) short DS18B20_Get_Temp(void) { u8 temp; u8 TL,TH; short tem; DS18B20_Start (); // ds1820 start convert DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0xbe);// convert TL=DS18B20_Read_Byte(); // LSB TH=DS18B20_Read_Byte(); // MSB if(TH>7) { TH=~TH; TL=~TL; temp=0; //温度为负 }else temp=1; //温度为正 tem=TH; //获得高八位 tem<<=8; tem+=TL; //获得底八位 tem=(float)tem*0.625; //转换 if(temp)return tem; //返回温度值 else return -tem; }
(2)main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45int main(void) { u8 t=0; short temperature; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组 2 uart_init(115200); //串口初始化为 115200 LED_Init(); //初始化与 LED 连接的硬件接口 LCD_Init(); //初始化 LCD POINT_COLOR=RED; //设置字体为红色 LCD_ShowString(30,50,200,16,16,"ELITE STM32"); LCD_ShowString(30,70,200,16,16,"DS18B20 TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2015/1/16"); while(DS18B20_Init()) //DS18B20 初始化 { LCD_ShowString(30,130,200,16,16,"DS18B20 Error"); delay_ms(200); LCD_Fill(30,130,239,130+16,WHITE); delay_ms(200); } LCD_ShowString(30,130,200,16,16,"DS18B20 OK"); POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,150,200,16,16,"Temp: . C"); while(1) { if(t%10==0) //每 100ms 读取一次 { temperature=DS18B20_Get_Temp(); if(temperature<0) { LCD_ShowChar(30+40,150,'-',16,0); //显示负号 temperature=-temperature; //转为正数 }else LCD_ShowChar(30+40,150,' ',16,0); //去掉负号 LCD_ShowNum(30+40+8,150,temperature/10,2,16); //显示正数部分 LCD_ShowNum(30+40+32,150,temperature%10,1,16); //显示小数部分 } delay_ms(10); t++; if(t==20) { t=0; LED0=!LED0; } } }
最后
以上就是苗条刺猬最近收集整理的关于嵌入式单片机基础篇(四十二)之单总线协议-DS18B20原理与stm32F1以及51单片机程序单总线协议-DS18B20原理与stm32F1以及51单片机程序的全部内容,更多相关嵌入式单片机基础篇(四十二)之单总线协议-DS18B20原理与stm32F1以及51单片机程序单总线协议-DS18B20原理与stm32F1以及51单片机程序内容请搜索靠谱客的其他文章。
发表评论 取消回复