第9章 综合练习9.8
9.8数码管扫描函数算法改进在学习数码管动态扫描时为了方便理解程序写的细致一些引入了switch的用法随着编程能力与领悟能力的增强对于74HC138这种非常有规律的数字器件在编程上也可以改进一下逻辑算法让程序变的更简洁。这种逻辑算法通常不是靠学一下可以全部掌握的而是通过不断的编写程序以及研究他人程序的过程中一点点积累起来的。前边动态扫描刷新函数是这么写的P0 0xFF;switch (i){case 0: ADDR20; ADDR10; ADDR00; i; P0LedBuff[0]; break;case 1: ADDR20; ADDR10; ADDR01; i; P0LedBuff[1]; break;case 2: ADDR20; ADDR11; ADDR00; i; P0LedBuff[2]; break;case 3: ADDR20; ADDR11; ADDR01; i; P0LedBuff[3]; break;case 4: ADDR21; ADDR10; ADDR00; i; P0LedBuff[4]; break;case 5: ADDR21; ADDR10; ADDR01; i0; P0LedBuff[5]; break;default: break;}每一个case分支结构是相同的即改变ADDR20、改变索引i、取数据写入P0只要把case后的常量与ADDR20和LedBuff的下标对比就可以发现它们其实是相等的那么可以直接把常量值实际上就是i在改变前的值赋值给它们即可而不必写上6遍。还剩下一个i的操作它进行了5次相同的与一次归0操作那么很明显用和if判断就可以替代这些操作。下面就是据此改进后的代码P0 0xFF;P1 (P1 0xF8) | i;P0 LedBuff[i];if (i 5)i;elsei 0;P1 (P1 0xF8) | i;这行代码就利用了前面讲到的和|运算来将i的低3位直接赋值到P1口的低3位上而P0的赋值也只需要一行代码i的处理也很简单。这样代码是不是要简洁的多也巧妙的多功能与前面的switch是一样的同样可以实现动态显示刷新的功能。设计一个秒表程序程序中涉及到的知识点前边都讲过了包括了定时器、数码管、中断、按键等多个知识点。多知识点同时应用到一个程序中的小综合。此程序是一个“真正的”并且“实用的”秒表程序第一它有足够的分辨率保留到小数点后两位即每10ms计一次数第二它也足够精确因为补偿了定时器中断延时造成的误差它完全可以为用来测量百米成绩。这种小综合也是将来做大项目程序的基础。#include reg52.hsbit ADDR3 P1^3;sbit ENLED P1^4;sbit KEY1 P2^4;sbit KEY2 P2^5;sbit KEY3 P2^6;sbit KEY4 P2^7;unsigned char code LedChar[] { //数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};unsigned char LedBuff[6] { //数码管显示缓冲区0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};unsigned char KeySta[4] { //按键当前状态1, 1, 1, 1};bit StopwatchRunning 0; //秒表运行标志bit StopwatchRefresh 1; //秒表计数刷新标志unsigned char DecimalPart 0; //秒表的小数部分unsigned int IntegerPart 0; //秒表的整数部分unsigned char T0RH 0; //T0重载值的高字节unsigned char T0RL 0; //T0重载值的低字节void ConfigTimer0(unsigned int ms);void StopwatchDisplay();void KeyDriver();void main(){EA 1; //开总中断ENLED 0; //使能选择数码管ADDR3 1;P2 0xFE; //P2.0置0选择第4行按键作为独立按键ConfigTimer0(2); //配置T0定时2mswhile (1){if (StopwatchRefresh) //需要刷新秒表示数时调用显示函数{StopwatchRefresh 0;StopwatchDisplay();}KeyDriver(); //调用按键驱动函数}}/* 配置并启动T0ms-T0定时时间 */void ConfigTimer0(unsigned int ms){unsigned long tmp; //临时变量tmp 11059200 / 12; //定时器计数频率tmp (tmp * ms) / 1000; //计算所需的计数值tmp 65536 - tmp; //计算定时器重载值tmp tmp 18; //补偿中断响应延时造成的误差T0RH (unsigned char)(tmp8); //定时器重载值拆分为高低字节T0RL (unsigned char)tmp;TMOD 0xF0; //清零T0的控制位TMOD | 0x01; //配置T0为模式1TH0 T0RH; //加载T0重载值TL0 T0RL;ET0 1; //使能T0中断TR0 1; //启动T0}/* 秒表计数显示函数 */void StopwatchDisplay(){signed char i;unsigned char buf[4]; //数据转换的缓冲区//小数部分转换到低2位LedBuff[0] LedChar[DecimalPart%10];LedBuff[1] LedChar[DecimalPart/10];//整数部分转换到高4位buf[0] IntegerPart%10;buf[1] (IntegerPart/10)%10;buf[2] (IntegerPart/100)%10;buf[3] (IntegerPart/1000)%10;for (i3; i1; i--) //整数部分高位的0转换为空字符{if (buf[i] 0)LedBuff[i2] 0xFF;elsebreak;}for ( ; i0; i--) //有效数字位转换为显示字符{LedBuff[i2] LedChar[buf[i]];}LedBuff[2] 0x7F; //点亮小数点}/* 秒表启停函数 */void StopwatchAction(){if (StopwatchRunning) //已启动则停止StopwatchRunning 0;else //未启动则启动StopwatchRunning 1;}/* 秒表复位函数 */void StopwatchReset(){StopwatchRunning 0; //停止秒表DecimalPart 0; //清零计数值IntegerPart 0;StopwatchRefresh 1; //置刷新标志}/* 按键驱动函数检测按键动作调度相应动作函数需在主循环中调用 */void KeyDriver(){unsigned char i;static unsigned char backup[4] {1,1,1,1};for (i0; i4; i) //循环检测4个按键{if (backup[i] ! KeySta[i]) //检测按键动作{if (backup[i] ! 0) //按键按下时执行动作{if (i 1) //Esc键复位秒表StopwatchReset();else if (i 2) //回车键启停秒表StopwatchAction();}backup[i] KeySta[i]; //刷新前一次的备份值}}}/* 按键扫描函数需在定时中断中调用 */void KeyScan(){unsigned char i;static unsigned char keybuf[4] { //按键扫描缓冲区0xFF, 0xFF, 0xFF, 0xFF};//按键值移入缓冲区keybuf[0] (keybuf[0] 1) | KEY1;keybuf[1] (keybuf[1] 1) | KEY2;keybuf[2] (keybuf[2] 1) | KEY3;keybuf[3] (keybuf[3] 1) | KEY4;//消抖后更新按键状态for (i0; i4; i){if (keybuf[i] 0x00){ //连续8次扫描值为0即16ms内都是按下状态时可认为按键已稳定的按下KeySta[i] 0;}else if (keybuf[i] 0xFF){ //连续8次扫描值为1即16ms内都是弹起状态时可认为按键已稳定的弹起KeySta[i] 1;}}}/* 数码管动态扫描刷新函数需在定时中断中调用 */void LedScan(){static unsigned char i 0; //动态扫描索引P0 0xFF; //关闭所有段选位显示消隐P1 (P1 0xF8) | i; //位选索引值赋值到P1口低3位P0 LedBuff[i]; //缓冲区中索引位置的数据送到P0口if (i 5) //索引递增循环遍历整个缓冲区i;elsei 0;}/* 秒表计数函数每隔10ms调用一次进行秒表计数累加 */void StopwatchCount(){if (StopwatchRunning) //当处于运行状态时递增计数值{DecimalPart; //小数部分1if (DecimalPart 100) //小数部分计到100时进位到整数部分{DecimalPart 0;IntegerPart; //整数部分1if (IntegerPart 10000) //整数部分计到10000时归零{IntegerPart 0;}}StopwatchRefresh 1; //设置秒表计数刷新标志}}/* T0中断服务函数完成数码管、按键扫描与秒表计数 */void InterruptTimer0() interrupt 1{static unsigned char tmr10ms 0;TH0 T0RH; //重新加载重载值TL0 T0RL;LedScan(); //数码管扫描显示KeyScan(); //按键扫描//定时10ms进行一次秒表计数tmr10ms;if (tmr10ms 5){tmr10ms 0;StopwatchCount(); //调用秒表计数函数}}