两种实现方案:基于51单片机的经典方案和基于Arduino的现代方案
方案一:51单片机数码管加减计数系统
一、系统设计
1.1 功能要求
- 4位共阳数码管显示(0-9999)
- 独立按键:加计数、减计数、清零
- 计数范围:0 ~ 9999(可设置上下限)
- 按键消抖处理
- 溢出保护(达到最大值停止加计数,最小值停止减计数)
- 可选:掉电保存计数值(EEPROM)
1.2 硬件资源
| 资源 |
型号/规格 |
数量 |
| 单片机 |
STC89C52/AT89C52 |
1 |
| 数码管 |
4位共阳数码管 |
1 |
| 按键 |
轻触开关 |
3 |
| 电阻 |
220Ω(限流) |
8 |
| 电阻 |
10kΩ(上拉) |
3 |
| 电容 |
10μF(复位) |
1 |
| 晶振 |
11.0592MHz |
1 |
二、硬件设计
2.1 电路原理图
+5V
|
+-+ 数码管
| 位选:P2.0-P2.3 (经220Ω电阻)
| 段选:P0.0-P0.7 (经220Ω电阻)
|
+-+ 按键
| K1(加) -> P3.0 -> GND
| K2(减) -> P3.1 -> GND
| K3(清零) -> P3.2 -> GND
| (各接10kΩ上拉电阻到+5V)
|
+-+ 单片机最小系统
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.3 |
LED指示 |
计数状态指示 |
三、软件设计
3.1 主程序 (main.c)
#include <reg52.h>
#include "display.h"
#include "keyboard.h"
#include "timer.h"
// 全局变量
unsigned int count_value = 0; // 当前计数值
unsigned int max_count = 9999; // 最大计数值
unsigned int min_count = 0; // 最小计数值
bit count_up_enable = 1; // 加计数使能
bit count_down_enable = 1; // 减计数使能
void main(void)
{
System_Init();
while(1)
{
Key_Process(); // 按键处理
Display_Update(); // 显示更新
Count_Check(); // 计数范围检查
}
}
void System_Init(void)
{
// 定时器0初始化(1ms中断)
Timer0_Init();
// 数码管初始化
Display_Init();
// 显示初始值
Display_Number(count_value);
// 开启总中断
EA = 1;
}
void Key_Process(void)
{
unsigned char key = Key_Scan();
switch(key)
{
case KEY_ADD: // 加计数
if(count_up_enable && count_value < max_count)
{
count_value++;
Display_Number(count_value);
}
break;
case KEY_SUB: // 减计数
if(count_down_enable && count_value > min_count)
{
count_value--;
Display_Number(count_value);
}
break;
case KEY_CLEAR: // 清零
count_value = 0;
Display_Number(count_value);
break;
default:
break;
}
}
void Count_Check(void)
{
// 检查是否达到上限
if(count_value >= max_count)
{
count_up_enable = 0; // 禁止加计数
P3_3 = 0; // LED亮表示达到上限
}
else
{
count_up_enable = 1;
P3_3 = 1; // LED灭
}
// 检查是否达到下限
if(count_value <= min_count)
{
count_down_enable = 0; // 禁止减计数
}
else
{
count_down_enable = 1;
}
}
// 定时器0中断服务程序(数码管动态扫描)
void Timer0_ISR(void) interrupt 1
{
static unsigned char scan_pos = 0;
TH0 = 0xFC; // 重装初值(1ms)
TL0 = 0x66;
// 数码管动态扫描
Display_Scan(scan_pos);
scan_pos = (scan_pos + 1) % 4;
}
3.2 数码管显示 (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);
#endif
3.3 数码管显示 (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 SegCode_DP[10] = {
0x40, // 0.
0x79, // 1.
0x24, // 2.
0x30, // 3.
0x19, // 4.
0x12, // 5.
0x02, // 6.
0x78, // 7.
0x00, // 8.
0x10 // 9.
};
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; // 位选全灭
}
// 显示缓冲区
unsigned char Display_Buffer[4] = {0xC0, 0xC0, 0xC0, 0xC0};
3.4 键盘扫描 (keyboard.h)
#ifndef __KEYBOARD_H__
#define __KEYBOARD_H__
#include <reg52.h>
// 按键定义
#define KEY_ADD 0x01
#define KEY_SUB 0x02
#define KEY_CLEAR 0x03
#define KEY_NONE 0xFF
// 按键端口定义
sbit KEY_ADD_PIN = P3^0;
sbit KEY_SUB_PIN = P3^1;
sbit KEY_CLR_PIN = P3^2;
// 函数声明
void Key_Init(void);
unsigned char Key_Scan(void);
unsigned char Key_Scan_With_Debounce(void);
#endif
3.5 键盘扫描 (keyboard.c)
#include "keyboard.h"
#include "delay.h"
void Key_Init(void)
{
// 按键端口初始化(P3.0-P3.2作为输入)
}
unsigned char Key_Scan(void)
{
// 检测加计数键
if(KEY_ADD_PIN == 0)
{
Delay_Ms(10); // 消抖
if(KEY_ADD_PIN == 0)
{
while(KEY_ADD_PIN == 0); // 等待按键释放
return KEY_ADD;
}
}
// 检测减计数键
if(KEY_SUB_PIN == 0)
{
Delay_Ms(10);
if(KEY_SUB_PIN == 0)
{
while(KEY_SUB_PIN == 0);
return KEY_SUB;
}
}
// 检测清零键
if(KEY_CLR_PIN == 0)
{
Delay_Ms(10);
if(KEY_CLR_PIN == 0)
{
while(KEY_CLR_PIN == 0);
return KEY_CLEAR;
}
}
return KEY_NONE;
}
// 带状态机消抖的按键扫描
unsigned char Key_Scan_With_Debounce(void)
{
static unsigned char key_state = 0;
static unsigned char key_press = 0;
static unsigned char key_old = 0;
unsigned char key_temp = 0;
// 读取当前按键状态
if(KEY_ADD_PIN == 0) key_temp |= 0x01;
if(KEY_SUB_PIN == 0) key_temp |= 0x02;
if(KEY_CLR_PIN == 0) key_temp |= 0x04;
// 状态机消抖
switch(key_state)
{
case 0: // 初始状态
if(key_temp != 0)
{
key_state = 1;
key_press = key_temp;
}
break;
case 1: // 确认按下
if(key_temp == key_press)
{
key_state = 2;
key_old = key_press;
return key_press;
}
else
{
key_state = 0;
}
break;
case 2: // 等待释放
if(key_temp == 0)
{
key_state = 0;
}
break;
}
return KEY_NONE;
}
3.6 定时器 (timer.h)
#ifndef __TIMER_H__
#define __TIMER_H__
#include <reg52.h>
// 函数声明
void Timer0_Init(void);
void Timer1_Init(void);
void Delay_Ms(unsigned int ms);
#endif
3.7 定时器 (timer.c)
#include "timer.h"
void Timer0_Init(void)
{
TMOD &= 0xF0; // 清除T0控制位
TMOD |= 0x01; // 设置T0为模式1(16位定时器)
TH0 = 0xFC; // 1ms定时(11.0592MHz)
TL0 = 0x66;
ET0 = 1; // 允许T0中断
TR0 = 1; // 启动T0
}
void Timer1_Init(void)
{
TMOD &= 0x0F; // 清除T1控制位
TMOD |= 0x20; // 设置T1为模式2(8位自动重载)
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 启动T1
}
void Delay_Ms(unsigned int ms)
{
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
四、Proteus仿真电路
4.1 Proteus元件清单
1. AT89C52 (单片机)
2. 7SEG-MPX4-CA (4位共阳数码管)
3. BUTTON (按键×3)
4. RES (电阻220Ω×8)
5. RES (电阻10kΩ×3)
6. CAP (电容10μF)
7. CRYSTAL (11.0592MHz)
8. POWER (电源)
9. GROUND (地)
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(清零)
XTAL1/XTAL2 -> 11.0592MHz晶振
RST -> 10μF电容+10kΩ电阻
VCC -> +5V
GND -> GND
五、扩展功能
5.1 EEPROM存储计数值
#include "eeprom.h"
// 保存计数值到EEPROM
void Save_Count_To_EEPROM(unsigned int count)
{
unsigned char high_byte, low_byte;
high_byte = count >> 8; // 高字节
low_byte = count & 0xFF; // 低字节
EEPROM_Write(0x2000, high_byte);
EEPROM_Write(0x2001, low_byte);
}
// 从EEPROM读取计数值
unsigned int Load_Count_From_EEPROM(void)
{
unsigned char high_byte, low_byte;
unsigned int count;
high_byte = EEPROM_Read(0x2000);
low_byte = EEPROM_Read(0x2001);
count = (high_byte << 8) | low_byte;
return count;
}
5.2 增加蜂鸣器提示
sbit BUZZER = P1^5; // 蜂鸣器引脚
void Beep_Alert(unsigned char times)
{
unsigned char i;
for(i = 0; i < times; i++)
{
BUZZER = 0; // 蜂鸣器响
Delay_Ms(100);
BUZZER = 1; // 蜂鸣器停
Delay_Ms(100);
}
}
// 在计数达到极限时报警
void Count_Check_With_Alert(void)
{
if(count_value >= max_count)
{
Beep_Alert(3); // 连续响3声
}
if(count_value <= min_count)
{
Beep_Alert(2); // 连续响2声
}
}
方案二:Arduino数码管加减计数系统
一、硬件设计
1.1 材料清单
| 材料 |
型号 |
数量 |
| Arduino板 |
Arduino UNO |
1 |
| 数码管 |
4位共阳数码管 |
1 |
| 按键 |
轻触开关 |
3 |
| 电阻 |
220Ω |
8 |
| 电阻 |
10kΩ |
3 |
| 面包板 |
830孔 |
1 |
| 杜邦线 |
公对公 |
若干 |
1.2 接线图
Arduino UNO 数码管
----------- -------
2-5 -> 段选a,b,c,d,e,f,g,dp
6-9 -> 位选1,2,3,4
10,11,12 -> 按键(加,减,清零)
GND -> 公共端
+5V -> 电源
二、软件设计
2.1 Arduino主程序 (counter.ino)
/*
* 数码管加减计数显示系统
* 功能:4位数码管显示,按键控制加减计数
*/
#include "display.h"
#include "keyboard.h"
// 全局变量
unsigned int countValue = 0;
unsigned int maxCount = 9999;
unsigned int minCount = 0;
bool countUpEnable = true;
bool countDownEnable = true;
void setup() {
Serial.begin(9600);
// 初始化数码管
Display_Init();
// 初始化按键
Keyboard_Init();
// 显示初始值
Display_Number(countValue);
Serial.println("Counter System Started!");
}
void loop() {
// 按键处理
unsigned char key = Key_Scan();
switch(key) {
case KEY_ADD:
if(countUpEnable && countValue < maxCount) {
countValue++;
Display_Number(countValue);
Serial.print("Count Up: ");
Serial.println(countValue);
}
break;
case KEY_SUB:
if(countDownEnable && countValue > minCount) {
countValue--;
Display_Number(countValue);
Serial.print("Count Down: ");
Serial.println(countValue);
}
break;
case KEY_CLEAR:
countValue = 0;
Display_Number(countValue);
Serial.println("Counter Cleared!");
break;
}
// 检查计数范围
Check_Count_Range();
// 数码管动态扫描(放在主循环中)
Display_Scan();
delay(10); // 延时10ms
}
void Check_Count_Range() {
// 检查上限
if(countValue >= maxCount) {
countUpEnable = false;
digitalWrite(13, HIGH); // LED亮表示达到上限
} else {
countUpEnable = true;
digitalWrite(13, LOW); // LED灭
}
// 检查下限
if(countValue <= minCount) {
countDownEnable = false;
} else {
countDownEnable = true;
}
}
2.2 数码管显示 (display.h)
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#include <Arduino.h>
// 引脚定义
#define SEG_A 2
#define SEG_B 3
#define SEG_C 4
#define SEG_D 5
#define SEG_E 6
#define SEG_F 7
#define SEG_G 8
#define SEG_DP 9
#define DIGIT_1 10
#define DIGIT_2 11
#define DIGIT_3 12
#define DIGIT_4 13
// 数字段码表(共阳)
extern const byte SegCode[10];
// 函数声明
void Display_Init(void);
void Display_Number(unsigned int num);
void Display_Scan(void);
void Display_Clear(void);
#endif
2.3 数码管显示 (display.cpp)
#include "display.h"
// 共阳数码管段码表
const byte SegCode[10] = {
0xC0, // 0
0xF9, // 1
0xA4, // 2
0xB0, // 3
0x99, // 4
0x92, // 5
0x82, // 6
0xF8, // 7
0x80, // 8
0x90 // 9
};
byte displayBuffer[4] = {0xC0, 0xC0, 0xC0, 0xC0};
byte scanPosition = 0;
void Display_Init(void) {
// 设置段选引脚为输出
pinMode(SEG_A, OUTPUT);
pinMode(SEG_B, OUTPUT);
pinMode(SEG_C, OUTPUT);
pinMode(SEG_D, OUTPUT);
pinMode(SEG_E, OUTPUT);
pinMode(SEG_F, OUTPUT);
pinMode(SEG_G, OUTPUT);
pinMode(SEG_DP, OUTPUT);
// 设置位选引脚为输出
pinMode(DIGIT_1, OUTPUT);
pinMode(DIGIT_2, OUTPUT);
pinMode(DIGIT_3, OUTPUT);
pinMode(DIGIT_4, OUTPUT);
// 初始状态:所有位关闭
digitalWrite(DIGIT_1, HIGH);
digitalWrite(DIGIT_2, HIGH);
digitalWrite(DIGIT_3, HIGH);
digitalWrite(DIGIT_4, HIGH);
}
void Display_Number(unsigned int num) {
// 分解各位数字
byte thousand = num / 1000;
byte hundred = (num % 1000) / 100;
byte ten = (num % 100) / 10;
byte unit = num % 10;
// 存入显示缓冲区
displayBuffer[0] = SegCode[thousand];
displayBuffer[1] = SegCode[hundred];
displayBuffer[2] = SegCode[ten];
displayBuffer[3] = SegCode[unit];
}
void Display_Scan(void) {
// 关闭所有位选
digitalWrite(DIGIT_1, HIGH);
digitalWrite(DIGIT_2, HIGH);
digitalWrite(DIGIT_3, HIGH);
digitalWrite(DIGIT_4, HIGH);
// 根据扫描位置显示对应位
switch(scanPosition) {
case 0: // 千位
digitalWrite(SEG_A, (displayBuffer[0] & 0x01) ? LOW : HIGH);
digitalWrite(SEG_B, (displayBuffer[0] & 0x02) ? LOW : HIGH);
digitalWrite(SEG_C, (displayBuffer[0] & 0x04) ? LOW : HIGH);
digitalWrite(SEG_D, (displayBuffer[0] & 0x08) ? LOW : HIGH);
digitalWrite(SEG_E, (displayBuffer[0] & 0x10) ? LOW : HIGH);
digitalWrite(SEG_F, (displayBuffer[0] & 0x20) ? LOW : HIGH);
digitalWrite(SEG_G, (displayBuffer[0] & 0x40) ? LOW : HIGH);
digitalWrite(SEG_DP, (displayBuffer[0] & 0x80) ? LOW : HIGH);
digitalWrite(DIGIT_1, LOW); // 选中千位
break;
case 1: // 百位
digitalWrite(SEG_A, (displayBuffer[1] & 0x01) ? LOW : HIGH);
digitalWrite(SEG_B, (displayBuffer[1] & 0x02) ? LOW : HIGH);
digitalWrite(SEG_C, (displayBuffer[1] & 0x04) ? LOW : HIGH);
digitalWrite(SEG_D, (displayBuffer[1] & 0x08) ? LOW : HIGH);
digitalWrite(SEG_E, (displayBuffer[1] & 0x10) ? LOW : HIGH);
digitalWrite(SEG_F, (displayBuffer[1] & 0x20) ? LOW : HIGH);
digitalWrite(SEG_G, (displayBuffer[1] & 0x40) ? LOW : HIGH);
digitalWrite(SEG_DP, (displayBuffer[1] & 0x80) ? LOW : HIGH);
digitalWrite(DIGIT_2, LOW); // 选中百位
break;
case 2: // 十位
digitalWrite(SEG_A, (displayBuffer[2] & 0x01) ? LOW : HIGH);
digitalWrite(SEG_B, (displayBuffer[2] & 0x02) ? LOW : HIGH);
digitalWrite(SEG_C, (displayBuffer[2] & 0x04) ? LOW : HIGH);
digitalWrite(SEG_D, (displayBuffer[2] & 0x08) ? LOW : HIGH);
digitalWrite(SEG_E, (displayBuffer[2] & 0x10) ? LOW : HIGH);
digitalWrite(SEG_F, (displayBuffer[2] & 0x20) ? LOW : HIGH);
digitalWrite(SEG_G, (displayBuffer[2] & 0x40) ? LOW : HIGH);
digitalWrite(SEG_DP, (displayBuffer[2] & 0x80) ? LOW : HIGH);
digitalWrite(DIGIT_3, LOW); // 选中十位
break;
case 3: // 个位
digitalWrite(SEG_A, (displayBuffer[3] & 0x01) ? LOW : HIGH);
digitalWrite(SEG_B, (displayBuffer[3] & 0x02) ? LOW : HIGH);
digitalWrite(SEG_C, (displayBuffer[3] & 0x04) ? LOW : HIGH);
digitalWrite(SEG_D, (displayBuffer[3] & 0x08) ? LOW : HIGH);
digitalWrite(SEG_E, (displayBuffer[3] & 0x10) ? LOW : HIGH);
digitalWrite(SEG_F, (displayBuffer[3] & 0x20) ? LOW : HIGH);
digitalWrite(SEG_G, (displayBuffer[3] & 0x40) ? LOW : HIGH);
digitalWrite(SEG_DP, (displayBuffer[3] & 0x80) ? LOW : HIGH);
digitalWrite(DIGIT_4, LOW); // 选中个位
break;
}
// 更新扫描位置
scanPosition = (scanPosition + 1) % 4;
}
void Display_Clear(void) {
digitalWrite(DIGIT_1, HIGH);
digitalWrite(DIGIT_2, HIGH);
digitalWrite(DIGIT_3, HIGH);
digitalWrite(DIGIT_4, HIGH);
}
2.4 键盘扫描 (keyboard.h)
#ifndef __KEYBOARD_H__
#define __KEYBOARD_H__
#include <Arduino.h>
// 按键定义
#define KEY_ADD 1
#define KEY_SUB 2
#define KEY_CLEAR 3
#define KEY_NONE 0
// 按键引脚
#define KEY_ADD_PIN 10
#define KEY_SUB_PIN 11
#define KEY_CLEAR_PIN 12
// 函数声明
void Keyboard_Init(void);
unsigned char Key_Scan(void);
#endif
2.5 键盘扫描 (keyboard.cpp)
#include "keyboard.h"
void Keyboard_Init(void) {
pinMode(KEY_ADD_PIN, INPUT_PULLUP);
pinMode(KEY_SUB_PIN, INPUT_PULLUP);
pinMode(KEY_CLEAR_PIN, INPUT_PULLUP);
}
unsigned char Key_Scan(void) {
// 检测加计数键
if(digitalRead(KEY_ADD_PIN) == LOW) {
delay(50); // 消抖
if(digitalRead(KEY_ADD_PIN) == LOW) {
while(digitalRead(KEY_ADD_PIN) == LOW); // 等待释放
return KEY_ADD;
}
}
// 检测减计数键
if(digitalRead(KEY_SUB_PIN) == LOW) {
delay(50);
if(digitalRead(KEY_SUB_PIN) == LOW) {
while(digitalRead(KEY_SUB_PIN) == LOW);
return KEY_SUB;
}
}
// 检测清零键
if(digitalRead(KEY_CLEAR_PIN) == LOW) {
delay(50);
if(digitalRead(KEY_CLEAR_PIN) == LOW) {
while(digitalRead(KEY_CLEAR_PIN) == LOW);
return KEY_CLEAR;
}
}
return KEY_NONE;
}
参考代码 数码管加减计数显示设计 www.youwenfan.com/contentcnv/118008.html
三、使用说明
3.1 操作步骤
- 按"加"键:计数值加1
- 按"减"键:计数值减1
- 按"清零"键:计数值归零
- 达到最大值9999时,加计数停止
- 达到最小值0时,减计数停止
3.2 调试技巧
- 先测试数码管显示单个数字
- 再测试动态扫描显示多位数字
- 然后测试按键功能
- 最后整合所有功能
3.3 常见问题解决
| 问题 |
解决方法 |
| 数码管不亮 |
检查共阳/共阴类型,确认限流电阻 |
| 显示乱码 |
检查段码表是否正确,引脚连接 |
| 按键不灵敏 |
增加消抖延时,检查上拉电阻 |
| 计数不准 |
检查变量类型,防止溢出 |