本节小项目,意在“人机界面”与“过程控制”如何关联的练习。
程序功能如下:
(1)数码管显示的格式是“S.D.CC”。其中S是代表3档速度,能显示的数字范围是“1、2、3”,分别代表“慢、中、快”3档速度。D代表方向,往右跑显示符号“r”(right的首字母),往左跑显示符号“L”(Left的首字母)。CC代表计数器,跑马灯每跑完一次,计数器自动加1,范围是0到99。
(2)【速度】按键K1。每按一次【速度】按键K1,速度档位显示的数字在“1、2、3”之间切换。
(3)【方向】按键K2。跑马灯上电后默认处于“往右跑”的方向,默认显示字符“r”。每按一次【方向】按键K2,跑马灯就在“往右跑”与“往左跑”两个方向之间切换,显示的字符在“r、L”之间切换。
(4)【启动暂停】按键K3。上电后,按下【启动暂停】按键K3启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉,此时再按一次【启动暂停】按键K3,则跑马灯处于“暂停”状态,接着又按一次【启动暂停】按键K3,跑马灯又变回“启动”状态。因此,【启动暂停】按键K3是专门用来切换“启动”和“暂停”这两种状态。
代码如下:
1#include "REG52.H" 2 3#define KEY_FILTER_TIME 25 4 5#define SCAN_TIME 1 6#define VOICE_TIME 50 7 8#define RUN_TIME_SLOW 500 //“慢”档速度的时间参数 9#define RUN_TIME_MIDDLE 300 //“中”档速度的时间参数 10#define RUN_TIME_FAST 100 //“快”档速度的时间参数 11 12void T0_time(); 13void SystemInitial(void) ; 14void Delay(unsigned long u32DelayTime) ; 15void PeripheralInitial(void) ; 16 17void KeyScan(void); 18void KeyTask(void); 19void RunTask(void); //跑马灯的任务函数 20 21void VoiceScan(void); 22void DisplayScan(void); 23void DisplayTask(void); 24void Wd1(void); //窗口1。 25void BeepOpen(void); 26void BeepClose(void); 27 28sbit KEY_INPUT1 = P2 ^ 2; 29sbit KEY_INPUT2 = P2 ^ 1; 30sbit KEY_INPUT3 = P2 ^ 0; 31 32sbit P1_0 = P1 ^ 0; 33sbit P1_1 = P1 ^ 1; 34sbit P1_2 = P1 ^ 2; 35sbit P1_3 = P1 ^ 3; 36 37sbit P3_4 = P3 ^ 4; 38 39//4个跑马灯的输出口 40sbit P1_4 = P1 ^ 4; 41sbit P1_5 = P1 ^ 5; 42sbit P1_6 = P1 ^ 6; 43sbit P3_3 = P3 ^ 3; 44 45 46//数码管转换表 47code unsigned char Cu8DigTable[] = 48{ 49 0x3f, //0 序号0 50 0x06, //1 序号1 51 0x5b, //2 序号2 52 0x4f, //3 序号3 53 0x66, //4 序号4 54 0x6d, //5 序号5 55 0x7d, //6 序号6 56 0x07, //7 序号7 57 0x7f, //8 序号8 58 0x6f, //9 序号9 59 0x00, //不显示 序号10 60 0x40, //横杠- 序号11 61 0x38, //字符L 序号12 62 0x70, //字符r 序号13 63}; 64 65volatile unsigned char vGu8ScanTimerFlag = 0; 66volatile unsigned int vGu16ScanTimerCnt = 0; 67 68volatile unsigned char vGu8BeepTimerFlag = 0; 69volatile unsigned int vGu16BeepTimerCnt = 0; 70 71unsigned char Gu8Wd = 0; //窗口选择变量。人机交互程序框架的支点。 72unsigned char Gu8WdUpdate = 0; //整屏更新变量。 73 74unsigned char Gu8PartUpdate_1 = 0; //局部1的更新变量, 75unsigned char Gu8PartUpdate_2 = 0; //局部2的更新变量 76unsigned char Gu8PartUpdate_3 = 0; //局部3的更新变量, 77 78volatile unsigned char vGu8Display_Righ_4 = 0; 79volatile unsigned char vGu8Display_Righ_3 = 0; 80volatile unsigned char vGu8Display_Righ_2 = 0; 81volatile unsigned char vGu8Display_Righ_1 = 0; 82 83volatile unsigned char vGu8Display_Righ_Dot_4 = 1; //需要显示的小数点 84volatile unsigned char vGu8Display_Righ_Dot_3 = 1; //需要显示的小数点 85volatile unsigned char vGu8Display_Righ_Dot_2 = 0; 86volatile unsigned char vGu8Display_Righ_Dot_1 = 0; 87 88volatile unsigned char vGu8KeySec = 0; 89 90unsigned char Gu8RunCounter = 0; //计数器,范围是0到99 91 92unsigned char Gu8RunStep = 0; //运行的步骤 93unsigned char Gu8RunStart = 0; //控制跑马灯启动的总开关 94 95unsigned char Gu8RunStatus = 0; //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。 96unsigned char Gu8RunDirection = 0; //标识跑马灯当前的方向。0代表往右跑,1代表往左跑。 97unsigned char Gu8RunSpeed = 1; //当前的速度档位。1代表“慢”,2代表“中”,3代表“快”。 98unsigned int Gu16RunSpeedTimeDate = 0; //承接各速度档位的时间参数的变量 99 100volatile unsigned char vGu8RunTimerFlag = 0; //用于控制跑马灯跑动速度的定时器 101volatile unsigned int vGu16RunTimerCnt = 0; 102 103void main() 104{ 105 SystemInitial(); 106 Delay(10000); 107 PeripheralInitial(); 108 109 while(1) 110 { 111 KeyTask(); //按键的任务函数 112 DisplayTask(); //数码管显示的上层任务函数 113 RunTask(); //跑马灯的任务函数 114 } 115} 116 117void RunTask(void) //跑马灯的任务函数,放在主函数内 118{ 119 if(0 == Gu8RunStart) //如果是停止的状态 120 { 121 return; //如果是停止的状态,退出当前函数,不扫描余下代码。 122 } 123 124 switch(Gu8RunStep) //屡见屡爱的switch又来了 125 { 126 case 0: 127 vGu8RunTimerFlag = 0; 128 vGu16RunTimerCnt = 0; //定时器清零 129 Gu8RunStep = 1; //切换到下一步,启动 130 131 break; 132 133 case 1: 134 if(1 == Gu8RunStatus && 0 == vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0 135 { 136 P1_4 = 0; //第1个灯亮 137 P1_5 = 1; //第2个灯灭 138 P1_6 = 1; //第3个灯灭 139 P3_3 = 1; //第4个灯灭 140 141 vGu8RunTimerFlag = 0; 142 vGu16RunTimerCnt = Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度 143 vGu8RunTimerFlag = 1; //启动定时器 144 145//灵活切换“步骤变量” 146 if(0 == Gu8RunDirection) //往右跑 147 { 148 Gu8RunStep = 2; 149 } 150 else //往左跑 151 { 152 if(Gu8RunCounter < 99) 153 { 154 Gu8RunCounter++; //往左边跑完一次,运行的计数器自加1 155 } 156 157 Gu8PartUpdate_3 = 1; //局部3的更新变量,更新显示计数器 158 159 Gu8RunStep = 4; 160 } 161 162 } 163 164 break; 165 166 case 2: 167 if(1 == Gu8RunStatus && 0 == vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0 168 { 169 P1_4 = 1; //第1个灯灭 170 P1_5 = 0; //第2个灯亮 171 P1_6 = 1; //第3个灯灭 172 P3_3 = 1; //第4个灯灭 173 174 vGu8RunTimerFlag = 0; 175 vGu16RunTimerCnt = Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度 176 vGu8RunTimerFlag = 1; //启动定时器 177 178//灵活切换“步骤变量” 179 if(0 == Gu8RunDirection) //往右跑 180 { 181 Gu8RunStep = 3; 182 } 183 else //往左跑 184 { 185 Gu8RunStep = 1; 186 } 187 } 188 189 break; 190 191 case 3: 192 if(1 == Gu8RunStatus && 0 == vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0 193 { 194 P1_4 = 1; //第1个灯灭 195 P1_5 = 1; //第2个灯灭 196 P1_6 = 0; //第3个灯亮 197 P3_3 = 1; //第4个灯灭 198 199 vGu8RunTimerFlag = 0; 200 vGu16RunTimerCnt = Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度 201 vGu8RunTimerFlag = 1; //启动定时器 202 203//灵活切换“步骤变量” 204 if(0 == Gu8RunDirection) //往右跑 205 { 206 Gu8RunStep = 4; 207 } 208 else //往左跑 209 { 210 Gu8RunStep = 2; 211 } 212 } 213 214 break; 215 216 case 4: 217 if(1 == Gu8RunStatus && 0 == vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0 218 { 219 P1_4 = 1; //第1个灯灭 220 P1_5 = 1; //第2个灯灭 221 P1_6 = 1; //第3个灯灭 222 P3_3 = 0; //第4个灯亮 223 224 vGu8RunTimerFlag = 0; 225 vGu16RunTimerCnt = Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度 226 vGu8RunTimerFlag = 1; //启动定时器 227 228//灵活切换“步骤变量” 229 if(0 == Gu8RunDirection) //往右跑 230 { 231 if(Gu8RunCounter < 99) 232 { 233 Gu8RunCounter++; //往右边跑完一次,运行的计数器自加1 234 } 235 236 Gu8PartUpdate_3 = 1; //局部3的更新变量,更新显示计数器 237 238 Gu8RunStep = 1; 239 } 240 else //往左跑 241 { 242 Gu8RunStep = 3; 243 } 244 } 245 246 break; 247 248 } 249 250} 251 252void KeyTask(void) //按键的任务函数 253{ 254 if(0 == vGu8KeySec) 255 { 256 return; 257 } 258 259 switch(vGu8KeySec) 260 { 261 case 1: //【速度】按键K1 262 switch(Gu8Wd) //在某个窗口下 263 { 264 case 1: //窗口1。 265 266 //每按一次K1按键,Gu8RunSpeed就在1、2、3三者之间切换, 267//并且根据Gu8RunSpeed的数值,对Gu16RunSpeedTimeDate赋值 268//不同的速度时间参数,从而控制速度档位。 269 270 if(1 == Gu8RunSpeed) 271 { 272 Gu8RunSpeed = 2; //“中”档 273 Gu16RunSpeedTimeDate = RUN_TIME_MIDDLE; //赋值“中”档的时间参数 274 } 275 else if(2 == Gu8RunSpeed) 276 { 277 Gu8RunSpeed = 3; //“快”档 278 Gu16RunSpeedTimeDate = RUN_TIME_FAST; //赋值“快”档的时间参数 279 } 280 else 281 { 282 Gu8RunSpeed = 1; //“慢”档 283 Gu16RunSpeedTimeDate = RUN_TIME_SLOW; //赋值“慢”档的时间参数 284 } 285 286 Gu8PartUpdate_1 = 1; //局部1的更新变量,更新显示“速度” 287 288 vGu8BeepTimerFlag = 0; 289 vGu16BeepTimerCnt = VOICE_TIME; //蜂鸣器发出“滴”一声 290 vGu8BeepTimerFlag = 1; 291 break; 292 } 293 294 vGu8KeySec = 0; 295 break; 296 297 case 2: //【方向】按键K2 298 299 switch(Gu8Wd) //在某个窗口下 300 { 301 case 1: //窗口1。 302 303 //每按一次K2按键,Gu8RunDirection就在0和1之间切换,从而控制方向 304 if(0 == Gu8RunDirection) 305 { 306 Gu8RunDirection = 1; 307 } 308 else 309 { 310 Gu8RunDirection = 0; 311 } 312 313 Gu8PartUpdate_2 = 1; //局部2更新显示,更新显示“方向” 314 315 vGu8BeepTimerFlag = 0; 316 vGu16BeepTimerCnt = VOICE_TIME; //蜂鸣器发出“滴”一声 317 vGu8BeepTimerFlag = 1; 318 break; 319 } 320 321 vGu8KeySec = 0; 322 break; 323 324 case 3: //【启动暂停】按键K3 325 switch(Gu8Wd) //在某个窗口下 326 { 327 case 1: //窗口1。 328 if(0 == Gu8RunStatus) //当跑马灯处于“停止”状态时 329 { 330 Gu8RunStep = 0; //运行步骤从0开始 331 Gu8RunStart = 1; //总开关“打开”。 332 Gu8RunStatus = 1; //状态切换到“启动”状态 333 } 334 else if(1 == Gu8RunStatus) //当跑马灯处于“启动”状态时 335 { 336 Gu8RunStatus = 2; //状态切换到“暂停”状态 337 } 338 else //当跑马灯处于“暂停”状态时 339 { 340 Gu8RunStatus = 1; //状态切换到“启动”状态 341 } 342 343 vGu8BeepTimerFlag = 0; 344 vGu16BeepTimerCnt = VOICE_TIME; //蜂鸣器发出“滴”一声 345 vGu8BeepTimerFlag = 1; 346 break; 347 } 348 349 vGu8KeySec = 0; 350 break; 351 } 352} 353 354void DisplayTask(void) //数码管显示的上层任务函数 355{ 356 switch(Gu8Wd) //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句 357 { 358 case 1: 359 Wd1(); //窗口1。 360 break; 361 } 362} 363 364void Wd1(void) //窗口1。 365{ 366//需要借用的中间变量,用来拆分数据位。 367 static unsigned char Su8Temp_4, Su8Temp_3, Su8Temp_2, Su8Temp_1; //需要借用的中间变量 368 369 if(1 == Gu8WdUpdate) //如果需要整屏更新 370 { 371 Gu8WdUpdate = 0; //及时清零,只更新一次显示即可,避免一直进来更新显示 372 373//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。 374 vGu8Display_Righ_Dot_4 = 1; //显示小数点 375 vGu8Display_Righ_Dot_3 = 1; //显示小数点 376 vGu8Display_Righ_Dot_2 = 0; 377 vGu8Display_Righ_Dot_1 = 0; 378 379 Gu8PartUpdate_1 = 1; //局部1更新显示 380 Gu8PartUpdate_2 = 1; //局部2更新显示 381 Gu8PartUpdate_3 = 1; //局部3更新显示 382 } 383 384 if(1 == Gu8PartUpdate_1) //局部1更新显示,速度 385 { 386 Gu8PartUpdate_1 = 0; //及时清零,只更新一次显示即可,避免一直进来更新显示 387 388 Su8Temp_4 = Gu8RunSpeed; 389 390 vGu8Display_Righ_4 = Su8Temp_4; //过渡需要显示的数据到底层驱动变量 391 } 392 393 if(1 == Gu8PartUpdate_2) //局部2更新显示,方向 394 { 395 Gu8PartUpdate_2 = 0; //及时清零,只更新一次显示即可,避免一直进来更新显示 396 397 if(0 == Gu8RunDirection) //往右跑 398 { 399 Su8Temp_3 = 13; //数码管的字模转换表序号13代表显示字符“r” 400 } 401 else 402 { 403 Su8Temp_3 = 12; //数码管的字模转换表序号12代表显示字符“L” 404 } 405 406 vGu8Display_Righ_3 = Su8Temp_3; //过渡需要显示的数据到底层驱动变量 407 } 408 409 if(1 == Gu8PartUpdate_3) //局部3更新显示,计数器 410 { 411 Gu8PartUpdate_3 = 0; //及时清零,只更新一次显示即可,避免一直进来更新显示 412 413 Su8Temp_2 = Gu8RunCounter % 100 / 10; //提取十位 414 Su8Temp_1 = Gu8RunCounter % 10 / 1; //提取个位 415 416 vGu8Display_Righ_2 = Su8Temp_2; //过渡需要显示的数据到底层驱动变量 417 vGu8Display_Righ_1 = Su8Temp_1; //过渡需要显示的数据到底层驱动变量 418 } 419 420} 421 422 423void KeyScan(void) //按键底层的驱动扫描函数,放在定时中断函数里 424{ 425 static unsigned char Su8KeyLock1; 426 static unsigned int Su16KeyCnt1; 427 static unsigned char Su8KeyLock2; 428 static unsigned int Su16KeyCnt2; 429 static unsigned char Su8KeyLock3; 430 static unsigned int Su16KeyCnt3; 431 432 433 if(0 != KEY_INPUT1) 434 { 435 Su8KeyLock1 = 0; 436 Su16KeyCnt1 = 0; 437 } 438 else if(0 == Su8KeyLock1) 439 { 440 Su16KeyCnt1++; 441 442 if(Su16KeyCnt1 >= KEY_FILTER_TIME) 443 { 444 Su8KeyLock1 = 1; 445 vGu8KeySec = 1; 446 } 447 } 448 449 if(0 != KEY_INPUT2) 450 { 451 Su8KeyLock2 = 0; 452 Su16KeyCnt2 = 0; 453 } 454 else if(0 == Su8KeyLock2) 455 { 456 Su16KeyCnt2++; 457 458 if(Su16KeyCnt2 >= KEY_FILTER_TIME) 459 { 460 Su8KeyLock2 = 1; 461 vGu8KeySec = 2; 462 } 463 } 464 465 if(0 != KEY_INPUT3) 466 { 467 Su8KeyLock3 = 0; 468 Su16KeyCnt3 = 0; 469 } 470 else if(0 == Su8KeyLock3) 471 { 472 Su16KeyCnt3++; 473 474 if(Su16KeyCnt3 >= KEY_FILTER_TIME) 475 { 476 Su8KeyLock3 = 1; 477 vGu8KeySec = 3; 478 } 479 } 480 481} 482 483void DisplayScan(void) //数码管底层的驱动扫描函数,放在定时中断函数里 484{ 485 static unsigned char Su8GetCode; 486 static unsigned char Su8ScanStep = 1; 487 488 if(0 == vGu16ScanTimerCnt) 489 { 490 491 492 P0 = 0x00; 493 P1_0 = 1; 494 P1_1 = 1; 495 P1_2 = 1; 496 P1_3 = 1; 497 498 switch(Su8ScanStep) 499 { 500 case 1: 501 Su8GetCode = Cu8DigTable[vGu8Display_Righ_1]; 502 503 if(1 == vGu8Display_Righ_Dot_1) 504 { 505 Su8GetCode = Su8GetCode | 0x80; 506 } 507 508 P0 = Su8GetCode; 509 P1_0 = 0; 510 P1_1 = 1; 511 P1_2 = 1; 512 P1_3 = 1; 513 break; 514 515 case 2: 516 Su8GetCode = Cu8DigTable[vGu8Display_Righ_2]; 517 518 if(1 == vGu8Display_Righ_Dot_2) 519 { 520 Su8GetCode = Su8GetCode | 0x80; 521 } 522 523 P0 = Su8GetCode; 524 P1_0 = 1; 525 P1_1 = 0; 526 P1_2 = 1; 527 P1_3 = 1; 528 break; 529 530 case 3: 531 Su8GetCode = Cu8DigTable[vGu8Display_Righ_3]; 532 533 if(1 == vGu8Display_Righ_Dot_3) 534 { 535 Su8GetCode = Su8GetCode | 0x80; 536 } 537 538 P0 = Su8GetCode; 539 P1_0 = 1; 540 P1_1 = 1; 541 P1_2 = 0; 542 P1_3 = 1; 543 break; 544 545 case 4: 546 Su8GetCode = Cu8DigTable[vGu8Display_Righ_4]; 547 548 if(1 == vGu8Display_Righ_Dot_4) 549 { 550 Su8GetCode = Su8GetCode | 0x80; 551 } 552 553 P0 = Su8GetCode; 554 P1_0 = 1; 555 P1_1 = 1; 556 P1_2 = 1; 557 P1_3 = 0; 558 break; 559 560 } 561 562 Su8ScanStep++; 563 564 if(Su8ScanStep > 4) 565 { 566 Su8ScanStep = 1; 567 } 568 569 vGu8ScanTimerFlag = 0; 570 vGu16ScanTimerCnt = SCAN_TIME; 571 vGu8ScanTimerFlag = 1; 572 } 573} 574 575void VoiceScan(void) //蜂鸣器的驱动函数 576{ 577 578 static unsigned char Su8Lock = 0; 579 580 if(1 == vGu8BeepTimerFlag && vGu16BeepTimerCnt > 0) 581 { 582 if(0 == Su8Lock) 583 { 584 Su8Lock = 1; 585 BeepOpen(); 586 } 587 else 588 { 589 590 vGu16BeepTimerCnt--; 591 592 if(0 == vGu16BeepTimerCnt) 593 { 594 Su8Lock = 0; 595 BeepClose(); 596 } 597 598 } 599 } 600} 601 602void BeepOpen(void) 603{ 604 P3_4 = 0; 605} 606 607void BeepClose(void) 608{ 609 P3_4 = 1; 610} 611 612void T0_time() interrupt 1 613{ 614 VoiceScan(); //蜂鸣器的驱动函数 615 KeyScan(); //按键底层的驱动扫描函数 616 DisplayScan(); //数码管底层的驱动扫描函数 617 618 if(1 == vGu8ScanTimerFlag && vGu16ScanTimerCnt > 0) 619 { 620 vGu16ScanTimerCnt--; 621 } 622 623 if(1 == vGu8RunTimerFlag && vGu16RunTimerCnt > 0) //用于控制跑马灯跑动速度的定时器 624 { 625 vGu16RunTimerCnt--; 626 } 627 628 TH0 = 0xfd; //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms 629 TL0 = 0x40; //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms 630} 631 632void SystemInitial(void) 633{ 634 P0 = 0x00; 635 P1_0 = 1; 636 P1_1 = 1; 637 P1_2 = 1; 638 P1_3 = 1; 639 640 TMOD = 0x01; 641 TH0 = 0xfd; //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms 642 TL0 = 0x40; //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms 643 EA = 1; 644 ET0 = 1; 645 TR0 = 1; 646 647//上电初始化一些关键的数据 648 649 Gu8Wd = 1; //窗口1。开机默认处于正常工作的窗口 650 Gu8WdUpdate = 1; //整屏更新变量 651//跑马灯处于初始化的状态 652 P1_4 = 0; //第1个灯亮 653 P1_5 = 1; //第2个灯灭 654 P1_6 = 1; //第3个灯灭 655 P3_3 = 1; //第4个灯灭 656 657//根据当前的速度档位Gu8RunSpeed,来初始化速度时间参数Gu16RunSpeedTimeDate 658 if(1 == Gu8RunSpeed) 659 { 660 Gu16RunSpeedTimeDate = RUN_TIME_SLOW; //赋值“慢”档的时间参数 661 } 662 else if(2 == Gu8RunSpeed) 663 { 664 Gu16RunSpeedTimeDate = RUN_TIME_MIDDLE; //赋值“中”档的时间参数 665 } 666 else 667 { 668 Gu16RunSpeedTimeDate = RUN_TIME_FAST; //赋值“快”档的时间参数 669 } 670 671} 672 673void Delay(unsigned long u32DelayTime) 674{ 675 for(; u32DelayTime > 0; u32DelayTime--); 676} 677 678void PeripheralInitial(void) 679{ 680 681}