单片机综合练习 - 多功能时钟
结合前几天来写过的文章, 今天总算写了一个功能较多的应用 - 多功能时钟, 集时钟, 秒表, 温度计一体.
基础文章:
1. 单片机练习 - DS18B20温度转换与显示
2. 用C51编写单片机延时函数
3. 单片机练习 - 定时器
4. 单片机练习 - 计时器
实验板: TX-1B实验板
6位数码管与单片机的连接电路图
按键S2, S3与单片机的连接电路图: 其中S2与P3.4连, S3与P3.5连接...
DS18B20与单片机连接电路图:
具体按键功能分配请看源代码注释部分:
1
//多功能时钟, 精确到小数0.01秒, 即10ms2
//功能: 时钟, 秒表, 温度计3

4

/**//*5
S5键为功能选择键, 上电默认使用时钟功能6
功能顺序为: 时钟, 温度计, 秒表7

8
mode = 1. 时钟(每次掉电后都要重新设置时间)9
1)当选中时钟功能时, 具体按键功能如下:10

11
2)可设置时分秒, 时利用发光二极管显示, 分秒用数码管显示12

13
3)时钟: 采用定时器0计时, 工作方式114

15
mode = 2. 时钟设置模式16
当选中时钟设置模式17
S2为位选, S3为增加选中位的值18
S4确定更改, S5放弃更改, 进入秒表模式19

20
mode = 3. 秒表21
1)当选中秒表功能时, 具体按键功能如下:22
S2为开始/暂停, S3为清零23

24
2)采用定时器1计时, 工作方式125

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; //时钟设置模式下, 光标所在的位置; 默认在048
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; //键S461
sbit S5 = P3^7; //键S562

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
//初始化DS18B2078
//让DS18B20一段相对长时间低电平, 然后一段相对非常短时间高电平, 即可启动79
void dsInit()80


{81
//对于11.0592MHz时钟, unsigned int型的i, 作一个i++操作的时间大于为8us82
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, 符合协议要求至少保持1us109
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拉高, 即完成写1143
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~120us151

{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 Rom167
writeByte(0x44); //写入温度转换命令字 Convert T168
}169

170
//向DS18B20发送读取数据命令171
void sendReadCmd()172


{173
dsInit();174
dsWait();175
delay(1);176
writeByte(0xcc); //写入跳过序列号命令字 Skip Rom177
writeByte(0xbe); //写入读取数据令字 Read Scratchpad178
}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型的value193
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.5204
}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
else246

{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
else288

{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
else326

{327
P1 = ~values[6];328
}329
}330
else331

{332
P1 = 0xff;333
}334
}335
else336

{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) //选中位增1422

{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; //定时10ms480
tl = 0xff;//(65536 - 10000/1.085) - th * 256;481
TH1 = TH0 = th; //初始化定时器1, 0482
TL1 = TL0 = tl;483
EA = 1; //开中断484
ET1 = ET0 = 1; //允许定时器1, 0 中断请求485
TMOD = 0x11; //定时器1, 0 都工作方式1486
TR1 = 0;487
TR0 = 1; //开始显示时间488
while(1)489

{490
checkKey(); //检测是否就键按下491
display(); //显示计时值492
}493
}494

495
//定时器0中断响应函数, 用于时钟计时496
void time0() interrupt 1497


{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 3540


{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秒
温度计: 当前室内温度


浙公网安备 33010602011771号