显示7段数码管计数器时如何临时禁用中断?
能不能在显示时临时禁用中断?
可以,但必须把禁用中断的时间压缩到极短,否则会导致按键触发的中断丢失,甚至出现你遇到的计数器完全失效的问题。你之前的问题根源不是“禁用中断”本身,而是代码里的两个关键bug加上不合理的中断禁用时机。
你遇到的问题原因
中断服务函数的变量混淆错误
看handleInterruptd(递减按键的中断函数)里,最后更新的是startTime_ph——但这个变量是给递增按键(handleInterrupta)用的防抖时间戳,递减按键应该更新的是startTime_pm。这个错误会直接打乱递减按键的防抖逻辑,甚至干扰递增按键的判断,再加上你如果在显示时长时间关中断,防抖时间计算彻底混乱,自然计数器就停了。显示函数里的
delay(10)导致中断被长时间屏蔽
如果你在vizualiza开头就关中断,两个delay(10)加起来就是20ms,这段时间内按键触发的中断会被完全忽略,不仅防抖逻辑失效,按键操作根本传不到中断函数里,计数器当然没法递增/递减。共享变量的竞争问题
unidades(个位)和decenas(十位)是主循环(显示)和中断服务函数都要访问的变量,没有做保护的话,可能出现“中断正在修改变量的时候,主循环刚好读取”的情况,导致显示出错误的数值。但这个问题不需要用全局禁用中断来解决,用短时间的原子读取更高效。
修复方案
1. 先修复中断函数的变量错误
把handleInterruptd里的startTime_ph = millis();改成startTime_pm = millis();,这是解决计数器失效的核心第一步。
2. 用原子读取保护共享变量(替代全局关中断)
不需要在整个显示过程中关中断,只需要在读取unidades和decenas的瞬间暂时禁用中断,确保读到的是完整、未被中断修改的数值,之后立刻恢复中断:
void vizualiza() { // 原子读取:暂时关中断,确保读取到完整的数值 noInterrupts(); int temp_unidades = unidades; int temp_decenas = decenas; interrupts(); // 立刻恢复中断,不影响按键响应 unidades1= temp_unidades +16; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(unidades1, i)); } delay(10); decenas1= temp_decenas +32; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(decenas1, i)); } delay(10); }
这种方式下,中断被禁用的时间只有几微秒,几乎不会影响按键的响应。
3. 如果一定要在显示期间关中断(不推荐)
必须去掉阻塞式的delay,改用非阻塞的时间判断,让每次显示切换的时间极短,确保中断不会被长时间屏蔽:
unsigned long lastDisplayTime = 0; bool showUnits = true; // 标记当前显示个位还是十位 void vizualiza() { if(millis() - lastDisplayTime >= 10) { lastDisplayTime = millis(); // 原子读取变量 noInterrupts(); int temp_unidades = unidades; int temp_decenas = decenas; interrupts(); if(showUnits) { // 显示个位 unidades1= temp_unidades +16; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(unidades1, i)); } } else { // 显示十位 decenas1= temp_decenas +32; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(decenas1, i)); } } showUnits = !showUnits; // 切换显示位 } }
这种非阻塞的显示方式,每次显示更新只占用几微秒,中断被禁用的时间可以忽略,完全不会影响按键的响应。
4. 完整修复后的代码
int outArray[]={D2,D3,D4,D5,D6,D7}; int count=0, unidades=0, decenas=0, unidades1=0, decenas1=0; const int pulsadora=D0; const int pulsadord=D1; const int timeThreshold = 150; long startTime_pm = 0; long startTime_ph = 0; void IRAM_ATTR handleInterrupta() { if (millis() - startTime_ph > timeThreshold) { unidades++; if(unidades>9) { unidades=0; decenas++; } if(decenas>9) { decenas=0; } startTime_ph = millis(); } } void IRAM_ATTR handleInterruptd() { if (millis() - startTime_pm > timeThreshold) { unidades--; if(unidades<0) { unidades=9; decenas--; } if(decenas<0) { decenas=9; } // 修复:更新递减按键的防抖时间戳 startTime_pm = millis(); } } void vizualiza() { // 原子读取共享变量,避免中断干扰 noInterrupts(); int temp_unidades = unidades; int temp_decenas = decenas; interrupts(); unidades1= temp_unidades +16; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(unidades1, i)); } delay(10); decenas1= temp_decenas +32; for(int i=0; i<6; i++) { digitalWrite(outArray[i], bitRead(decenas1, i)); } delay(10); } void setup() { pinMode(pulsadora, INPUT); pinMode(pulsadord, INPUT); for(int i=0; i<6; i++) { pinMode(outArray[i], OUTPUT); } attachInterrupt(digitalPinToInterrupt(pulsadora),handleInterrupta,FALLING); attachInterrupt(digitalPinToInterrupt(pulsadord),handleInterruptd,FALLING); } void loop() { vizualiza(); }
总结
- 临时禁用中断是可行的,但必须只在必要的瞬间禁用,绝不能在
delay这类阻塞操作期间关中断 - 优先用原子读取保护共享变量,这是比全局关中断更高效、更安全的方式
- 修复中断函数里的变量混淆错误是解决计数器失效的关键前提
内容的提问来源于stack exchange,提问作者Axwell Duarte

