[title]单片机秒表程序效果[/title]
首先,我们先看下效果视频:
[title]思维导图(9个函数之间的关联)[/title]
本程序用到的基本模块有:按键、数码管
用到了MCU内部的中断和定时器
在写代码之前,先画一个思维导图,理清各个模块、函数之间的关联。还有各个状态变量。
[title]程序代码[/title]
/*! * 心得体会: * 1. 单片机由于没有硬件调试工具,所以debug显得十分棘手。因此我们只能通过观察硬件故障(与设计不相符)来确定是哪块函数出现了问题。 * 需要用到中断的函数,在排查该函数之前先排除中断问题。 * 然后:例如数码管显示不正常,那就需要查看LedScan()函数。该函数是控制扫描的,如果该函数错误就会出现有的数码管显示有的不显示,频闪等问题。 * 顺藤摸瓜,如果LedScan()函数没有问题,就查数码管显示函数。如果显示函数出现错误,就会导致显示数字诡异。 * 又例如按键出现失效,比如只能按下一次,在程序运行过程中失效。那么大多是backup变量出现了问题。 * 2. 再次强调模块化的重要性,试想下我们这个简单的程序就有9个函数。如果将它们都写在main函数中,那这个程序的调试基本完蛋。一定要降低耦合度。方便出现问题的时候按硬件对应的模块调试。 * 3. 对于复杂程序,可以画一个思维导图来理清各个函数之间的调用关系。 * 4. 主程序之外的全局变量一定要赋初值! * 5. 理解函数内static的作用范围。例如经常在函数内for循环经常用到的i,为什么有的加static有的不加? * 6. 模块化学习,各个硬件独立函数封装,以便于改进和记忆。 */ #include <reg52.h> sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; sbit KeyIn1 = P2^4; sbit KeyIn2 = P2^5; sbit KeyIn3 = P2^6; sbit KeyIn4 = 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 //KeyIn的默认状态 }; unsigned char DecimalPart = 0; unsigned int IntegerPart = 0; bit StopwatchRunning = 0; bit StopwatchRefresh = 1; //1为reset状态 void ConfigTimer0(unsigned int ms); void KeyDriver(); void StopwatchDisplay(); unsigned char T0RH = 0; unsigned char T0RL = 0; void main() { ENLED = 0; ADDR3 = 1; P2 = 0xFE; //开启中断 EA = 1; ConfigTimer0(2); //2ms while(1) { if (StopwatchRefresh == 1) { StopwatchRefresh = 0; StopwatchDisplay(); } KeyDriver(); } } void StopwatchCount() //每10ms计数一次 { if (StopwatchRunning == 1) { DecimalPart++; if (DecimalPart >= 100) { DecimalPart = 0; IntegerPart++; if (IntegerPart >= 10000) { IntegerPart = 0; } } StopwatchRefresh = 1; } } void StopwatchDisplay() { unsigned char buf[4]; //4的意思是整数部分占用4个数码管 static signed char i; //确定每个数码管显示的数值,即buf[] //小数部分 LedBuff[0] = LedChar[DecimalPart%10]; LedBuff[1] = LedChar[DecimalPart/10]; //整数部分 buf[0] = IntegerPart%10; buf[1] = IntegerPart/10%10; buf[2] = IntegerPart/100%10; buf[3] = IntegerPart/1000%10; //从高位看,最高位如果是0则不显示 for (i=3; i>=1; i--) { if (buf[i] == 0) { LedBuff[i+2] = 0xFF; //+2的意思是:避开最后两个显示毫秒的数码管 } else { break; } } for (; i>=0; i--) { LedBuff[i+2] = LedChar[buf[i]]; } LedBuff[2] &= 0x7F; //显示小数点,在指定的数码管 } void LedScan() { static unsigned char i = 0; P0 = 0xFF; //消影 P1 = (P1 & 0xF8) | i; P0 = LedBuff[i]; //最终在这里数码管被扫描刷新 //[0, 5]循环 if (i<5) { i++; } else { i = 0; } } /* 秒表启停函数 */ 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[] = { 1, 1, 1, 1 }; for (i=0; i<4;i++) { if (backup[i] != KeySta[i]) { if (backup[i] != 0) //按下 { if (i == 1) { StopwatchReset(); } else if (i == 2) { StopwatchAction(); } //backup[i] = KeySta[i]; } backup[i] = KeySta[i]; //此句写错到上面的位置出现的症状是:按键只能在开始的时候使用一次,后面在程序运行过程中按键失效。 } } } void KeyScan() { unsigned char i; static unsigned char keybuf[] = { 0xFF, 0xFF, 0xFF, 0xFF }; keybuf[0] = (keybuf[0] << 1) | KeyIn1; keybuf[1] = (keybuf[1] << 1) | KeyIn2; keybuf[2] = (keybuf[2] << 1) | KeyIn3; keybuf[3] = (keybuf[3] << 1) | KeyIn4; for (i=0; i<4; i++) { if (keybuf[i] == 0x00) { KeySta[i] = 0; } else if (keybuf[i] == 0xFF) { KeySta[i] = 1; } } } /* 配置中断 */ void ConfigTimer0(unsigned int ms) { static unsigned long tmp; //初值 tmp = 65536-(11059200/12*ms/1000); tmp = tmp + 18; //tmp共16位长(long) T0RH = (unsigned char)(tmp >> 8); //取前8位 T0RL = (unsigned char)tmp; //截断前8位 TH0 = T0RH; TL0 = T0RL; TMOD = TMOD & 0xF0; TMOD = TMOD | 0x01; ET0 = 1; TR0 = 1; } /* 中断 */ void InterruptTimer0() interrupt 1 { static unsigned char tmr10ms = 0; TH0 = T0RH; TL0 = T0RL; //提供2ms中断服务 LedScan(); KeyScan(); //提供10ms终端服务 tmr10ms++; if (tmr10ms >= 5) { tmr10ms = 0; StopwatchCount(); } }
[title]心得体会[/title]
心得体会:
- 单片机由于没有硬件调试工具,所以debug显得十分棘手。因此我们只能通过观察硬件故障(与设计不相符)来确定是哪块函数出现了问题。
需要用到中断的函数,在排查该函数之前先排除中断问题。然后:例如数码管显示不正常,那就需要查看LedScan()函数。该函数是控制扫描的,如果该函数错误就会出现有的数码管显示有的不显示,频闪等问题。顺藤摸瓜,如果LedScan()函数没有问题,就查数码管显示函数。如果显示函数出现错误,就会导致显示数字诡异。
又例如按键出现失效,比如只能按下一次,在程序运行过程中失效。那么大多是backup变量出现了问题。 - 再次强调模块化的重要性,试想下我们这个简单的程序就有9个函数。如果将它们都写在main函数中,那这个程序的调试基本完蛋。一定要降低耦合度。方便出现问题的时候按硬件对应的模块调试。
- 对于复杂程序,可以画一个思维导图来理清各个函数之间的调用关系。
- 主程序之外的全局变量一定要赋初值!
- 理解函数内static的作用范围。例如经常在函数内for循环经常用到的i,为什么有的加static有的不加?
- 模块化学习,各个硬件独立函数封装,以便于改进和记忆。