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); }
关键细节说明
- 状态机设计:用枚举把每个流程阶段拆成独立状态,代码逻辑更清晰,不会像原来那样把所有操作堆在loop里,后续维护也方便。
- 非阻塞定时:用
millis()记录每个状态的开始时间,通过currentMillis - stateStartTime计算时间差,完全替代delay(),程序全程能响应按钮输入。 - 按钮检测:在7秒检测窗口里,每次loop都会读取按钮状态,不会错过按下动作(注意这里用了
INPUT_PULLUP,所以按钮按下时引脚是LOW,你需要根据实际接线调整)。 - 原函数复用:保留了你原来的
B_change_1()和B_change_2(),如果想要完全非阻塞,可以把这些函数里的delay()也改成状态机逻辑,不过目前这样已经能满足需求。
其他可行方案
如果你想试试其他方法,外部中断也是一个选择:
- 给按钮引脚设置上升/下降沿中断,当按钮按下时触发中断服务函数。
- 但需要配合定时器(比如用
millis()或者定时器中断)来控制“3秒后开启7秒检测窗口”的逻辑——比如在3秒后允许中断触发,7秒后禁止中断。这个方法适合对响应速度要求极高的场景,但比状态机复杂一些,新手还是推荐用上面的millis()状态机方案。
内容的提问来源于stack exchange,提问作者Ester Cruz
相关产品推荐
相关产品推荐

