回顾

面对即将到来的高三,不得不停止了在学校机器人的学习生涯,在这学期我和我的组员们共同完成了这个小钟从电路设计,焊接,再到编程调试的过程。我们将之前学习到的单片机和电路知识运用起来,虽然经历了不计其数的Bug和坎坷,可是就是在这样痛苦的过程中我们不知不觉中成长了许多,收获了更多的实战经验。

提出

一直以来,我多数在开发软件,偶尔碰碰Arduino实现点小功能,幸运的是我的高中有机器人实验室,于是我们就在龚鹏老师的带领下,系统的学习STC单片机开发和基础硬件知识。上个学期,课程已经基本学完,在7*5点阵的基础下,我们几个都想独立自主的完成一个有规模的项目,在大家的提议下,我们选择去做一个可以显示温度的LED时钟,而且显示要用LED才大气,于是我们开始了有序的准备工作。

实现

为了实现这个小钟的预期功能,我们听取了老师的意见。使用STC15W402AS芯片(宏晶科技生产)作为主控芯片,利用LED小灯组成的4个“8”作为显示部分,同时使用DS1302(Maxim生产)作为系统时钟芯片,使用DS18B20(Maxim生产)作为温度传感器芯片。电源使用MicroUSB+5V供电。电路图为自行设计,使用Protel绘制,再有工厂加工,随后按电路图焊接原件,最后进行编程调试。

图片展示

DSC_0072-min
DSC_0072-min
DSC_0093
DSC_0093
DSC_0088-min
DSC_0088-min

结构

使用STC15W402AS芯片(宏晶科技生产)作为主控芯片,利用LED小灯组成的4个“8”作为显示部分,同时使用DS1302(Maxim生产)作为系统时钟芯片,使用DS18B20(Maxim生产)作为温度传感器芯片。电源使用MicroUSB+5V供电。电路图为自行设计。

重要逻辑

显示原理 - 视觉暂留扫描

由于我们使用了类似于放大版“数码管”的显示方式,限于单片机引脚数量限制,我们不能把每个8的每个部分都和单片机相连。于是我们采用了现在LED显示屏的显示原理–扫描点亮的原理。在我们的电路中,我们将4个“8”的8个组成部分(a,b,c,d,e,f,g)的正极和单片机P1端口(恰好8个端口)连接(也就是把每个“8”的a并连起来接到一个单片机引脚,把每个“8”的b并连起来接到另一个单片机引脚,以此类推),再将每个“8”的所有组成部分的所有负极并联,分别和单片机上P2端口上的4个引脚相连,组成显示控制电路。需要点亮第1个“8”时就先将这个“8”的负极引脚置低,再将需要点亮的部分引脚同时置高。例如下图所示想要显示“1”,就将b,c的引脚置高

数码管
数码管

这样问题就来了:现在我们能一个一个点亮了,如果我让这4个“8”一起亮怎么办?

这就需要视觉暂留原理了,大家都知道电影之所以能动起来就是因为我们人眼有0.1秒的视觉暂留。我们点亮小灯也如此,先点亮第一个,延迟5ms,再点亮第二个,延迟5ms,以此类推。5ms这样短的时间人根本看不出闪动感,于是完整的图像显示出来。 下面是时间送显的代码(chart数组里是预先设计好的自模):

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
void convertShow(char hour_s1,hour_s2,min_s1,min_s2)
{

E1 = 0;
P1 = chart[hour_s1];
delayMS(5);
P1 = allclear;
E1 = 1;

E2 = 0;
P1 = chart[hour_s2];
delayMS(5);
P1 = allclear;
E2 = 1;

E3 = 0;
P1 = chart[min_s1];
delayMS(5);
P1 = allclear;
E3 = 1;

E4 = 0;
P1 = chart[min_s2];
delayMS(5);
P1 = allclear;
E4 = 1;
if(sec_2%0x02==0x01)
{
MDLIGHT = 0;
}
else
MDLIGHT = 1;

}

读取时间和温度

这个想起来很简单,无非是单片机一边显示,一边不断向时钟芯片或者温度芯片询问数据,至于芯片如何驱动,则是后面要讲的重头戏

时间修改/模式切换 - 按钮的使用

谁家的钟不能改时间啊,所以我们要支持时间修改功能,于是我们决定用传统的按钮来修改时间。使用三个按钮(称呼他们为Btn1,Btn2,Btn3)。Btn1负责加时间,短按是加1,长按递加,直到松开。Btn2和Btn1相反,而Btn3则是模式切换(一共三种:修改小时,修改分钟,显示温度)。

可是在实际编程时我们发现,按钮只要收到轻微扰动就响应,并不是按下才响应,这是为何?

原来通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖,如下图。

20150722113229813
20150722113229813

看来我们需要软件软件消抖(硬件消抖效果好可是我们的电路图已经印刷了啊)。软件消抖即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给5ms~10ms的延时,待后沿抖动消失后才能转入该键的处理程序。程序如下:

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
if (KEY_1==0 || KEY_2==0 || KEY_3==0)
{
delayMS(20); //20毫秒软件防抖
if (KEY_1 == 0)
{
keyValue = 1;
while(KEY_1==0)
{
modifyTime(status+8);
delayShow(500);
}
}
if (KEY_2 == 0)
{
keyValue = 2;
while(KEY_2==0)
{
modifyTime(status+16);
delayShow(500);
}
}
if (KEY_3 == 0)
{
keyValue = 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
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
/*************按钮扫描函数******************************/
void keyScan()
{
if (KEY_1==0 || KEY_2==0 || KEY_3==0)
{
delayMS(20); //20毫秒软件防抖
if (KEY_1 == 0)
{
keyValue = 1;
while(KEY_1==0)
{
modifyTime(status+8);
delayShow(500);
}
}
if (KEY_2 == 0)
{
keyValue = 2;
while(KEY_2==0)
{
modifyTime(status+16);
delayShow(500);
}
}
if (KEY_3 == 0)
{
keyValue = 3;
}
}
}

/*************按钮响应处理函数******************************/
void keyHandle()
{
if(keyValue==1)
{
DS1302_Write(sec,min,hour,day,month,week,year);
keyValue = 0;
}
else if(keyValue==2)
{
DS1302_Write(sec,min,hour,day,month,week,year);
keyValue = 0;
}
else if(keyValue==3)
{
if (status==1)
{
status = 2;
}
else if(status==2)
{
status = 3;
}
else if(status==3)
{
status = 1;
}
keyValue = 0;

}

}

还有修改时间的一些诡异逻辑代码,涉及到一些标志变量,请仔细研读体会:

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
/*************修改时间函数******************************/
void modifyTime(int mode) //mode指明修改小时或分钟 1:小时 2:分钟
{

//修改小时(1)+递增(8)
if(mode == 9)
{
if(hour==0x23)
{
hour = 0x00;
}
else if(hour==0x09)
{
hour=0x10;
}
else if(hour==0x19)
{
hour=0x20;
}
else
{
hour=hour+0x01;
}

timeConvert();
convertShow(hour_1,hour_2,min_1,min_2);
}

//修改小时(1)+递减(16)
if(mode == 17)
{
if(hour==0x00)
{
hour = 0x23;
}
else if(hour==0x10)
{
hour = 0x09;
}
else if(hour==0x20)
{
hour = 0x19;
}
else
{
hour = hour-0x01;
}

timeConvert();
convertShow(hour_1,hour_2,min_1,min_2);
}

//修改分钟(2)+递增(8)
if(mode == 10)
{

if(min==0x59)
{
min=0x00;
}
else if(min-(min_1<<4)==0x09)
{
min=(min_1+0x01)<<4;
}
else
{
min=min+0x01;
}

timeConvert();
convertShow(hour_1,hour_2,min_1,min_2);
}

//修改分钟(2)+递减(16)
if(mode == 18)
{
if(min==0x00)
{
min=0x59;
}
else if(min_2==0x00)
{
min=min-0x10;
min=min+0x09;
}
else
{
min=min-0x01;
}

timeConvert();
convertShow(hour_1,hour_2,min_1,min_2);
}

}

整体逻辑

各部分的逻辑设计都完成了,我们现在开始设计整个程序的主函数部分。单片机上电后首先初始化(包括引脚置低什么的不再赘述)检测DS1302是否在运行(内置电池可以掉电走时),如果在运行就读取时间送显,不在运行就起振(开始走时)。然后在每个显示函数过后侦测按钮操作,如果发现有效按钮操作就进行处理按钮的逻辑,该修改时间就修改时间,该显示温度就显示温度。这个流程用死循环加以嵌套,就可以无止无休的运行下去(不断电的话)。下面是主函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main()
{
init();
while(1)
{
if(status==3)
{
temp = DS18B20_ReadTemp();
tempHandle();
tempshow(temp\_1,temp\_2);
}
else
{
DS1302_readtime();
convertShow(hour\_1,hour\_2,min\_1,min\_2);
}
keyScan();
if(keyValue!=0&&KEY\_1==1&&KEY\_2==1&&KEY_3==1)
{
keyHandle();
}
}
}

下面是初始化函数:

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
void init()
{
//关闭所有小灯
E1=1;
E2=1;
E3=1;
E4=1;
MDLIGHT = 1;
P1 = allclear;
T_CE = 0;
T_SCLK = 0;

//默认初始化时间12:00
sec = 0x00;
min = 0x00;
hour = 0x16;
year = 0x01;
month = 0x01;
week = 0x01;
day = 0x01;
status = 1;
keyValue = 0;
delayMS(1000);
//DS1302初始化判断是否存在后备电源

if(DS1302_Read(0x81)&0x80==0x80)
{
DS1302\_Write\_one(0x8e,0x00);
DS1302\_Write\_one(0x80,sec); //起振
DS1302\_Write\_one(0x8e,0x80);
}
else
{

}

}

最后就剩下这两枚芯片和一些相关更改函数了。下面我将详细讲解下这两个核心芯片。

核心技术

BCD码

为什么要讲它?因为在单片机中,很多芯片存储数据都使用了BCD码。我们日常生产生活中用的最多的数字是十进制数字,而单片机系统的所有数据本质上都是二进制的,所以聪明的前辈们就给我们创造了BCD码。 BCD码(Binary-Coded Decimal)亦称二进码十进制数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个数字。是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。我们前边讲过十六进制和二进制本质上是一回事,十六进制仅仅是二进制的一种缩写形式而已。而十进制的一位数字,从0到9,最大的数字就是9,再加1就要进位,所以用4位二进制表示十进制,就是从0000到1001,不存在1010、1011、1100、1101、1110、1111这6个数字。BCD码如果到了1001,再加1的话,数字就变成了0001 0000这样的数字了,相当于用了8位的二进制数字表示了2位的十进制数字。

DS1302时钟芯片概述

DS1302是DALLAS(达拉斯)公司出的一款涓流充电时钟芯片,2001年DALLAS被MAXIM(美信)收购,因此我们看到的DS1302的数据手册既有DALLAS的标志,又有MAXIM的标志,大家了解即可。DS1302实时时钟芯片广泛应用于电话、传真、便携式仪器等产品领域,他的主要性能指标如下: - DS1302是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软年自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。 - 拥有31字节数据存储RAM。 - 串行I/O通信方式,相对并行来说比较节省IO口的使用。 - DS1302的工作电压比较宽,大概是2.0V~5.5V都可以正常工作。 - DS1302这种时钟芯片功耗一般都很低,它在工作电压2.0V的时候,工作电流小于300nA。 - DS1302共有8个引脚,有两种封装形式,一种是DIP-8封装,芯片宽度(不含引脚)是300mil,一种是SOP-8封装,有两种宽度,一种是150mil,一种是208mil。我们看一下DS1302的引脚封装图

1339602328_6820
1339602328_6820

所谓的DIP封装Dual In-line Package,也叫做双列直插式封装技术,就如同我们开发板上的STC89C52RC单片机,就是个典型的DIP封装,当然这个STC89C52RC还有其他的封装,为了方便学习使用,我们采用的是DIP封装。而74HC245、74HC138、24C02、DS1302我们用的都是SOP封装Small Out-Line Package,是一种芯片两侧引出L形引脚的封装技术,大家可以看看开发板上的芯片,了解一下这些常识性知识。

DS1302时钟芯片硬件信息

下面是这种芯片的引脚图和常用电路:

005622dumxffht26fo57z3
005622dumxffht26fo57z3
005623reu8ke2rpzqpvvp4
005623reu8ke2rpzqpvvp4

1脚VCC2是主电源正极的引脚,2脚X1和3脚X2是晶振输入和输出引脚,4脚GND是负极,5脚CE是使能引脚,接单片机的IO口,6脚I/O是数据传输引脚,接单片机的IO口,7脚SCLK是通信时钟引脚,接单片机的IO口,8脚VCC1是备用电源引脚。 在实际使用时除了和单片机连接,还要连接晶振(32.768k)产生振荡信号维持走时,电路如下图:

005625x1htctqg4cthtzh7
005625x1htctqg4cthtzh7

我们希望时钟即使掉电也能走时,好在它同样提供了这个功能,下图的电容我们换做了RS2302纽扣电池,RS2302电流恒定,自放电小,使用时间长:

005626gp8kff5mjmbnzhwn
005626gp8kff5mjmbnzhwn

DS1302时钟芯片寄存器介绍

###命令字节 DS1302的一条指令一个字节8位,其中第七位(即最高位)是固定1,这一位如果是0的话,那写进去是无效的。第六位是选择RAM还是CLOCK的,我前边说过,我们这里主要讲CLOCK时钟的使用,它的RAM功能我们不用,所以如果选择CLOCK功能,第六位是0,如果要用RAM,那第六位就是1。从第五到第一位,决定了寄存器的5位地址,而第零位是读写位,如果要写,这一位就是0,如果要读,这一位就是1,附上命令字节图:

005627coirnijcoijuhczi
005627coirnijcoijuhczi

###DS1302时钟芯片时钟寄存器 DS1302时钟的寄存器,其中8个和时钟有关的,5位地址分别是00000一直到00111这8个地址,还有一个寄存器的地址是01000,这是涓流充电所用的寄存器,我们这里不讲。在DS1302的数据手册里的地址,直接把第七位、第六位和第零位值给出来了,所以指令就成了80H、81H那些了,最低位是1,那么表示读,最低位是0表示写,附上图:

005919jldgzkz84e4jk90f
005919jldgzkz84e4jk90f
  • 寄存器一:最高位CH是一个时钟停止标志位。如果我们的时钟电路有备用电源部分,上电后,我们要先检测一下这一位,如果这一位是0,那说明我们的时钟在系统掉电后,由于备用电源的供给,时钟是持续正常运行的;如果这一位是1,那么说明我们的时钟在系统掉电后,时钟部分不工作了。若我们的Vcc1悬空或者是电池没电了,当我们下次重新上电时,读取这一位,那这一位就是1,我们可以通过这一位判断时钟在单片机系统掉电后是否持续运行。剩下的7位高3位是秒的十位,低4位是秒的个位,这里注意再提一次,DS1302内部是BCD码,而秒的十位最大是5,所以3个二进制位就够了。

  • 寄存器二:bit7没意义,剩下的7位高3位是分钟的十位,低4位是分钟的个位。

  • 寄存器三:bit7是1的话代表是12小时制,是0的话代表是24小时制,bit6固定是0,bit5在12小时制下0代表的是上午,1代表的是下午,在24小时制下和bit4一起代表了小时的十位,低4位代表的是小时的个位。

  • 寄存器四:高2位固定是0,bit5和bit4是日期的十位,低4位是日期的个位。

  • 寄存器五:高3位固定是0,bit4是月的十位,低4位是月的个位。

  • 寄存器六:高5位固定是0,低3位代表了星期。

  • 寄存器七:高4位代表了年的十位,低4位代表了年的个位。这里特别注意,这里的00到99年指的是2000年到2099年。

  • 寄存器八:bit7是一个保护位,如果这一位是1,那么是禁止给任何其他的寄存器或者那31个字节的RAM写数据的。因此在写数据之前,这一位必须先写成0。

DS1302时钟芯片通信介绍

DS1302我们前边也有提起过,是三根线,分别是CE、I/O和SCLK,其中CE是使能线,SCLK是时钟线,I/O是数据线。这个DS1302的通信线定义和SPI怎么这么像呢?事实上,DS1302的通信是SPI的变异种类,它用了SPI的通信时序,但是通信的时候没有完全按照SPI的规则来。 先看一下单字节写入操作:

005922kdzkkokdknkkbqoo
005922kdzkkokdknkkbqoo

再看单字节读出操作:

005929p0m00b0lvclxn7jl
005929p0m00b0lvclxn7jl

对DS1302的操作一般只有两种:读数据和写数据。读数据即读出芯片RAM中的日期,时间等信息,写数据即修改这些信息。数据的读写是以字节为单位操作的,读操作函数要完成的功能便是传入一个一字节表示特定的地址,函数返该地址RAM中一字节或多字节的数据。写操作函数需要传入两个参数:地址和要写的数据;函数将要写的一字节数据写入给定的地址。对于同一个内容,读和写通过地址中一个标志位来区分,因此表现出来就是读和写的地址不一样,这样芯片通过地址来区分你是要还是要写;例如读秒的地址是0x81,而写秒的地址是0x80 CE,SCLK,I/O是单片机与DS1302连接的三条线,要进行读操作,首先把CE和SCLK置0,准备读数据。 CE是使能信号,只有他为高才能进行操作。读数据操作实际上是先写后读的操作,因为要先写入地址,然后读该地址的数据。图中带箭头的脉冲跳变表示起作用的脉冲跳变,因此读数据时上升沿有效,即SCLK从0变1的瞬间,I/O线上的数据就会被写入DS1302。而读出是下降沿有效,即DS1302会在每个下降沿的时候修改数据线上的数据,因此,从此刻到下一个下降沿的这段时间内,MCU读数据线上的数据都是正确的。要注意MCU写完一个字节后第一个下降沿DS1302就开始送出数据了读写都是从低位到高位。 写数据简单,一次性把两个字节全部写进去就行了。 下面看看我们是实现的代码:

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
/*DS1302驱动函数*/

/*************写入一字节****************/
void DS1302_Input_Byte(char Input) //向时钟IC写入一字节
{
char i;
T_SCLK = 0;
delay2us();
ACC =Input;
for(i=8; i>0; i--)
{
T_DIO = ACC_0; //相当于汇编中的 RRC
delay2us();
T_SCLK = 1;
delay2us();
T_SCLK = 0;
ACC = ACC >> 1;
}
}

/*************读取一字节****************/
char DS1302_Output_Byte(void) //从时钟IC读取一字节()
{
char i;
for(i=8; i>0; i--)
{
ACC>>=1;
T_DIO= 1;
delay2us();
ACC_7 = T_DIO;
T_SCLK = 1; //相当于汇编中的 RRC
delay2us();
T_SCLK = 0;
delay2us();
}
T_DIO = 0;
delay2us();
return(ACC);
}

/*************写一字节数据****************/
void DS1302_Write_one( char addr,dat ) // 写入地址、数据子程序
{
T_CE=0; //T_CE引脚为低,数据传送中止
T_SCLK=0; //清零时钟总线
T_CE = 1; //T_CE引脚为高,逻辑控制有效
DS1302_Input_Byte(addr); // 地址,命令
DS1302_Input_Byte(dat); // 写1Byte数据
T_SCLK = 1;
T_CE = 0;
}

/*************读一字节数据****************/
char DS1302_Read ( char addr ) //数据读取子程序
{
char date;
T_CE=0;
T_SCLK=0;
T_CE = 1;
DS1302_Input_Byte(addr); // 地址,命令
date = DS1302_Output_Byte(); // 读1Byte数据
T_SCLK = 1;
T_CE = 0;
return(date);
}


/*************写入时间数据****************/
void DS1302_Write(char sec_w,min_w,hour_w,day_w,month_w,week_w,year_w)
{
DS1302_Write\one(0x8e,0x00);
DS1302_Write_one(0x82,min_w);
DS1302_Write_one(0x84,hour_w);
DS1302_Write_one(0x86,day_w);
DS1302_Write_one(0x88,month_w);
DS1302_Write_one(0x8a,week_w);
DS1302_Write_one(0x8c,year_w);
DS1302_Write_one(0x80,sec_w);
DS1302_Write_one(0x8e,0x80);

}

/*************时间转换为显示格式****************/
void timeConvert()
{
sec_1 = sec>>4;
sec_2 = sec&0x0f;
min_1 = min>>4;
min_2 = min&0x0f;
hour_1 = hour>>4;
hour_2 = hour&0x0f;

}

/*************从芯片读取时间****************/
void DS1302_readtime()
{
sec=DS1302_Read(0x81); //读秒
min=DS1302_Read(0x83); //读分
hour=DS1302_Read(0x85); //读时
day=DS1302_Read(0x87); //读日
month=DS1302_Read(0x89); //读月
year=DS1302_Read(0x8d); //读年
week=DS1302_Read(0x8b); //读星期
timeConvert();

}

写这段函数可真是历尽千辛万苦,其中的BUG那真是诡异啊。第一次用国产芯片就是没法揭开保护,换成进口原装之后又不能正常写入时间(国产是有多不靠谱),后来才发现网上流传的代码有一个不起眼错误导致无法写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char DS1302\_Output\_Byte(void)      //从时钟IC读取一字节()
{
char i;
for(i=8; i>0; i--)
{
ACC>>=1;
T_DIO= 1;
delay2us();
ACC\_7 = T\_DIO;
T_SCLK = 1; //相当于汇编中的 RRC
delay2us();
T_SCLK = 0;
delay2us();
}
T_DIO = 0; //问题所在
delay2us();
return(ACC);
}

有没有注意循环结束后的TDIO = 0;_ ?这句话表示读取后重新将数据口置低,如果没有这句话,后续的写入将无法进行,大家要注意。 PS:如果进行开发工作,硬件一定要原装进口,否则问题会很多! 其实还有一个Burst模式和涓流充电,由于没用到,在这里不多讲解。

DS18B20温度芯片

DS18B20温度芯片概述

DS1820 数字温度计提供 9 位温度读数,指示器件的温度信息经过单线接口送入 DS1820 或从 DS1820 送出 因此从中央处理器到 DS1820 仅需连接一条线 (和地)读,写和完成温度变换所需的电源可以由数据线本身提供,而不需要外部电源。因为每一个 DS1820 有唯一的系列号(silicon serial number)因此多个 DS1820 可以存在 于同一条单线总线上,这允许在许多不同的地方放置温度灵敏器件 优点:

  • 采用单总线的接口方式 与微处理器连接时仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量,使用方便等优点,使用户可轻松地组建传感器网络,为测量系统的构建引入全新概念。

  • 测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ + 125 ℃ ; 在 -10+ 85°C范围内,精度为 ± 0.5°C 。

  • 在使用中不需要任何外围元件。

  • 持多点组网功能 多个 DS18B20 可以并联在惟一的单线上,实现多点测温。

  • 供电方式灵活 DS18B20 可以通过内部寄生电路从数据线上获取电源。因此,当数据线上的时序满足一定的要求时,可以不接外部电源,从而使系统结构更趋简单,可靠性更高。

  • 测量参数可配置 DS18B20 的测量分辨率可通过程序设定 9~12 位。

  • 负压特性电源极性接反时,温度计不会因发热而烧毁,但不能正常工作。

  • 掉电保护功能 DS18B20 内部含有 EEPROM ,在系统掉电以后,它仍可保存分辨率及报警温度的设定值。

DS18B20 具有体积更小、适用电压更宽、更经济、可选更小的封装方式,更宽的电压适用范围,适合于构建自己的经济的测温系统,因此也就被设计者们所青睐。

DS18B20温度芯片的封装和管脚定义

2图二:DS18B20封装及引脚图 (1)
2图二:DS18B20封装及引脚图 (1)
164335tbb3010zs8z80cvh
164335tbb3010zs8z80cvh

DS18B20温度芯片电路连接

1-140323230233592
1-140323230233592

外部供电模式下的单只DS18B20芯片的连接图

DS18B20温度芯片内部寄存器解析及工作原理

  • DS18B20内部主要寄存器的逻辑图
    6图六:DS18B20内部结构框图
    6图六:DS18B20内部结构框图

结合图中的内部寄存器框图,我们先简单说一下DS18B20芯片的主要寄存器工作流程,而在对DS18B20工作原理进行详细说明前,有必要先上几张相关图片:

  • DS18B20内部寄存器结构图

[

7图七:DS18B20寄存器结构示意图](../images/2016/08/7图七:DS18B20寄存器结构示意图-300x177.jpg)
7图七:DS18B20寄存器结构示意图](../images/2016/08/7图七:DS18B20寄存器结构示意图-300x177.jpg)

  • DS18B20主要寄存器数据格式图示
8图八:DS18B20主要寄存器数据格式
8图八:DS18B20主要寄存器数据格式
  • DS18B20通讯指令图
DS18B20-ROM指令
DS18B20-ROM指令

DS18B20启动后将进入低功耗等待状态,当需要执行温度测量和AD转换时,总线控制器发出[44H]指令完成温度测量和AD转换(其他功能指令见上面的指令表),DS18B20将产生的温度数据以两个字节的形式存储到高速暂存器的温度寄存器中,然后,DS18B20继续保持等待状态。当DS18B20芯片由外部电源供电时,总线控制器在温度转换指令之后发起“读时隙”,从而读出测量到的温度数据通过总线完成与单片机的数据通讯(DS18B20正在温度转换中由DQ引脚返回0,转换结束则返回1。如果DS18B20由寄生电源供电,除非在进入温度转换时总线被一个强上拉拉高,否则将不会有返回值)。另外,DS18B20在完成一次温度转换后,会将温度值与存储在TH(高温触发器)和TL(低温触发器)中各一个字节的用户自定义的报警预置值进行比较,寄存器中的S标志位(详见寄存器格式图示中的“TH和TL寄存器格式”图示)指出温度值的正负(S=0时为正,S=1时为负),如果测得的温度高于TH或者低于TL数值,报警条件成立,DS18B20内部将对一个报警标识置位,此时,总线控制器通过发出报警搜索命令[ECH]检测总线上所有的DS18B20报警标识,然后,对报警标识置位的DS18B20将响应这条搜索命令。

DS18B20温度芯片的工作时序

在由DS18B20芯片构建的温度检测系统中,采用达拉斯公司独特的单总线数据通讯方式,允许在一条总线上挂载多个DS18B20,那么,在对DS18B20的操作和控制中,由总线控制器发出的时隙信号就显得尤为重要。如下图所示,分别为DS18B20芯片的上电初始化时隙、总线控制器从DS18B20读取数据时隙、总线控制器向DS18B20写入数据时隙的示意图,在系统编程时,_一定要严格参照时隙图中的时间数据_,做到精确的把握总线电平随时间(微秒级)的变化,才能够顺利地控制和操作DS18B20。另外,需要注意到不同单片机的机器周期是不尽相同的,所以,程序中的延时函数并不是完全一样,要根据单片机不同的机器周期有所改动。在平常的DS18B20程序调试中,若发现诸如温度显示错误等故障,基本上都是由于时隙的误差较大甚至时隙错误导致的,在对DS18B20编程时需要格外注意。

  • 上电初始化时隙图
9图九:DS18B20上电初始化时隙图
9图九:DS18B20上电初始化时隙图

主机首先发出一个480-960微秒的低电平脉冲,然后释放总线变为高电平,并在随后的480微秒时间内对总线进行检测,如果有低电平出现说明总线上有器件已做出应答。若无低电平出现一直都是高电平说明总线上无器件应答。做为从器件的DS18B20在一上电后就一直在检测总线上是否有480-960微秒的低电平出现,如果有,在总线转为高电平后等待15-60微秒后将总线电平拉低60-240微秒做出响应存在脉冲,告诉主机本器件已做好准备。若没有检测到就一直在检测等待。 初始化代码如下:

1
2
3
4
5
6
7
8
9
10
void DS18B20_Init(void)
{
uchar x=0;
DQ = 1; //DQ复位
DelayXus(7); //稍做延时
DQ = 0; //单片机将DQ拉低
DelayXus(500); //精确延时 大于 480us
DQ = 1; //拉高总线
DelayXus(200); //足够的延迟 确保能让DS18B20发出存在脉冲
}
  • 数据读取时通讯总线的时隙图
10图十:总线控制器从DS18B20寄存器读数据时隙图
10图十:总线控制器从DS18B20寄存器读数据时隙图

写周期最少为60微秒,最长不超过120微秒。写周期一开始做为主机先把总线拉低1微秒表示写周期开始。随后若主机想写0,则继续拉低电平最少60微秒直至写周期结束,然后释放总线为高电平。若主机想写1,在一开始拉低总线电平1微秒后就释放总线为高电平,一直到写周期结束。而做为从机的DS18B20则在检测到总线被拉底后等待15微秒然后从15us到45us开始对总线采样,在采样期内总线为高电平则为1,若采样期内总线为低电平则为0。 读取代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uchar DS18B20_ReadOneChar(void)
{
uchar i=0;
uchar dat = 0;
for (i=8;i>0;i--)
{
DQ = 0; // 给脉冲信号
DelayXus(1);
dat>>=1;
DQ = 1; // 给脉冲信号
DelayXus(7);
if(DQ)
dat|=0x80;
DelayXus(60);
}
return(dat);
}
  • 数据写入时通讯总线的时隙图
    11图十一:总线控制器向DS18B20写数据时隙图
    11图十一:总线控制器向DS18B20写数据时隙图

对于读数据操作时序也分为读0时序和读1时序两个过程。读时隙是从主机把单总线拉低之后,在1微秒之后就得释放单总线为高电平,以让DS18B20把数据传输到单总线上。DS18B20在检测到总线被拉低1微秒后,便开始送出数据,若是要送出0就把总线拉为低电平直到读周期结束。若要送出1则释放总线为高电平。主机在一开始拉低总线1微秒后释放总线,然后在包括前面的拉低总线电平1微秒在内的15微秒时间内完成对总线进行采样检测,采样期内总线为低电平则确认为0。采样期内总线为高电平则确认为1。完成一个读时序过程,至少需要60us才能完成 写入代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void DS18B20_WriteOneChar(uchar dat)
{
uchar i=0;
for (i=8; i>0; i--)
{
DQ = 0;
DelayXus(1);
DQ = dat&0x01;
DelayXus(60);
DQ = 1;
dat>>=1;
DelayXus(7);
}
}

DS18B20温度芯片单线通信

DS18B20 单线通信功能是分时完成的,他有严格的时隙概念,如果出现序列混乱, 1-WIRE 器件将不响应主机,因此读写时序很重要。系统对 DS18B20 的各种操作必须按协议进行。根据 DS18B20 的协议规定,微控制器控制 DS18B20 完成温度的转换必须经过以下 3个步骤 :
1.每次读写前对 DS18B20 进行复位初始化。复位要求主 CPU 将数据线下拉 500us ,然后释放, DS18B20 收到信号后等待 16us60us 左右,然后发出60us240us 的存在低脉冲,主 CPU 收到此信号后表示复位成功。

2.发送一条 ROM 指令

03a5e041da0a33d31d50293c62824404
03a5e041da0a33d31d50293c62824404

3.发送存储器指令

50db8a09face58dba90cd8b37281d656
50db8a09face58dba90cd8b37281d656

通信过程实例

具体操作举例:

  • 现在我们要做的是让DS18B20进行一次温度的转换,那具体的操作就是:

1.主机先作个复位操作, 2.主机再写跳过ROM的操作(CCH)命令, 3.然后主机接着写个转换温度的操作命令,后面释放总线至少一秒,让DS18B20完成转换的操作。在这里要注意的是每个命令字节在写的时候都是低字节先写,例如CCH的二进制为11001100,在写到总线上时要从低位开始写,写的顺序是“零、零、壹、壹、零、零、壹、壹”。

  • 读取RAM内的温度数据。同样,这个操作也要接照三个步骤。

1.主机发出复位操作并接收DS18B20的应答(存在)脉冲。 2.主机发出跳过对ROM操作的命令(CCH)。 3.主机发出读取RAM的命令(BEH),随后主机依次读取DS18B20发出的从第0一第8,共九个字节的数据。如果只想读取温度数据,那在读完第0和第1个数据后就不再理会后面DS18B20发出的数据即可。同样读取数据也是低位在前的。

DS18B20温度芯片温度存储格式

DS18B20 通过编程,可以实现最高 12 位的温度存储值,在寄存器中,以补码的格式存储

1-14032323032L26
1-14032323032L26

一共 2 个字节,LSB 是低字节,MSB 是高字节,其中 MSB 是字节的高位,LSB 是字节的低位。大家可以看出来,二进制数字,每一位代表的温度的含义,都表示出来了。其中 S表示的是符号位,低 11 位都是 2 的幂,用来表示最终的温度。DS18B20 的温度测量范围是从-55 度到+125 度,而温度数据的表现形式,有正负温度,寄存器中每个数字如同卡尺的刻度一样分布,其中S代表温度正负

1-140323230431910 (1)
1-140323230431910 (1)

其中读取温度和温度数据处理的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uint DS18B20_ReadTemp(void)
{
uchar a;
uchar b;
uint readTemp;
DS18B20_Init();
DS18B20_WriteOneChar(0xCC);
DS18B20_WriteOneChar(0x44);
while (!DQ);
DS18B20_Init();
DS18B20_WriteOneChar(0xCC);
DS18B20_WriteOneChar(0xBE);
a=DS18B20_ReadOneChar(); //读取温度值低位
b=DS18B20_ReadOneChar(); //读取温度值高位
readTemp = b<<8;
readTemp |= a;
readTemp = readTemp>>4;
return readTemp;
}

至此,我们就完成了整个单片机的讲解。下面我给大家切入一下实践过程,讲解下如何使用自己完成一块温度LED小钟。

相关资源下载

代码电路图文档

代码以及电路图被开源在Github上,可以从仓库Clone 基于STC芯片的DS1302温度LED时钟 如果你没有Github,我们也提供百度云代码包下载 上述讲解的芯片的技术文档也提供百度云文档包下载

文件结构

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
.
├── circuit-diagram //电路图文件夹
│   ├── CLOCK.PCB //基础电路图
│   └── CLOCKPT.PCB //铺铜电路图
├── header-file //头文件文件夹
│   ├── definecontrol.h //定义引脚以及字模
│   ├── DS1302.H //定义DS1302引脚
│   └── STC15W402AS.h //单片机头文件
├── LICENSE.md
├── main-code //主要逻辑代码
│   ├── 1302BatteryDebug.c //DS1302掉电走时调试
│   ├── 1302Debug.c //DS1302调试
│   ├── 18B20Test.c //DS18B20测试
│   ├── buttonTest.c //按钮测试
│   ├── chartTest.c //字模测试
│   ├── Clock.xmp //TKStudio项目文件
│   ├── Debug //二进制文件夹
│   │   ├── 1302BatteryDebug.obj //DS1302掉电走时调试
│   │   ├── 1302TEST.obj //DS1302调试
│   │   ├── 18B20Test.obj //DS18B20测试
│   │   ├── buttonTest.obj //按钮测试
│   │   ├── Clock
│   │   ├── Clock.hex
│   │   ├── Clock.lnp
│   │   ├── Clock.M51
│   │   ├── lightTest.obj //LED灯测试
│   │   ├── main.obj //最终代码
│   ├── definecontrol.h //定义引脚以及字模
│   ├── DS1302.h //定义DS1302引脚
│   ├── lightTest.c //LED灯测试
│   ├── main.c //最终代码
│   └── STC15W402AS.h //单片机头文件
└── README.md

DIY教程

Windows

1.克隆或下载本仓库,PCB电路图在仓库内,印刷后按照引脚焊接元件。

2.下载宏晶科技官网所提供的相关烧录软件与教程,并安装。

3.下载KeilC51开发工具,并安装。

4.下载TKStudio,并安装(可选)。

5.使用USB转TTl烧录器连接板载串口与计算机(如果需要驱动请自行安装)

6.使用STC烧录软件,选择STC15W402AS型号,使用默认配置(单片机震荡频率11.0592MHz),载入想要烧录的二进制文件,上电烧录。

7.如果需要增加修改逻辑代码,可以使用TKStudio打开main-code/Clock.xmp进行二次开发,其他IDE也可。 PS:如果你懒得下上述编程软件,我们也提供了百度云单片机软件包下载

用途

可作为家用时钟,同时提供了针对STC系列单片机较为成熟的DS1302和DS18B20驱动方案,可以作为院校单片机学习项目。

关于作者

主要设计者&代码贡献者:hackerchai(柴轶晟) 参与开发者:沈冠霖 王荣 主要硬件设计:李沐东 沈冠霖 资料整理:张子誉 感谢辽宁省沈阳市东北育才学校高中部 机器人实验室 龚鹏老师的指导。