[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有的不加?
- 模块化学习,各个硬件独立函数封装,以便于改进和记忆。