结合前几天来写过的文章, 今天总算写了一个功能较多的应用 - 多功能时钟, 集时钟, 秒表, 温度计一体.
基础文章:
1. 单片机练习 - DS18B20温度转换与显示
2. 用C51编写单片机延时函数
3. 单片机练习 - 定时器
4. 单片机练习 - 计时器
实验板: TX-1B实验板
6位数码管与单片机的连接电路图

按键S2, S3与单片机的连接电路图: 其中S2与P3.4连, S3与P3.5连接...

DS18B20与单片机连接电路图:

具体按键功能分配请看源代码注释部分:

多功能时钟
1
//多功能时钟, 精确到小数0.01秒, 即10ms
2
//功能: 时钟, 秒表, 温度计
3
4
/**//*
5
S5键为功能选择键, 上电默认使用时钟功能
6
功能顺序为: 时钟, 温度计, 秒表
7
8
mode = 1. 时钟(每次掉电后都要重新设置时间)
9
1)当选中时钟功能时, 具体按键功能如下:
10
11
2)可设置时分秒, 时利用发光二极管显示, 分秒用数码管显示
12
13
3)时钟: 采用定时器0计时, 工作方式1
14
15
mode = 2. 时钟设置模式
16
当选中时钟设置模式
17
S2为位选, S3为增加选中位的值
18
S4确定更改, S5放弃更改, 进入秒表模式
19
20
mode = 3. 秒表
21
1)当选中秒表功能时, 具体按键功能如下:
22
S2为开始/暂停, S3为清零
23
24
2)采用定时器1计时, 工作方式1
25
26
mode = 4. 温度计
27
1)利用DS18B20检测环境温度;
28
2)最小温度值为0.01℃, 可表示温度范围: -55℃~+125℃
29
30
*/
31
32
#include <reg52.H>
33
#include <intrins.H>
34
#include <math.h>
35
36
//0-F数码管的编码(共阴极)
37
unsigned char code table[]=
{0x3f,0x06,0x5b,0x4f,0x66,
38
0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
39
//0-9数码管的编码(共阴极), 带小数点
40
unsigned char code tableWidthDot[]=
{0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd,
41
0x87, 0xff, 0xef};
42
43
sbit wela = P2^7; //数码管位选
44
sbit dula = P2^6; //数码管段选
45
sbit ds = P2^2;
46
unsigned char th, tl, mode = 1; //mode存放功能模式, 默认在模式1 时钟
47
unsigned char clockPosition = 0; //时钟设置模式下, 光标所在的位置; 默认在0
48
unsigned char clockTmp = 0; //用于时钟模式下临时计数
49
bit clockTmpBit = 0; //用于时钟模式下临时标志位
50
51
//秒4字节, 分2字节, 时1字节
52
unsigned char datas[] =
{0, 0, 0, 0, 0, 0, 0};//保存计时器数据
53
unsigned char clockDatas[] =
{0, 0, 0, 0, 0, 0, 0};//保存时钟数据
54
unsigned char * values = clockDatas; //根据mode选择适当的数据数组指针
55
int tempValue; //存放温度值
56
unsigned char tempCount = 0; //用于记录显示了多少次温度值, 用于定时
57
58
sbit S2 = P3^4; //键S2, 作开始/暂停
59
sbit S3 = P3^5; //键S3, 清零
60
sbit S4 = P3^6; //键S4
61
sbit S5 = P3^7; //键S5
62
unsigned char tmpDatas[] =
{0, 0, 0, 0, 0, 0, 0}; //存放临时设置值
63
unsigned char icount;
64
65
//延时函数, 对于11.0592MHz时钟, 例i=5,则大概延时5ms.
66
void delay(unsigned int i)
67

{
68
unsigned int j;
69
while(i--)
70
{
71
for(j = 0; j < 125; j++);
72
}
73
}
74
75
/**//***********************温度计模式******************************/
76
77
//初始化DS18B20
78
//让DS18B20一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动
79
void dsInit()
80

{
81
//对于11.0592MHz时钟, unsigned int型的i, 作一个i++操作的时间大于为8us
82
unsigned int i;
83
ds = 0;
84
i = 100; //拉低约800us, 符合协议要求的480us以上
85
while(i>0) i--;
86
ds = 1; //产生一个上升沿, 进入等待应答状态
87
i = 4;
88
while(i>0) i--;
89
}
90
91
void dsWait()
92

{
93
unsigned int i;
94
while(ds);
95
while(~ds); //检测到应答脉冲
96
i = 4;
97
while(i > 0) i--;
98
}
99
100
//向DS18B20读取一位数据
101
//读一位, 让DS18B20一小周期低电平, 然后两小周期高电平,
102
//之后DS18B20则会输出持续一段时间的一位数据
103
bit readBit()
104

{
105
unsigned int i;
106
bit b;
107
ds = 0;
108
i++; //延时约8us, 符合协议要求至少保持1us
109
ds = 1;
110
i++; i++; //延时约16us, 符合协议要求的至少延时15us以上
111
b = ds;
112
i = 8;
113
while(i>0) i--; //延时约64us, 符合读时隙不低于60us要求
114
return b;
115
}
116
117
//读取一字节数据, 通过调用readBit()来实现
118
unsigned char readByte()
119

{
120
unsigned int i;
121
unsigned char j, dat;
122
dat = 0;
123
for(i=0; i<8; i++)
124
{
125
j = readBit();
126
//最先读出的是最低位数据
127
dat = (j << 7) | (dat >> 1);
128
}
129
return dat;
130
}
131
132
//向DS18B20写入一字节数据
133
void writeByte(unsigned char dat)
134

{
135
unsigned int i;
136
unsigned char j;
137
bit b;
138
for(j = 0; j < 8; j++)
139
{
140
b = dat & 0x01;
141
dat >>= 1;
142
//写"1", 将DQ拉低15us后, 在15us~60us内将DQ拉高, 即完成写1
143
if(b)
144
{
145
ds = 0;
146
i++; i++; //拉低约16us, 符号要求15~60us内
147
ds = 1;
148
i = 8; while(i>0) i--; //延时约64us, 符合写时隙不低于60us要求
149
}
150
else //写"0", 将DQ拉低60us~120us
151
{
152
ds = 0;
153
i = 8; while(i>0) i--; //拉低约64us, 符号要求
154
ds = 1;
155
i++; i++; //整个写0时隙过程已经超过60us, 这里就不用像写1那样, 再延时64us了
156
}
157
}
158
}
159
160
//向DS18B20发送温度转换命令
161
void sendChangeCmd()
162

{
163
dsInit(); //初始化DS18B20, 无论什么命令, 首先都要发起初始化
164
dsWait(); //等待DS18B20应答
165
delay(1); //延时1ms, 因为DS18B20会拉低DQ 60~240us作为应答信号
166
writeByte(0xcc); //写入跳过序列号命令字 Skip Rom
167
writeByte(0x44); //写入温度转换命令字 Convert T
168
}
169
170
//向DS18B20发送读取数据命令
171
void sendReadCmd()
172

{
173
dsInit();
174
dsWait();
175
delay(1);
176
writeByte(0xcc); //写入跳过序列号命令字 Skip Rom
177
writeByte(0xbe); //写入读取数据令字 Read Scratchpad
178
}
179
180
//获取当前温度值
181
void getTempValue()
182

{
183
unsigned int tmpvalue;
184
float t;
185
unsigned char low, high;
186
sendReadCmd();
187
//连续读取两个字节数据
188
low = readByte();
189
high = readByte();
190
//将高低两个字节合成一个整形变量
191
//计算机中对于负数是利用补码来表示的
192
//若是负值, 读取出来的数值是用补码表示的, 可直接赋值给int型的value
193
tmpvalue = high;
194
tmpvalue <<= 8;
195
tmpvalue |= low;
196
tempValue = tmpvalue;
197
198
//使用DS18B20的默认分辨率12位, 精确度为0.0625度, 即读回数据的最低位代表0.0625度
199
t = tempValue * 0.0625;
200
//将它放大100倍, 使显示时可显示小数点后两位, 并对小数点后第三进行4舍5入
201
//如t=11.0625, 进行计数后, 得到value = 1106, 即11.06 度
202
//如t=-11.0625, 进行计数后, 得到value = -1106, 即-11.06 度
203
tempValue = t * 100 + (tempValue > 0 ? 0.5 : -0.5); //大于0加0.5, 小于0减0.5
204
}
205
206
//显示当前温度值, 精确到小数点后一位
207
//若先位选再段选, 由于IO口默认输出高电平, 所以当先位选会使数码管出现乱码
208
void displayTemp()
209

{
210
unsigned char i;
211
unsigned int tmp = abs(tempValue);
212
tmpDatas[0] = tmp / 10000;
213
tmpDatas[1] = tmp % 10000 / 1000;
214
tmpDatas[2] = tmp % 1000 / 100;
215
tmpDatas[3] = tmp % 100 / 10;
216
tmpDatas[4] = tmp % 10;
217
if(tempValue < 0)
218
{
219
//关位选, 去除对上一位的影响
220
P0 = 0xff;
221
wela = 1; //打开锁存, 给它一个下降沿量
222
wela = 0;
223
//段选
224
P0 = 0x40; //显示"-"号
225
dula = 1; //打开锁存, 给它一个下降沿量
226
dula = 0;
227
228
//位选
229
P0 = 0xfe;
230
wela = 1; //打开锁存, 给它一个下降沿量
231
wela = 0;
232
delay(1);
233
}
234
for(i = 0; i != 5; i++)
235
{
236
//关位选, 去除对上一位的影响
237
P0 = 0xff;
238
wela = 1; //打开锁存, 给它一个下降沿量
239
wela = 0;
240
//段选
241
if(i != 2)
242
{
243
P0 = table[tmpDatas[i]]; //显示数字
244
}
245
else
246
{
247
P0 = tableWidthDot[tmpDatas[i]]; //显示带小数点数字
248
}
249
dula = 1; //打开锁存, 给它一个下降沿量
250
dula = 0;
251
252
//位选
253
P0 = _crol_(0xfd, i); //选择第(i + 1) 个数码管
254
wela = 1; //打开锁存, 给它一个下降沿量
255
wela = 0;
256
delay(1);
257
}
258
tempCount++;
259
if(tempCount==100)
260
{
261
tempCount = 0;
262
getTempValue();
263
sendChangeCmd();
264
}
265
}
266
267
//显示时钟和秒表的结果
268
void display()
269

{
270
unsigned char i;
271
if(mode == 4) //显示温度
272
{
273
displayTemp();
274
}
275
else
{ //显示时间
276
for(i = 0; i < 6; i++)
277
{
278
//关位选, 去除对上一位的影响
279
P0 = 0xff;
280
wela = 1; //打开锁存, 给它一个下降沿量
281
wela = 0;
282
//段选
283
if(i == 2 || i == 4)
284
{
285
P0 = tableWidthDot[values[i]]; //显示带小数点数字, 作为分秒, 秒与毫秒的分隔
286
}
287
else
288
{
289
P0 = table[values[i]]; //显示数字
290
}
291
if(mode == 2 && i == clockPosition) //时钟设置模式下, 光标所在位置, 闪烁
292
{
293
clockTmp++;
294
if(clockTmp == 20)
295
{
296
clockTmpBit = ~clockTmpBit;
297
clockTmp = 0;
298
}
299
if(clockTmpBit)
300
P0 = 0x00;
301
}
302
dula = 1; //打开锁存, 给它一个下降沿量
303
dula = 0;
304
305
//位选
306
P0 = _cror_(0xdf, i); //选择第(i + 1) 个数码管
307
wela = 1; //打开锁存, 给它一个下降沿量
308
wela = 0;
309
delay(1);
310
}
311
if(mode == 2 && 6 == clockPosition) //时钟设置模式下, 光标所在位置, 闪烁
312
{
313
clockTmp++;
314
if(clockTmp == 20)
315
{
316
clockTmpBit = ~clockTmpBit;
317
clockTmp = 0;
318
}
319
if(clockTmpBit)
320
{
321
if(values[6] == 0)
322
{
323
P1 = 0x0f;
324
}
325
else
326
{
327
P1 = ~values[6];
328
}
329
}
330
else
331
{
332
P1 = 0xff;
333
}
334
}
335
else
336
{
337
P1 = ~values[6];
338
}
339
}
340
}
341
342
//检测功能模式键是否被按下
343
void checkModeKey()
344

{
345
if(!S5)
346
{
347
delay(7);
348
if(!S5)
349
{
350
mode++;
351
switch(mode)
352
{
353
case 2: //定时器0是不会关闭的, 即使在设置模式下
354
for(icount = 0; icount < 7; icount++)
355
{
356
tmpDatas[icount] = clockDatas[icount]; //将设置之前的值暂存到临时数组
357
}
358
values = tmpDatas;
359
break;
360
case 3: //秒表模式
361
values = datas;
362
break;
363
case 4: //温度计模式
364
//启动温度转换
365
sendChangeCmd();
366
getTempValue();
367
break;
368
default:
369
values = clockDatas;
370
mode = 1;
371
TR0 = 1;
372
}
373
while(!S5) //等待释放键
374
{
375
display();
376
}
377
}
378
}
379
}
380
381
//选中位数值增1, 用于时钟模式的设置状态下
382
void add()
383

{
384
values[clockPosition]++;
385
switch(clockPosition)
386
{
387
case 3:
388
case 5:
389
if(values[clockPosition] > 5)
390
values[clockPosition] = 0;
391
break;
392
case 6:
393
if(values[clockPosition] > 23)
394
values[clockPosition] = 0;
395
break;
396
default:
397
if(values[clockPosition] > 9)
398
values[clockPosition] = 0;
399
}
400
}
401
402
//检测是否有键按下, 并执行相应功能
403
void checkKey()
404

{
405
checkModeKey();
406
switch(mode)
407
{
408
case 2:
409
if(!S2) //左移光标位
410
{
411
delay(7);
412
if(!S2)
413
{
414
clockPosition++;
415
if(clockPosition > 6)
416
{
417
clockPosition = 0;
418
}
419
}
420
}
421
else if(!S3) //选中位增1
422
{
423
delay(7);
424
if(!S3)
425
{
426
add();
427
}
428
}
429
else if(!S4) //选中确定更改
430
{
431
delay(7);
432
if(!S4)
433
{
434
for(icount = 0; icount < 7; icount++)
435
{
436
clockDatas[icount] = tmpDatas[icount]; //将设置的值存到clockDatas[]
437
}
438
values = clockDatas;
439
mode = 1; //将模式变成时钟模式
440
}
441
}
442
break;
443
case 3:
444
if(!S2)
445
{
446
delay(7); //延时大约10ms, 去抖动
447
if(!S2) //开始/暂停键按下
448
{
449
TR1 = ~TR1;
450
}
451
}
452
453
else if(!S3)
454
{
455
delay(7); //延时大约10ms, 去抖动
456
if(!S3) //清零键按下
457
{
458
TR1 = 0;
459
TH1 = th;
460
TL1 = tl;
461
for(icount = 0; icount < 7; icount++)
462
{
463
datas[icount] = 0;
464
}
465
}
466
}
467
break;
468
}
469
//等待键被释放
470
while(!S2 || !S3 || !S4)
471
{
472
display(); // 等待期间要显示
473
}
474
}
475
476
void main()
477

{
478
delay(1);
479
th = 0xdb;//(65536 - 10000/1.085) / 256; //定时10ms
480
tl = 0xff;//(65536 - 10000/1.085) - th * 256;
481
TH1 = TH0 = th; //初始化定时器1, 0
482
TL1 = TL0 = tl;
483
EA = 1; //开中断
484
ET1 = ET0 = 1; //允许定时器1, 0 中断请求
485
TMOD = 0x11; //定时器1, 0 都工作方式1
486
TR1 = 0;
487
TR0 = 1; //开始显示时间
488
while(1)
489
{
490
checkKey(); //检测是否就键按下
491
display(); //显示计时值
492
}
493
}
494
495
//定时器0中断响应函数, 用于时钟计时
496
void time0() interrupt 1
497

{
498
TH0 = th; //重置计数值
499
TL0 = tl;
500
501
clockDatas[0]++; //0.01秒
502
if(clockDatas[0] == 10)
503
{
504
clockDatas[0] = 0;
505
clockDatas[1]++; //0.1秒
506
if(clockDatas[1] == 10)
507
{
508
clockDatas[1] = 0;
509
clockDatas[2]++; //秒
510
if(clockDatas[2] == 10)
511
{
512
clockDatas[2] = 0;
513
clockDatas[3]++; //10秒
514
if(clockDatas[3] == 6)
515
{
516
clockDatas[3] = 0;
517
clockDatas[4]++; //分
518
if(clockDatas[4] == 10)
519
{
520
clockDatas[4] = 0;
521
clockDatas[5]++; //10分
522
if(clockDatas[5] == 6)
523
{
524
clockDatas[5] = 0;
525
clockDatas[6]++; //时
526
if(clockDatas[6] == 24)
527
{
528
clockDatas[6] = 0;
529
}
530
}
531
}
532
}
533
}
534
}
535
}
536
}
537
538
//定时器1中断响应函数, 用于秒表计时
539
void time1() interrupt 3
540

{
541
TH1 = th; //重置计数值
542
TL1 = tl;
543
544
datas[0]++; //0.01秒
545
if(datas[0] == 10)
546
{
547
datas[0] = 0;
548
datas[1]++; //0.1秒
549
if(datas[1] == 10)
550
{
551
datas[1] = 0;
552
datas[2]++; //秒
553
if(datas[2] == 10)
554
{
555
datas[2] = 0;
556
datas[3]++; //10秒
557
if(datas[3] == 6)
558
{
559
datas[3] = 0;
560
datas[4]++; //分
561
if(datas[4] == 10)
562
{
563
datas[4] = 0;
564
datas[5]++; //10分
565
if(datas[5] == 6)
566
{
567
datas[5] = 0;
568
datas[6]++; //时
569
if(datas[6] == 24)
570
{
571
datas[6] = 0;
572
}
573
}
574
}
575
}
576
}
577
}
578
}
579
}
效果图:
时钟模式: 当前时间为11: 52: 56,98

秒表: 已计时34分13.88秒

温度计: 当前室内温度

来源:http://www.cnblogs.com/fengmk2/archive/2007/03/15/676604.html