本节小项目,意在“人机界面”与“过程控制”如何关联的练习。 程序功能如下: (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
678 void PeripheralInitial(void)