You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Arduino交通灯项目:如何检测按钮在指定时间间隔内被按下?

解决Arduino交通灯项目的行人按钮检测问题

嘿,我来帮你搞定这个交通灯的按钮检测难题!你之前用delay()和for循环的方式行不通,核心原因是delay()会完全阻塞程序——在延迟期间,Arduino没法持续检测按钮状态,而且你只在延迟后读了一次按钮值,那7秒窗口里根本没重新检查按钮有没有被按。咱们用非阻塞的millis()方法来解决,这是Arduino处理定时任务的标准操作,不会卡住程序,还能实时响应按钮。

核心思路:用状态机+millis()实现非阻塞定时

咱们把交通灯的流程拆成不同的状态,比如:

  • 初始灯态(街道1绿灯、街道2红灯)
  • 等待3秒(准备进入按钮检测窗口)
  • 7秒按钮检测窗口(实时检查按钮,按下就触发对应函数)
  • 正常灯态切换流程

用millis()记录每个状态的开始时间,每次循环计算时间差,判断是否进入下一个状态,全程不阻塞,这样就能实时检测按钮了。

修改后的完整代码

// 先定义引脚(你需要根据实际接线替换这些值)
const int str1_verd = 2;
const int str1_ama = 3;
const int str1_verm = 4;
const int str2_verd = 5;
const int str2_ama = 6;
const int str2_verm = 7;
const int ped1_verd = 8;
const int ped1_verm = 9;
const int ped2_verd = 10;
const int ped2_verm = 11;
const int button_street1 = 12;
const int button_street2 = 13;
const int buzzer = 14;

// 定义交通灯状态枚举,让流程更清晰
enum TrafficState {
  STATE1_INIT,          // 初始状态:街道1绿,街道2红
  STATE1_WAIT_3S,       // 等待3秒,准备进入按钮检测
  STATE1_CHECK_BUTTON,  // 7秒窗口检测行人按钮
  STATE1_TRANSITION,    // 正常切换到街道1红、街道2绿
  STATE2_INIT,          // 街道2绿,街道1红
  STATE2_WAIT_3S,       // 等待3秒
  STATE2_CHECK_BUTTON,  // 7秒窗口检测按钮
  STATE2_TRANSITION     // 切换回初始状态
};

TrafficState currentState = STATE1_INIT;
unsigned long stateStartTime = 0;
const unsigned long WAIT_3S = 3000;
const unsigned long CHECK_BUTTON_WINDOW = 7000;
const unsigned long TRANSITION_DELAY = 5000;

void setup() {
  // 初始化所有引脚为输出/输入
  pinMode(str1_verd, OUTPUT);
  pinMode(str1_ama, OUTPUT);
  pinMode(str1_verm, OUTPUT);
  pinMode(str2_verd, OUTPUT);
  pinMode(str2_ama, OUTPUT);
  pinMode(str2_verm, OUTPUT);
  pinMode(ped1_verd, OUTPUT);
  pinMode(ped1_verm, OUTPUT);
  pinMode(ped2_verd, OUTPUT);
  pinMode(ped2_verm, OUTPUT);
  pinMode(button_street1, INPUT_PULLUP); // 用内部上拉,省外部电阻
  pinMode(button_street2, INPUT_PULLUP);
  pinMode(buzzer, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();

  switch(currentState) {
    case STATE1_INIT:
      // 设置初始灯态
      digitalWrite(str1_verd, HIGH);
      digitalWrite(str1_ama, LOW);
      digitalWrite(str1_verm, LOW);
      digitalWrite(str2_verd, LOW);
      digitalWrite(str2_ama, LOW);
      digitalWrite(str2_verm, HIGH);
      digitalWrite(ped1_verd, LOW);
      digitalWrite(ped1_verm, HIGH);
      digitalWrite(ped2_verd, HIGH);
      digitalWrite(ped2_verm, LOW);
      // 记录状态开始时间,切换到等待3秒状态
      stateStartTime = currentMillis;
      currentState = STATE1_WAIT_3S;
      break;

    case STATE1_WAIT_3S:
      // 等待3秒后进入按钮检测窗口
      if(currentMillis - stateStartTime >= WAIT_3S) {
        stateStartTime = currentMillis;
        currentState = STATE1_CHECK_BUTTON;
      }
      break;

    case STATE1_CHECK_BUTTON:
      // 检测7秒窗口内的按钮状态
      if(digitalRead(button_street1) == LOW) { // 如果按钮被按下(INPUT_PULLUP下按下是LOW)
        B_change_1();
        // 执行完按钮函数后,直接切换到街道2的初始状态
        stateStartTime = currentMillis;
        currentState = STATE2_INIT;
        break;
      }
      // 如果7秒到了还没按,进入正常切换流程
      if(currentMillis - stateStartTime >= CHECK_BUTTON_WINDOW) {
        currentState = STATE1_TRANSITION;
        stateStartTime = currentMillis;
      }
      break;

    case STATE1_TRANSITION:
      // 正常灯态切换:街道1黄+街道2黄,持续5秒
      digitalWrite(str1_verd, LOW);
      digitalWrite(str1_ama, HIGH);
      digitalWrite(str2_ama, HIGH);
      if(currentMillis - stateStartTime >= TRANSITION_DELAY) {
        // 切换到街道2绿灯状态
        digitalWrite(str1_ama, LOW);
        digitalWrite(str2_ama, LOW);
        digitalWrite(str1_verm, HIGH);
        digitalWrite(str2_verm, LOW);
        digitalWrite(str2_verd, HIGH);
        digitalWrite(ped1_verm, LOW);
        digitalWrite(ped1_verd, HIGH);
        digitalWrite(ped2_verd, LOW);
        digitalWrite(ped2_verm, HIGH);
        stateStartTime = currentMillis;
        currentState = STATE2_INIT;
      }
      break;

    case STATE2_INIT:
      // 设置街道2绿灯、街道1红灯的灯态
      digitalWrite(str2_verd, HIGH);
      digitalWrite(str2_ama, LOW);
      digitalWrite(str2_verm, LOW);
      digitalWrite(str1_verd, LOW);
      digitalWrite(str1_ama, LOW);
      digitalWrite(str1_verm, HIGH);
      digitalWrite(ped2_verd, LOW);
      digitalWrite(ped2_verm, HIGH);
      digitalWrite(ped1_verd, HIGH);
      digitalWrite(ped1_verm, LOW);
      stateStartTime = currentMillis;
      currentState = STATE2_WAIT_3S;
      break;

    case STATE2_WAIT_3S:
      if(currentMillis - stateStartTime >= WAIT_3S) {
        stateStartTime = currentMillis;
        currentState = STATE2_CHECK_BUTTON;
      }
      break;

    case STATE2_CHECK_BUTTON:
      if(digitalRead(button_street2) == LOW) {
        B_change_2();
        stateStartTime = currentMillis;
        currentState = STATE1_INIT;
        break;
      }
      if(currentMillis - stateStartTime >= CHECK_BUTTON_WINDOW) {
        currentState = STATE2_TRANSITION;
        stateStartTime = currentMillis;
      }
      break;

    case STATE2_TRANSITION:
      digitalWrite(str2_verd, LOW);
      digitalWrite(str2_ama, HIGH);
      digitalWrite(str1_ama, HIGH);
      if(currentMillis - stateStartTime >= TRANSITION_DELAY) {
        digitalWrite(str2_ama, LOW);
        digitalWrite(str1_ama, LOW);
        digitalWrite(str2_verm, HIGH);
        digitalWrite(str1_verm, LOW);
        digitalWrite(str1_verd, HIGH);
        digitalWrite(ped2_verm, LOW);
        digitalWrite(ped2_verd, HIGH);
        digitalWrite(ped1_verd, LOW);
        digitalWrite(ped1_verm, HIGH);
        stateStartTime = currentMillis;
        currentState = STATE1_INIT;
      }
      break;
  }
}

// 行人按钮1触发的函数
void B_change_1(){
  // 执行灯态切换
  digitalWrite(str1_verd, LOW);
  digitalWrite(str1_ama, HIGH);
  digitalWrite(str2_ama, HIGH);
  delay(5000); // 这里如果不想阻塞,可以改成用状态机,但暂时保留你的逻辑
  digitalWrite(str1_ama, LOW);
  digitalWrite(str2_ama, LOW);
  digitalWrite(str2_verm, LOW);
  digitalWrite(str1_verm, HIGH);
  digitalWrite(str2_verd, HIGH);
  digitalWrite(ped2_verm, HIGH);
  digitalWrite(ped1_verm, LOW);
  digitalWrite(ped1_verd, HIGH);
  buzzer_alert();
  digitalWrite(ped2_verd, LOW);
}

// 行人按钮2触发的函数
void B_change_2(){
  digitalWrite(str2_verd, LOW);
  digitalWrite(str1_ama, HIGH);
  digitalWrite(str2_ama, HIGH);
  delay(5000);
  digitalWrite(str2_ama, LOW);
  digitalWrite(str1_ama, LOW);
  digitalWrite(str1_verm, LOW);
  digitalWrite(str2_verm, HIGH);
  digitalWrite(str1_verd, HIGH);
  digitalWrite(ped1_verm, HIGH);
  digitalWrite(ped2_verm, LOW);
  digitalWrite(ped2_verd, HIGH);
}

// 蜂鸣器提醒函数
void buzzer_alert(){
  tone(buzzer, 1000);
  delay(500);
  noTone(buzzer);
}

关键细节说明

  1. 状态机设计:用枚举把每个流程阶段拆成独立状态,代码逻辑更清晰,不会像原来那样把所有操作堆在loop里,后续维护也方便。
  2. 非阻塞定时:用millis()记录每个状态的开始时间,通过currentMillis - stateStartTime计算时间差,完全替代delay(),程序全程能响应按钮输入。
  3. 按钮检测:在7秒检测窗口里,每次loop都会读取按钮状态,不会错过按下动作(注意这里用了INPUT_PULLUP,所以按钮按下时引脚是LOW,你需要根据实际接线调整)。
  4. 原函数复用:保留了你原来的B_change_1()和B_change_2(),如果想要完全非阻塞,可以把这些函数里的delay()也改成状态机逻辑,不过目前这样已经能满足需求。

其他可行方案

如果你想试试其他方法,外部中断也是一个选择:

  • 给按钮引脚设置上升/下降沿中断,当按钮按下时触发中断服务函数。
  • 但需要配合定时器(比如用millis()或者定时器中断)来控制“3秒后开启7秒检测窗口”的逻辑——比如在3秒后允许中断触发,7秒后禁止中断。这个方法适合对响应速度要求极高的场景,但比状态机复杂一些,新手还是推荐用上面的millis()状态机方案。

内容的提问来源于stack exchange,提问作者Ester Cruz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:25:45