基于51单片机的T0计数器系统设计,使用定时器0作为计数器对外部脉冲进行计数,并通过数码管显示计数值。
一、系统设计
1.1 功能要求
- T0作为计数器,对P3.4(T0)引脚的外部脉冲计数
- 4位共阳数码管显示计数值(0-9999)
- 支持加计数和减计数两种模式
- 按键控制:清零、模式切换、暂停/继续
- 计数溢出处理(0-9999循环)
- 计数频率可达50kHz
1.2 硬件资源
| 资源 |
型号/规格 |
数量 |
| 单片机 |
STC89C52/AT89C52 |
1 |
| 数码管 |
4位共阳数码管 |
1 |
| 按键 |
轻触开关 |
3 |
| 电阻 |
220Ω(限流) |
8 |
| 电阻 |
10kΩ(上拉) |
3 |
| 晶振 |
11.0592MHz |
1 |
| 脉冲源 |
按键/信号发生器 |
1 |
二、硬件设计
2.1 电路原理图
+5V
|
+-+ 数码管显示
| 段选:P0.0-P0.7 (经220Ω电阻)
| 位选:P2.0-P2.3 (经220Ω电阻)
|
+-+ 按键控制
| K1(清零) -> P3.0 -> GND
| K2(模式) -> P3.1 -> GND
| K3(暂停) -> P3.2 -> GND
| (各接10kΩ上拉电阻到+5V)
|
+-+ 计数输入
| P3.4(T0) <- 外部脉冲输入
|
+-+ 单片机最小系统
XTAL1/XTAL2 -> 11.0592MHz晶振
RST -> 10kΩ电阻+10μF电容
EA -> +5V
VCC -> +5V
GND -> GND
2.2 引脚分配表
| 单片机引脚 |
连接设备 |
功能说明 |
| P0.0-P0.7 |
数码管段选 |
a,b,c,d,e,f,g,dp |
| P2.0-P2.3 |
数码管位选 |
千位、百位、十位、个位 |
| P3.0 |
按键K1 |
清零 |
| P3.1 |
按键K2 |
模式切换 |
| P3.2 |
按键K3 |
暂停/继续 |
| P3.4 |
外部脉冲 |
T0计数输入 |
| P1.0 |
LED指示 |
计数状态指示 |
三、软件设计
3.1 主程序 (main.c)
#include <reg52.h>
#include "display.h"
#include "keyboard.h"
#include "timer0_counter.h"
// 全局变量
unsigned int count_value = 0; // 当前计数值
unsigned int max_count = 9999; // 最大计数值
unsigned int min_count = 0; // 最小计数值
bit count_mode = 0; // 0:加计数 1:减计数
bit count_enable = 1; // 计数使能
bit overflow_flag = 0; // 溢出标志
void main(void)
{
System_Init();
while(1)
{
Key_Process(); // 按键处理
Display_Update(); // 显示更新
Count_Check(); // 计数范围检查
}
}
void System_Init(void)
{
// 初始化定时器0为计数器模式
Timer0_Counter_Init();
// 初始化数码管
Display_Init();
// 显示初始值
Display_Number(count_value);
// 开启总中断
EA = 1;
// 启动计数
TR0 = 1;
}
void Key_Process(void)
{
unsigned char key = Key_Scan();
switch(key)
{
case KEY_CLEAR: // 清零
count_value = 0;
Display_Number(count_value);
break;
case KEY_MODE: // 切换模式
count_mode = !count_mode;
if(count_mode)
Display_Show_Mode("DEC"); // 显示减计数模式
else
Display_Show_Mode("INC"); // 显示加计数模式
break;
case KEY_PAUSE: // 暂停/继续
count_enable = !count_enable;
if(count_enable)
{
TR0 = 1; // 继续计数
P1_0 = 0; // LED亮表示计数中
}
else
{
TR0 = 0; // 暂停计数
P1_0 = 1; // LED灭表示暂停
}
break;
default:
break;
}
}
void Count_Check(void)
{
// 检查溢出
if(overflow_flag)
{
overflow_flag = 0;
if(count_mode == 0) // 加计数溢出
{
count_value = min_count; // 回到最小值
}
else // 减计数溢出
{
count_value = max_count; // 回到最大值
}
Display_Number(count_value);
}
// 检查是否达到极限
if(count_value >= max_count && count_mode == 0)
{
count_enable = 0; // 禁止计数
TR0 = 0;
P1_0 = 1; // LED灭
}
if(count_value <= min_count && count_mode == 1)
{
count_enable = 0; // 禁止计数
TR0 = 0;
P1_0 = 1; // LED灭
}
}
// 定时器0中断服务程序(计数器溢出中断)
void Timer0_ISR(void) interrupt 1
{
overflow_flag = 1; // 设置溢出标志
// 根据计数模式更新计数值
if(count_mode == 0) // 加计数
{
count_value++;
if(count_value > max_count)
count_value = min_count; // 循环
}
else // 减计数
{
count_value--;
if(count_value < min_count)
count_value = max_count; // 循环
}
Display_Number(count_value);
}
3.2 定时器0计数器 (timer0_counter.h)
#ifndef __TIMER0_COUNTER_H__
#define __TIMER0_COUNTER_H__
#include <reg52.h>
// 函数声明
void Timer0_Counter_Init(void);
void Timer0_Start(void);
void Timer0_Stop(void);
void Timer0_Set_Value(unsigned int value);
unsigned int Timer0_Get_Value(void);
#endif
3.3 定时器0计数器 (timer0_counter.c)
#include "timer0_counter.h"
void Timer0_Counter_Init(void)
{
// 设置T0为计数器模式,工作方式1(16位计数)
TMOD &= 0xF0; // 清除T0控制位
TMOD |= 0x05; // 0000 0101: T0为计数器,方式1(16位)
// 设置计数初值(0)
TH0 = 0x00;
TL0 = 0x00;
// 允许T0中断
ET0 = 1;
// 启动T0(由TR0控制)
TR0 = 0; // 先不启动
}
void Timer0_Start(void)
{
TR0 = 1; // 启动计数器
}
void Timer0_Stop(void)
{
TR0 = 0; // 停止计数器
}
void Timer0_Set_Value(unsigned int value)
{
TH0 = value >> 8; // 高8位
TL0 = value & 0xFF; // 低8位
}
unsigned int Timer0_Get_Value(void)
{
unsigned int value;
value = (TH0 << 8) | TL0;
return value;
}
3.4 数码管显示 (display.h)
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#include <reg52.h>
// 数码管段码定义(共阳)
#define SEG_A 0x01
#define SEG_B 0x02
#define SEG_C 0x04
#define SEG_D 0x08
#define SEG_E 0x10
#define SEG_F 0x20
#define SEG_G 0x40
#define SEG_DP 0x80
// 数字0-9段码表
extern unsigned char code SegCode[10];
// 位选定义
#define DIGIT_1 P2_0 // 千位
#define DIGIT_2 P2_1 // 百位
#define DIGIT_3 P2_2 // 十位
#define DIGIT_4 P2_3 // 个位
// 函数声明
void Display_Init(void);
void Display_Number(unsigned int num);
void Display_Scan(unsigned char pos);
void Display_Clear(void);
void Display_Show_Mode(char *mode);
// 显示缓冲区
extern unsigned char Display_Buffer[4];
#endif
3.5 数码管显示 (display.c)
#include "display.h"
// 共阳数码管段码表(0-9)
unsigned char code SegCode[10] = {
0xC0, // 0: 1100 0000
0xF9, // 1: 1111 1001
0xA4, // 2: 1010 0100
0xB0, // 3: 1011 0000
0x99, // 4: 1001 1001
0x92, // 5: 1001 0010
0x82, // 6: 1000 0010
0xF8, // 7: 1111 1000
0x80, // 8: 1000 0000
0x90 // 9: 1001 0000
};
// 字母段码表
unsigned char code LetterCode[26] = {
0x88, // A
0x83, // b
0xC6, // C
0xA1, // d
0x86, // E
0x8E, // F
0xC2, // G
0x89, // H
0xF9, // I
0xF1, // J
0xC7, // L
0xC8, // M
0xC1, // n
0xC0, // O
0x8C, // P
0x98, // q
0x92, // r
0x87, // S
0x83, // t
0xC1, // u
0x91, // v
0xD1, // w
0x89, // X
0x49, // y
0x90 // Z
};
unsigned char Display_Buffer[4] = {0xC0, 0xC0, 0xC0, 0xC0};
void Display_Init(void)
{
P0 = 0xFF; // 段选全灭
P2 = 0x0F; // 位选全灭
}
void Display_Number(unsigned int num)
{
// 分解各位数字
unsigned char thousand, hundred, ten, unit;
thousand = num / 1000;
hundred = (num % 1000) / 100;
ten = (num % 100) / 10;
unit = num % 10;
// 存储显示缓冲区
Display_Buffer[0] = SegCode[thousand];
Display_Buffer[1] = SegCode[hundred];
Display_Buffer[2] = SegCode[ten];
Display_Buffer[3] = SegCode[unit];
}
void Display_Scan(unsigned char pos)
{
// 关闭所有位选
P2 = 0x0F;
// 送段码
switch(pos)
{
case 0: // 千位
DIGIT_1 = 0; // 选中千位
P0 = Display_Buffer[0];
break;
case 1: // 百位
DIGIT_2 = 0; // 选中百位
P0 = Display_Buffer[1];
break;
case 2: // 十位
DIGIT_3 = 0; // 选中十位
P0 = Display_Buffer[2];
break;
case 3: // 个位
DIGIT_4 = 0; // 选中个位
P0 = Display_Buffer[3];
break;
}
}
void Display_Clear(void)
{
P0 = 0xFF; // 段选全灭
P2 = 0x0F; // 位选全灭
}
void Display_Show_Mode(char *mode)
{
// 显示模式标识
if(mode[0] == 'I') // INC
{
Display_Buffer[0] = LetterCode['I'-'A'];
Display_Buffer[1] = LetterCode['N'-'A'];
Display_Buffer[2] = LetterCode['C'-'A'];
Display_Buffer[3] = 0xFF; // 熄灭
}
else if(mode[0] == 'D') // DEC
{
Display_Buffer[0] = LetterCode['D'-'A'];
Display_Buffer[1] = LetterCode['E'-'A'];
Display_Buffer[2] = LetterCode['C'-'A'];
Display_Buffer[3] = 0xFF; // 熄灭
}
}
3.6 键盘扫描 (keyboard.h)
#ifndef __KEYBOARD_H__
#define __KEYBOARD_H__
#include <reg52.h>
// 按键定义
#define KEY_CLEAR 0x01
#define KEY_MODE 0x02
#define KEY_PAUSE 0x03
#define KEY_NONE 0xFF
// 按键端口定义
sbit KEY_CLR_PIN = P3^0;
sbit KEY_MOD_PIN = P3^1;
sbit KEY_PAU_PIN = P3^2;
// 函数声明
void Key_Init(void);
unsigned char Key_Scan(void);
#endif
3.7 键盘扫描 (keyboard.c)
#include "keyboard.h"
#include "delay.h"
void Key_Init(void)
{
// 按键端口初始化
}
unsigned char Key_Scan(void)
{
// 检测清零键
if(KEY_CLR_PIN == 0)
{
Delay_Ms(10); // 消抖
if(KEY_CLR_PIN == 0)
{
while(KEY_CLR_PIN == 0); // 等待按键释放
return KEY_CLEAR;
}
}
// 检测模式键
if(KEY_MOD_PIN == 0)
{
Delay_Ms(10);
if(KEY_MOD_PIN == 0)
{
while(KEY_MOD_PIN == 0);
return KEY_MODE;
}
}
// 检测暂停键
if(KEY_PAU_PIN == 0)
{
Delay_Ms(10);
if(KEY_PAU_PIN == 0)
{
while(KEY_PAU_PIN == 0);
return KEY_PAUSE;
}
}
return KEY_NONE;
}
3.8 延时函数 (delay.h)
#ifndef __DELAY_H__
#define __DELAY_H__
#include <reg52.h>
// 函数声明
void Delay_Ms(unsigned int ms);
void Delay_Us(unsigned int us);
#endif
3.9 延时函数 (delay.c)
#include "delay.h"
// 毫秒延时
void Delay_Ms(unsigned int ms)
{
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
// 微秒延时
void Delay_Us(unsigned int us)
{
while(us--)
{
_nop_();
_nop_();
_nop_();
_nop_();
}
}
四、Proteus仿真电路
4.1 Proteus元件清单
1. AT89C52 (单片机)
2. 7SEG-MPX4-CA (4位共阳数码管)
3. BUTTON (按键×3)
4. RES (电阻220Ω×8)
5. RES (电阻10kΩ×3)
6. CRYSTAL (11.0592MHz)
7. CAP (电容30pF×2)
8. POWER (电源)
9. GROUND (地)
10. SIGNAL_GENERATOR (信号发生器,用于产生脉冲)
4.2 仿真连接图
AT89C52:
P0.0-P0.7 -> 数码管段选a-g,dp
P2.0-P2.3 -> 数码管位选1-4
P3.0 -> 按键K1(清零)
P3.1 -> 按键K2(模式)
P3.2 -> 按键K3(暂停)
P3.4 -> 信号发生器输出(T0计数输入)
XTAL1/XTAL2 -> 11.0592MHz晶振
RST -> 10kΩ电阻+10μF电容
VCC -> +5V
GND -> GND
五、功能扩展
5.1 增加计数频率测量
// 测量计数频率
void Measure_Frequency(void)
{
static unsigned int last_count = 0;
static unsigned long last_time = 0;
unsigned int current_count;
unsigned long current_time;
unsigned int frequency;
current_time = Get_System_Time(); // 获取系统时间(ms)
current_count = Timer0_Get_Value();
if(current_time - last_time >= 1000) // 每秒测量一次
{
frequency = current_count - last_count;
last_count = current_count;
last_time = current_time;
// 显示频率
Display_Frequency(frequency);
}
}
5.2 增加计数预置值
// 设置预置值
void Set_Preset_Value(unsigned int preset)
{
count_value = preset;
Timer0_Set_Value(preset);
Display_Number(count_value);
}
5.3 增加串行通信
// 串口初始化
void UART_Init(void)
{
TMOD &= 0x0F; // 清除T1控制位
TMOD |= 0x20; // 设置T1为模式2(8位自动重载)
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 启动T1
SM0 = 0; // 设置串口为模式1
SM1 = 1;
REN = 1; // 允许接收
EA = 1; // 允许总中断
ES = 1; // 允许串口中断
}
// 串口中断服务程序
void UART_ISR(void) interrupt 4
{
unsigned char received_data;
if(RI) // 接收中断
{
RI = 0; // 清除接收中断标志
received_data = SBUF;
// 根据接收到的数据设置预置值
if(received_data >= '0' && received_data <= '9')
{
unsigned int digit = received_data - '0';
count_value = count_value * 10 + digit;
if(count_value > 9999)
count_value = 9999;
Display_Number(count_value);
}
else if(received_data == 'C' || received_data == 'c')
{
count_value = 0;
Display_Number(count_value);
}
}
}
参考代码 数码管T0计数器设计 www.youwenfan.com/contentcnv/118036.html
六、使用说明
6.1 操作步骤
- 接通5V电源
- 数码管显示初始值"0000"
- 将脉冲源连接到P3.4(T0)引脚
- 按"模式"键切换加/减计数模式
- 按"暂停"键暂停/继续计数
- 按"清零"键将计数值清零
6.2 注意事项
- 外部脉冲幅度需在0-5V之间
- 脉冲宽度需大于2个机器周期
- 最大计数频率不超过50kHz
- 计数溢出时会自动循环
6.3 调试技巧
- 先用按键模拟脉冲测试基本功能
- 再用信号发生器测试高频计数
- 观察LED指示灯判断计数状态
- 使用示波器观察T0引脚波形
七、性能指标
| 参数 |
指标 |
| 计数范围 |
0-9999 |
| 计数模式 |
加计数/减计数 |
| 最大计数频率 |
50kHz |
| 显示方式 |
4位静态显示 |
| 按键响应 |
<50ms |
| 功耗 |
<50mA @5V |