在嵌入式系统开发中,使用 51 单片机实现倒计时功能非常常见,尤其是在各种智能家居、工业控制等领域。本文将详细介绍如何利用 51 单片机的定时器 1 中断,结合数码管显示技术,实现一个精确的 60 秒倒计时器。我们会深入剖析底层原理,提供完整的代码示例,并分享实战中的避坑经验。
问题场景重现:倒计时器的需求与挑战
假设我们需要开发一个简单的厨房定时器,要求能够显示 60 秒倒计时,并在倒计时结束后发出提示音。这个需求的挑战在于:
- 精度要求: 倒计时必须尽可能精确,避免出现较大的误差。
- 资源限制: 51 单片机的资源相对有限,需要高效地利用定时器和中断。
- 显示驱动: 数码管的驱动需要占用一定的 I/O 口,需要合理分配。
底层原理深度剖析:定时器 1 中断的奥秘
51 单片机提供了两个定时器(Timer0 和 Timer1),它们可以工作在不同的模式下。这里我们选择定时器 1,并配置为定时模式。定时器的工作原理是通过计数器对外部时钟脉冲进行计数,当计数器溢出时,会触发一个中断。我们可以通过配置定时器的初值和工作模式,来控制中断发生的频率。
定时器 1 的配置
工作模式: 定时器 1 可以配置为模式 0、模式 1、模式 2 和模式 3。常用的模式是模式 1(16 位定时器)和模式 2(8 位自动重载定时器)。这里我们选择模式 1,因为它可以提供更大的计数范围,从而降低中断频率,减少 CPU 的负担。
定时器初值: 定时器初值决定了计数器从哪个值开始计数。我们需要根据晶振频率和所需的中断频率来计算定时器初值。假设我们使用 12MHz 的晶振,希望每 50ms 触发一次中断,那么定时器初值可以通过以下公式计算:

初值 = 65536 - (晶振频率 / 12 / 中断频率) = 65536 - (12000000 / 12 / 20) = 65536 - 50000 = 15536转换为十六进制:
TH1 = (65536 - 50000) / 256; TL1 = (65536 - 50000) % 256;
中断使能: 需要使能定时器 1 的中断,才能在计数器溢出时触发中断服务程序。这需要设置中断使能寄存器
IE的相应位。
数码管显示原理
数码管分为共阴极和共阳极两种。共阴极数码管的公共端接地,通过控制各个段的阳极电平来显示数字。共阳极数码管的公共端接电源,通过控制各个段的阴极电平来显示数字。驱动数码管需要使用 I/O 口,并且可能需要使用锁存器来扩展 I/O 口的数量。
具体的代码解决方案
#include <reg51.h>
// 定义数码管的段选和位选端口
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
sbit DIG1 = P2^0; // 个位
sbit DIG2 = P2^1; // 十位
unsigned char code led_number[]={ // 数码管显示 0-9 的编码
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f
};
unsigned char second = 60; // 初始倒计时时间
unsigned char count = 0; // 中断计数器
void Timer1_Init(void) // 定时器 1 初始化函数
{
TMOD |= 0x10; // 选择定时器 1 的模式 1(16 位定时器)
TH1 = (65536 - 50000) / 256; // 设置定时器初值,50ms 中断一次
TL1 = (65536 - 50000) % 256;
ET1 = 1; // 使能定时器 1 中断
EA = 1; // 开启总中断
TR1 = 1; // 启动定时器 1
}
void Display(unsigned char shi, unsigned char ge) // 数码管显示函数
{
DIG1 = 0; //选中个位
P0 = led_number[ge];
DIG1 = 1;
delay(1);
DIG2 = 0; //选中十位
P0 = led_number[shi];
DIG2 = 1;
delay(1);
}
void delay(unsigned int i)
{
while(i--);
}
void main()
{
Timer1_Init(); // 初始化定时器
while(1)
{
Display(second / 10, second % 10); // 显示倒计时时间
}
}
void Timer1_ISR() interrupt 5 // 定时器 1 中断服务程序
{
TH1 = (65536 - 50000) / 256; // 重新加载定时器初值
TL1 = (65536 - 50000) % 256;
count++;
if (count >= 20) // 50ms * 20 = 1 秒
{
count = 0;
if (second > 0)
{
second--; // 倒计时减 1
}
else
{
TR1 = 0; // 停止定时器
// 添加倒计时结束后的处理代码,例如发出提示音
P0 = 0xff; //点亮所有数码管,模拟提示
delay(1000); //延时 1s
P0 = 0x00; //关闭
}
}
}
代码解释:
Timer1_Init()函数负责初始化定时器 1,包括设置工作模式、定时器初值和中断使能。Timer1_ISR()函数是定时器 1 的中断服务程序,每次中断都会执行。在这个函数中,我们首先重新加载定时器初值,然后将中断计数器count加 1。当count达到 20 时,表示已经过去了 1 秒,我们将倒计时时间second减 1。当second减到 0 时,表示倒计时结束,我们可以停止定时器,并执行一些其他的操作,例如发出提示音。Display()函数负责将倒计时时间显示在数码管上。这个函数需要根据数码管的类型(共阴极或共阳极)来选择合适的显示编码。- 主函数
main()负责初始化定时器,并在循环中不断更新数码管的显示。
实战避坑经验总结
- 晶振频率的选择: 晶振频率直接影响定时器的精度。建议选择精度较高的晶振。
- 定时器初值的计算: 定时器初值的计算必须准确,否则会导致倒计时误差较大。可以使用示波器来验证中断频率是否正确。
- 中断服务程序的编写: 中断服务程序应该尽可能短小,避免占用过多的 CPU 时间。不要在中断服务程序中执行耗时的操作,例如数码管显示。
- 数码管的驱动: 数码管的驱动需要占用一定的 I/O 口,需要合理分配。如果 I/O 口不够用,可以使用锁存器来扩展 I/O 口的数量。
- 抗干扰设计: 嵌入式系统容易受到电磁干扰的影响,需要进行抗干扰设计。可以采取的措施包括:电源滤波、信号线屏蔽、接地等。
掌握了以上内容,相信你就能在 51 单片机上轻松实现 60 秒数码管倒计时功能。在实际项目中,可以根据具体需求进行修改和扩展,例如添加按键设置倒计时时间、添加蜂鸣器提示等。51 单片机的应用领域非常广泛,希望本文能够帮助你入门嵌入式系统开发。
冠军资讯
代码一只喵