一·所需材料

 

二·接线图

  

三·代码

   

四·实物效果

   

    

五·拓展

一·所需材料

​​​​​​​

Arduino Uno控制器:1块
四位共阴数码管:1个(关键点:讲义教程多为共阳,如果你手头的是共阴,必须修改代码逻辑!)
按键开关:1个
10kΩ 电阻:1个(下拉电阻)
面包板及跳线:若干

二·接线图

三·代码​​​​​​​

​
/*
 * 项目名称: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));
  }
}

​
四·实物效果
五·拓展

for 循环最常用,但理解 while 和 do...while 的执行流程同样重要。

以上为for循环

​
for(int ledpin = 0; ledpin < ledCount; ledpin++) {
    pinMode(ledPins[ledpin], OUTPUT);
}

​

改为while循环便是:

​
int ledpin = 0; 
while(ledpin < ledCount) { 
    pinMode(ledPins[ledpin], OUTPUT);
    ledpin++; 
}

​

但如果是do...while的话

int ledpin = 0;
do {
    pinMode(ledPins[ledpin], OUTPUT);
    ledpin++;
} while(ledpin < ledCount);

以上不同语句的代码各有优势,

for循环

· 优势:最适合已知循环次数的场景
· 特点:将初始化、条件判断、迭代更新集中在一行,结构紧凑
· 典型场景:遍历数组、固定次数的计算

while循环

· 优势:最适合“先判断后执行”的逻辑
· 特点:条件检查在前,可能一次都不执行
· 典型场景:读取输入直到特定值、条件驱动的循环

do...while循环

· 优势:确保至少执行一次
· 特点:先执行循环体,后检查条件
· 典型场景:菜单显示、至少需要执行一次的操作

选择建议:

· 明确次数 → 用for
· 条件先行 → 用while
· 至少一次 → 用do...while

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐