单片机综合练习 - 多功能时钟
结合前几天来写过的文章, 今天总算写了一个功能较多的应用 - 多功能时钟, 集时钟, 秒表, 温度计一体.
基础文章:
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/**//*
5S5键为功能选择键, 上电默认使用时钟功能
6功能顺序为: 时钟, 温度计, 秒表
7
8mode = 1. 时钟(每次掉电后都要重新设置时间)
91)当选中时钟功能时, 具体按键功能如下:
10
112)可设置时分秒, 时利用发光二极管显示, 分秒用数码管显示
12
133)时钟: 采用定时器0计时, 工作方式1
14
15mode = 2. 时钟设置模式
16当选中时钟设置模式
17S2为位选, S3为增加选中位的值
18S4确定更改, S5放弃更改, 进入秒表模式
19
20mode = 3. 秒表
211)当选中秒表功能时, 具体按键功能如下:
22S2为开始/暂停, S3为清零
23
242)采用定时器1计时, 工作方式1
25
26mode = 4. 温度计
271)利用DS18B20检测环境温度;
282)最小温度值为0.01℃, 可表示温度范围: -55℃~+125℃
29
30*/
31
32#include <reg52.H>
33#include <intrins.H>
34#include <math.h>
35
36//0-F数码管的编码(共阴极)
37unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,
38 0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
39//0-9数码管的编码(共阴极), 带小数点
40unsigned char code tableWidthDot[]={0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd,
41 0x87, 0xff, 0xef};
42
43sbit wela = P2^7; //数码管位选
44sbit dula = P2^6; //数码管段选
45sbit ds = P2^2;
46unsigned char th, tl, mode = 1; //mode存放功能模式, 默认在模式1 时钟
47unsigned char clockPosition = 0; //时钟设置模式下, 光标所在的位置; 默认在0
48unsigned char clockTmp = 0; //用于时钟模式下临时计数
49bit clockTmpBit = 0; //用于时钟模式下临时标志位
50
51//秒4字节, 分2字节, 时1字节
52unsigned char datas[] = {0, 0, 0, 0, 0, 0, 0};//保存计时器数据
53unsigned char clockDatas[] = {0, 0, 0, 0, 0, 0, 0};//保存时钟数据
54unsigned char * values = clockDatas; //根据mode选择适当的数据数组指针
55int tempValue; //存放温度值
56unsigned char tempCount = 0; //用于记录显示了多少次温度值, 用于定时
57
58sbit S2 = P3^4; //键S2, 作开始/暂停
59sbit S3 = P3^5; //键S3, 清零
60sbit S4 = P3^6; //键S4
61sbit S5 = P3^7; //键S5
62unsigned char tmpDatas[] = {0, 0, 0, 0, 0, 0, 0}; //存放临时设置值
63unsigned char icount;
64
65//延时函数, 对于11.0592MHz时钟, 例i=5,则大概延时5ms.
66void 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一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动
79void 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
91void 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则会输出持续一段时间的一位数据
103bit 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()来实现
118unsigned 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写入一字节数据
133void 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发送温度转换命令
161void 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发送读取数据命令
171void sendReadCmd()
172{
173 dsInit();
174 dsWait();
175 delay(1);
176 writeByte(0xcc); //写入跳过序列号命令字 Skip Rom
177 writeByte(0xbe); //写入读取数据令字 Read Scratchpad
178}
179
180//获取当前温度值
181void 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口默认输出高电平, 所以当先位选会使数码管出现乱码
208void 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//显示时钟和秒表的结果
268void 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//检测功能模式键是否被按下
343void 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, 用于时钟模式的设置状态下
382void 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//检测是否有键按下, 并执行相应功能
403void 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
476void 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中断响应函数, 用于时钟计时
496void 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中断响应函数, 用于秒表计时
539void 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}
1//多功能时钟, 精确到小数0.01秒, 即10ms
2//功能: 时钟, 秒表, 温度计
3
4/**//*
5S5键为功能选择键, 上电默认使用时钟功能
6功能顺序为: 时钟, 温度计, 秒表
7
8mode = 1. 时钟(每次掉电后都要重新设置时间)
91)当选中时钟功能时, 具体按键功能如下:
10
112)可设置时分秒, 时利用发光二极管显示, 分秒用数码管显示
12
133)时钟: 采用定时器0计时, 工作方式1
14
15mode = 2. 时钟设置模式
16当选中时钟设置模式
17S2为位选, S3为增加选中位的值
18S4确定更改, S5放弃更改, 进入秒表模式
19
20mode = 3. 秒表
211)当选中秒表功能时, 具体按键功能如下:
22S2为开始/暂停, S3为清零
23
242)采用定时器1计时, 工作方式1
25
26mode = 4. 温度计
271)利用DS18B20检测环境温度;
282)最小温度值为0.01℃, 可表示温度范围: -55℃~+125℃
29
30*/
31
32#include <reg52.H>
33#include <intrins.H>
34#include <math.h>
35
36//0-F数码管的编码(共阴极)
37unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,
38 0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
39//0-9数码管的编码(共阴极), 带小数点
40unsigned char code tableWidthDot[]={0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd,
41 0x87, 0xff, 0xef};
42
43sbit wela = P2^7; //数码管位选
44sbit dula = P2^6; //数码管段选
45sbit ds = P2^2;
46unsigned char th, tl, mode = 1; //mode存放功能模式, 默认在模式1 时钟
47unsigned char clockPosition = 0; //时钟设置模式下, 光标所在的位置; 默认在0
48unsigned char clockTmp = 0; //用于时钟模式下临时计数
49bit clockTmpBit = 0; //用于时钟模式下临时标志位
50
51//秒4字节, 分2字节, 时1字节
52unsigned char datas[] = {0, 0, 0, 0, 0, 0, 0};//保存计时器数据
53unsigned char clockDatas[] = {0, 0, 0, 0, 0, 0, 0};//保存时钟数据
54unsigned char * values = clockDatas; //根据mode选择适当的数据数组指针
55int tempValue; //存放温度值
56unsigned char tempCount = 0; //用于记录显示了多少次温度值, 用于定时
57
58sbit S2 = P3^4; //键S2, 作开始/暂停
59sbit S3 = P3^5; //键S3, 清零
60sbit S4 = P3^6; //键S4
61sbit S5 = P3^7; //键S5
62unsigned char tmpDatas[] = {0, 0, 0, 0, 0, 0, 0}; //存放临时设置值
63unsigned char icount;
64
65//延时函数, 对于11.0592MHz时钟, 例i=5,则大概延时5ms.
66void 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一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动
79void 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
91void 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则会输出持续一段时间的一位数据
103bit 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()来实现
118unsigned 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写入一字节数据
133void 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发送温度转换命令
161void 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发送读取数据命令
171void sendReadCmd()
172{
173 dsInit();
174 dsWait();
175 delay(1);
176 writeByte(0xcc); //写入跳过序列号命令字 Skip Rom
177 writeByte(0xbe); //写入读取数据令字 Read Scratchpad
178}
179
180//获取当前温度值
181void 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口默认输出高电平, 所以当先位选会使数码管出现乱码
208void 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//显示时钟和秒表的结果
268void 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//检测功能模式键是否被按下
343void 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, 用于时钟模式的设置状态下
382void 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//检测是否有键按下, 并执行相应功能
403void 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
476void 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中断响应函数, 用于时钟计时
496void 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中断响应函数, 用于秒表计时
539void 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秒
温度计: 当前室内温度