四位共阴极数码管?用Arduino一个代码玩懂它!

1.前言

你是否因为大一迷惘而焦虑??
这个实验教程献给所有大一想努力的新手

2.欣赏成果

成果

3.安装对应软件

首先先安装对应软件Arduino

网址arduino.cc/en/software

4.学习软件的使用

安装结束,我们进入首页选择我们对应的开发板Arduino UNO

在这里插入图片描述

选择之后就会将UNO开发板作为默认**(UNO开发板适合初学者简单易上手)**

在这里插入图片描述

并将开发板连接到电脑**(注意:后面一定带有后缀,我前面买的盗版无法识别就没有后缀)**

如果没有出现开发板(com)请检查链接器是否连接好或者开发板是否接触不良

准备道具

– Arduino Uno控制器
– 1个四位共阴数码管(注意分清阴阳)
– 8个220 欧姆电阻
– 1个按键
– 1个面包版
–条线若

如何区别共阴和共阳极数码管

共阳极:标注 Common Anode(简称 CA);
共阴极:标注 Common Cathode(简称 CC);

共阳数码管

是指将所有发光二极管的阳极接到一起形成公共阳极 (COM) 的数码管,共阳数码管在应用时应将公共极 COM 接到 + 5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。

共阴数码管

是指将所有发光二极管的阴极接到一起形成公共阴极 COM) 的数码管,共阴数码管在应用时应将公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮
我们本次实验使用共阳极管
在这里插入图片描述
在这里插入图片描述

注意加上按钮

6.代码

这个代码存在bug(但是也是跑)

/*
 * 项目名称:Arduino四位数码管计时系统 (初代共阴极版)
 * 硬件配置:
 * - 段选 A-H: D2-D9
 * - 位选 COM1-4: D10-D13
 * - 按键: D1
 * 状态:存在阻塞Bug
 * 说明:按住按键不放时,数码管会熄灭
 */

// 按键定义
#define KEY_PIN 1

// 数码管段选引脚 (a, b, c, d, e, f, g, dp) -> (2, 3, 4, 5, 6, 7, 8, 9)
int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9}; 

// 数码管位选引脚 (千, 百, 十, 个) -> (COM1, COM2, COM3, COM4) -> (10, 11, 12, 13)
int segPins[] = {10, 11, 12, 13};

// 共阴极段码表 (0-9)
// 1(HIGH) 亮
const unsigned char DuanMa[10] = {
  0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f
};

unsigned char displayTemp[4]; 
bool countEnable = false;    
int currentNumber = 0;   

// 按键状态记录
bool lastKeyState = LOW;
bool currentKeyState = LOW;

void setup() {
  // 初始化段选
  for (int i = 0; i < 8; i++) pinMode(ledPins[i], OUTPUT);
  
  // 初始化位选
  for (int i = 0; i < 4; i++) {
    pinMode(segPins[i], OUTPUT);
    digitalWrite(segPins[i], HIGH); // 共阴初始关闭
  }
  
  pinMode(KEY_PIN, INPUT); 
  updateDisplayBuffer(0);
}

// ❌ 存在问题的按键处理函数
void handleKeyPress() {
  currentKeyState = digitalRead(KEY_PIN);
  
  // 检测按下瞬间
  if (currentKeyState == HIGH && lastKeyState == LOW) {
    delay(20); // 阻塞1:延时消抖
    
    // 再次确认
    if (digitalRead(KEY_PIN) == HIGH) {
      countEnable = !countEnable;
      
      // ☠️ 阻塞2:死循环等待按键松开
      // 只要你不松手,程序就永远卡在这里,回不到 loop()
      while(digitalRead(KEY_PIN) == HIGH); 
    }
  }
  lastKeyState = currentKeyState;
}

void loop() {
  // 1. 处理按键
  handleKeyPress(); 
  
  // 2. 计时逻辑
  static unsigned long lastTimerTime = 0;
  if(countEnable && millis() - lastTimerTime >= 1000) {
    lastTimerTime = millis();
    currentNumber = (currentNumber + 1) % 10000;
    updateDisplayBuffer(currentNumber);
  }

  // 3. 动态扫描
  // 如果 handleKeyPress 卡住了,这里就不会执行 -> 数码管熄灭
  refreshDisplay();
}

void updateDisplayBuffer(int num) {
  displayTemp[0] = DuanMa[num / 1000];          
  displayTemp[1] = DuanMa[(num % 1000) / 100];  
  displayTemp[2] = DuanMa[(num % 100) / 10];    
  displayTemp[3] = DuanMa[num % 10];            
}

void refreshDisplay() {
  static unsigned long lastScanTime = 0;
  static int currentDigit = 0;
  if(millis() - lastScanTime >= 3) {
    lastScanTime = millis();
    
    // 消影
    for(int i=0; i<8; i++) digitalWrite(ledPins[i], LOW);
    digitalWrite(segPins[currentDigit], HIGH); // 关位选
    
    currentDigit = (currentDigit + 1) % 4;
    
    digitalWrite(segPins[currentDigit], LOW); // 开新位选
    for(int i=0; i<8; i++) digitalWrite(ledPins[i], bitRead(displayTemp[currentDigit], i));
  }
}

这个代码有bug,虽然可以使用但是容易造成堵塞原代码的核心 Bug(按键阻塞导致数码管熄灭)

阻塞点 1:delay(20)

问题:delay()会暂停整个程序 20ms,期间无法执行动态扫描refreshDisplay(),数码管会短暂闪烁 / 变暗;

阻塞点 2:while(digitalRead(KEY_PIN) == HIGH)

问题:这是 “死循环等待”—— 只要按键不松开,程序就永远卡在这个循环里,完全无法回到loop()执行refreshDisplay(),数码管直接熄灭。

修改后完整代码如下

/*
 * 修复版:Arduino四位数码管计时系统(共阴极,非阻塞)
 * 硬件配置:
 * - 段选 A-H: D2-D9
 * - 位选 COM1-4: D10-D13
 * - 按键: D1
 * 修复点:
 * 1. 移除delay/while阻塞,改用millis()防抖
 * 2. 按键松开才触发状态切换,避免卡死
 */

// 按键定义
#define KEY_PIN 1

// 数码管段选引脚 (a, b, c, d, e, f, g, dp) -> (2, 3, 4, 5, 6, 7, 8, 9)
int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9}; 

// 数码管位选引脚 (千, 百, 十, 个) -> (COM1, COM2, COM3, COM4) -> (10, 11, 12, 13)
int segPins[] = {10, 11, 12, 13};

// 共阴极段码表 (0-9):1(HIGH)亮,0(LOW)灭
const unsigned char DuanMa[10] = {
  0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f
};

unsigned char displayTemp[4];  // 显示缓冲区(存4位段码)
bool countEnable = false;      // 计时使能标志
int currentNumber = 0;         // 当前显示数字

// 按键防抖相关
unsigned long keyDebounceTime = 0;  // 防抖时间戳
const unsigned long debounceDelay = 20; // 防抖延时20ms
bool lastKeyState = LOW;            // 上一次按键状态

void setup() {
  // 初始化段选引脚(输出)
  for (int i = 0; i < 8; i++) {
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], LOW);
  }
  
  // 初始化位选引脚(输出,共阴初始HIGH关闭)
  for (int i = 0; i < 4; i++) {
    pinMode(segPins[i], OUTPUT);
    digitalWrite(segPins[i], HIGH);
  }
  
  // 初始化按键引脚(INPUT默认高阻,建议改INPUT_PULLUP更稳定)
  pinMode(KEY_PIN, INPUT); 
  updateDisplayBuffer(0); // 初始显示0
}

// ✅ 修复后:非阻塞按键处理(无delay/while)
void handleKeyPress() {
  bool currentKeyState = digitalRead(KEY_PIN);
  unsigned long currentTime = millis();

  // 1. 防抖:只有状态变化且超过防抖时间才处理
  if (currentKeyState != lastKeyState && currentTime - keyDebounceTime > debounceDelay) {
    keyDebounceTime = currentTime; // 更新防抖时间戳
    
    // 2. 检测「按键松开」事件(避免按住时重复触发)
    if (currentKeyState == LOW && lastKeyState == HIGH) {
      countEnable = !countEnable; // 切换计时状态(开始/暂停)
    }
  }
  
  lastKeyState = currentKeyState; // 更新上一次按键状态
}

void loop() {
  // 1. 非阻塞按键处理(永不卡死)
  handleKeyPress(); 
  
  // 2. 非阻塞计时逻辑(1秒递增)
  static unsigned long lastTimerTime = 0;
  if (countEnable && millis() - lastTimerTime >= 1000) {
    lastTimerTime = millis();
    currentNumber = (currentNumber + 1) % 10000; // 0~9999循环
    updateDisplayBuffer(currentNumber); // 更新显示缓冲区
  }

  // 3. 动态扫描显示(高频执行,永不阻塞)
  refreshDisplay();
}

// ✅ 拆分数字+填充显示缓冲区(核心逻辑)
void updateDisplayBuffer(int num) {
  // 提取千位:num ÷ 1000(整数除法)
  displayTemp[0] = DuanMa[num / 1000];          
  // 提取百位:先取余1000去掉千位,再÷100
  displayTemp[1] = DuanMa[(num % 1000) / 100];  
  // 提取十位:先取余100去掉千/百位,再÷10
  displayTemp[2] = DuanMa[(num % 100) / 10];    
  // 提取个位:取余10
  displayTemp[3] = DuanMa[num % 10];            
}

// ✅ 非阻塞动态扫描(避免闪烁)
void refreshDisplay() {
  static unsigned long lastScanTime = 0;
  static int currentDigit = 0; // 当前扫描的位(0=千,1=百,2=十,3=个)
  unsigned long currentTime = millis();

  // 3ms扫描一次(人眼无闪烁)
  if (currentTime - lastScanTime >= 3) {
    lastScanTime = currentTime;
    
    // 1. 消影:先关闭所有位选+段选
    digitalWrite(segPins[currentDigit], HIGH); // 关闭当前位
    for (int i = 0; i < 8; i++) {
      digitalWrite(ledPins[i], LOW); // 段选清零
    }
    
    // 2. 切换到下一位(循环0-3)
    currentDigit = (currentDigit + 1) % 4;
    
    // 3. 输出当前位的段码
    digitalWrite(segPins[currentDigit], LOW); // 打开当前位(共阴低电平有效)
    for (int i = 0; i < 8; i++) {
      // bitRead:提取段码的第i位(0/1),控制对应段脚亮灭
      digitalWrite(ledPins[i], bitRead(displayTemp[currentDigit], i));
    }
  }
}

如果是阳极管代码则是

/*
 * 项目名称:Arduino四位数码管计时系统 (共阳极版)
 * 硬件配置:
 * - 段选 A-H: D2-D9
 * - 位选 COM1-4: D10-D13
 * - 按键: D1
 * 功能:修复阻塞Bug + 适配共阳极数码管
 */

// 按键定义
#define KEY_PIN 1

// 数码管段选引脚 (a, b, c, d, e, f, g, dp) -> (2, 3, 4, 5, 6, 7, 8, 9)
int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9}; 

// 数码管位选引脚 (千, 百, 十, 个) -> (COM1, COM2, COM3, COM4) -> (10, 11, 12, 13)
int segPins[] = {10, 11, 12, 13};

// 共阳极段码表 (0-9) 低电平点亮,段码取反
// 0(0x3F)->0xC0, 1(0x06)->0xF9, 2(0x5B)->0xA4, 3(0x4F)->0xB0, 4(0x66)->0x99
// 5(0x6D)->0x92, 6(0x7D)->0x82, 7(0x07)->0xF8, 8(0x7F)->0x80, 9(0x6F)->0x90
const unsigned char DuanMa[10] = {
  0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90
};

unsigned char displayTemp[4]; 
bool countEnable = false;    
int currentNumber = 0;   

// 按键状态记录
bool lastKeyState = LOW;
bool currentKeyState = LOW;
unsigned long lastDebounceTime = 0; // 防抖时间戳

void setup() {
  // 初始化段选
  for (int i = 0; i < 8; i++) {
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], HIGH); // 共阳极初始熄灭(高电平)
  }
  
  // 初始化位选(共阳极位选高电平有效,初始关闭所有位)
  for (int i = 0; i < 4; i++) {
    pinMode(segPins[i], OUTPUT);
    digitalWrite(segPins[i], LOW); // 共阳极初始关闭(低电平)
  }
  
  pinMode(KEY_PIN, INPUT_PULLUP); // 改为内部上拉(更稳定)
  updateDisplayBuffer(0);
}

// 优化版按键处理(非阻塞)
void handleKeyPress() {
  unsigned long currentTime = millis();
  // 防抖间隔20ms
  if (currentTime - lastDebounceTime < 20) return;
  
  currentKeyState = digitalRead(KEY_PIN);
  
  // 检测按下瞬间(INPUT_PULLUP 按下为LOW)
  if (currentKeyState == LOW && lastKeyState == HIGH) {
    countEnable = !countEnable;
    lastDebounceTime = currentTime; // 更新防抖时间
  }
  
  lastKeyState = currentKeyState;
}

void loop() {
  // 1. 处理按键(非阻塞)
  handleKeyPress(); 
  
  // 2. 计时逻辑
  static unsigned long lastTimerTime = 0;
  if(countEnable && millis() - lastTimerTime >= 1000) {
    lastTimerTime = millis();
    currentNumber = (currentNumber + 1) % 10000;
    updateDisplayBuffer(currentNumber);
  }

  // 3. 动态扫描(核心显示逻辑)
  refreshDisplay();
}

// 更新显示缓冲区
void updateDisplayBuffer(int num) {
  displayTemp[0] = DuanMa[num / 1000];          // 千位
  displayTemp[1] = DuanMa[(num % 1000) / 100];  // 百位
  displayTemp[2] = DuanMa[(num % 100) / 10];    // 十位
  displayTemp[3] = DuanMa[num % 10];            // 个位
}

// 共阳极动态扫描刷新(适配阳极电平)
void refreshDisplay() {
  static unsigned long lastScanTime = 0;
  static int currentDigit = 0;
  
  if(millis() - lastScanTime >= 3) {
    lastScanTime = millis();
    
    // 消影:先关闭所有段选和当前位选
    for(int i=0; i<8; i++) digitalWrite(ledPins[i], HIGH); // 段选熄灭
    digitalWrite(segPins[currentDigit], LOW); // 关闭当前位选(共阳极低电平关闭)
    
    // 切换到下一位
    currentDigit = (currentDigit + 1) % 4;
    
    // 点亮新位
    digitalWrite(segPins[currentDigit], HIGH); // 开启当前位(共阳极高电平有效)
    for(int i=0; i<8; i++) {
      // 共阳极:段码bit为0时点亮(输出LOW),bit为1时熄灭(输出HIGH)
      digitalWrite(ledPins[i], bitRead(displayTemp[currentDigit], i) ? HIGH : LOW);
    }
  }
}

并且将GND和5v的位置调换

修改后代码的好处

按键非阻塞:

移除delay(20)和while死循环,改用millis()记录时间戳,防抖同时不暂停程序;
只在 “按键松开” 时触发状态切换,避免按住按键时重复触发。

扫描永不中断:

refreshDisplay()在loop()中高频执行,即使按键按住,数码管也能正常显示;

稳定性提升:

增加 “消影” 逻辑,避免多位数码管之间的串影 / 重影;
按键状态仅在 “状态变化 + 防抖超时” 时处理,误触发率大幅降低。

接下验证 ,左上角的勾是验证符号

在这里插入图片描述

查看是否验证成功,然后上传

在这里插入图片描述

上传成功就可以欣赏到开头的结果了

7.心得

我认为这是个新的开始我也遇到了很多问题(见下面),但是当我成功做出来了后感到非常自豪,所以我认为在这个时代人类要不断走出自己的舒适圈,才能前进,如果觉得有帮助就请点一下赞,我火速更新下一章,我是FOX,励志做一个博学的萌新。

8.花絮

在看到代码的时候,我就已经在想代码有什么可以修改的了,于是这些问题就诞生了

为什么会用millis()记录时间戳

用millis()替代delay:通过时间戳记录按键状态变化的时间,超过防抖阈值才处理,不阻塞程序;
检测 “松开边缘”:仅在「按键从按下(HIGH)变为松开(LOW)」时触发逻辑,避免按住时重复触发;
无while死循环:彻底移除等待按键松开的死循环,保证loop()持续运行,数码管扫描不中断。

在这里插入图片描述
溢出问题:millis() 约 49.7 天后会从 4294967295 重置为 0,此时 currentTime - lastTime 仍能正确计算(无符号数减法特性),无需额外处理。

// 按键防抖+非阻塞配置
unsigned long keyDebounceTime = 0;    // 按键防抖时间戳
const unsigned long debounceDelay = 20; // 防抖阈值(20ms)
bool lastKeyState = HIGH;             // 上一次按键状态(初始高电平,适配INPUT_PULLUP)
bool keyTriggered = false;            // 避免重复触发的标志

// ✅ 非阻塞按键处理:仅松开触发,无delay/while
void handleKeyPress() {
  // 1. 读取当前按键状态(建议按键接INPUT_PULLUP,按下为LOW,松开为HIGH)
  bool currentKeyState = digitalRead(KEY_PIN);
  unsigned long currentTime = millis();

  // 2. 防抖判断:状态变化且超过防抖时间,才处理
  if (currentKeyState != lastKeyState && currentTime - keyDebounceTime > debounceDelay) {
    keyDebounceTime = currentTime; // 更新防抖时间戳

    // 3. 检测「按键松开」边缘(从按下→松开)
    // 若用INPUT(非上拉),则判断条件改为:currentKeyState == HIGH && lastKeyState == LOW
    if (currentKeyState == HIGH && lastKeyState == LOW) { 
      countEnable = !countEnable; // 切换计时状态(仅松开时触发一次)
      keyTriggered = true;        // 标记已触发,避免重复
    }
  }

  // 4. 更新上一次按键状态
  lastKeyState = currentKeyState;
}

经典知识点回顾

for()

for()是我们常见的循环,这这个代码中甚至可以看到很多for循环

for (int i = 0; i < 8; i++) {
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], LOW);
  }

然而我们可以通过for循环变成其他循环比如while()和do…while()

分别是while()

// 替换原for循环:用while初始化段选引脚
int i = 0; // 初始化循环变量
while (i < 8) { // 条件:i小于8时执行
  pinMode(ledPins[i], OUTPUT);
  digitalWrite(ledPins[i], LOW);
  i++; // 循环变量自增,避免死循环
}

先定义循环变量 i 并赋值为 0;
判断 i < 8,满足则执行循环体(配置引脚 + 拉低电平);
每次循环结束后 i++,直到 i=8 时条件不满足,退出循环

do…while()

// 替换原for循环:用do...while初始化段选引脚
int i = 0; // 初始化循环变量
do {
  pinMode(ledPins[i], OUTPUT);
  digitalWrite(ledPins[i], LOW);
  i++; // 循环变量自增
} while (i < 8); // 条件:i小于8时继续循环

先执行一次循环体(即使条件不满足,也会先执行);
执行完后判断 i < 8,满足则继续循环,不满足则退出;
此处 i 初始为 0,最终执行逻辑和 for/while 完全一致(执行 8 次)
两者的区别则是do…while 至少执行一次循环体,适合 “必须执行一次” 的场景,while则不用。
在这里插入图片描述

制作不易点赞收藏加关注,关注主播不迷路

### 连接阴极七段数码管 阴极七段数码管的所有LED的阴极连接在一起并接地,而各个段的阳极单独引出控制信号[^4]。在使用Arduino驱动时,需要将每个段的阳极通过限流电阻(通常为220Ω或330Ω)连接到Arduino的数字输出引脚。 以下是具体的电路连接方式: - 数码管的a~g七个段分别通过限流电阻接到Arduino的数字引脚。 - 小数点段(dp)可以连接到另一个数字引脚,但若不使用可悬空。 - 阴极端子(COM)连接到电源负极(GND)。 例如,将a~g分别连接到数字引脚2~8,并确保每个段都串联了适当的限流电阻。 ### 编程实现数字显示 编写代码时,根据需要显示的数字设置对应的段为高电平(点亮)或低电平(熄灭)。以下是一个示例程序,用于显示数字“5”: ```arduino int a = 2; int b = 3; int c = 4; int d = 5; int e = 6; int f = 7; int g = 8; void setup() { pinMode(a, OUTPUT); pinMode(b, OUTPUT); pinMode(c, OUTPUT); pinMode(d, OUTPUT); pinMode(e, OUTPUT); pinMode(f, OUTPUT); pinMode(g, OUTPUT); // 显示数字5:a, f, g, c, d 段亮 digitalWrite(a, HIGH); digitalWrite(b, LOW); digitalWrite(c, HIGH); digitalWrite(d, HIGH); digitalWrite(e, LOW); digitalWrite(f, HIGH); digitalWrite(g, HIGH); } void loop() { // 循环保持状态 } ``` ### 使用译码芯片简化控制 为了简化编程和减少使用的Arduino引脚数量,可以使用如74HC595移位寄存器或CD4511等专用译码驱动芯片。这些芯片可以通过硬件逻辑将BCD码转换为对应的段码输出,从而直接控制数码管显示相应的数字。 例如,使用CD4511时,只需提供四位二进制输入(代表要显示的数字0~9),即可自动点亮相应的段[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值