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

显示7段数码管计数器时如何临时禁用中断?

问题分析与解决方案

能不能在显示时临时禁用中断?

可以,但必须把禁用中断的时间压缩到极短,否则会导致按键触发的中断丢失,甚至出现你遇到的计数器完全失效的问题。你之前的问题根源不是“禁用中断”本身,而是代码里的两个关键bug加上不合理的中断禁用时机。

你遇到的问题原因

  1. 中断服务函数的变量混淆错误
    看handleInterruptd(递减按键的中断函数)里,最后更新的是startTime_ph——但这个变量是给递增按键(handleInterrupta)用的防抖时间戳,递减按键应该更新的是startTime_pm。这个错误会直接打乱递减按键的防抖逻辑,甚至干扰递增按键的判断,再加上你如果在显示时长时间关中断,防抖时间计算彻底混乱,自然计数器就停了。

  2. 显示函数里的delay(10)导致中断被长时间屏蔽
    如果你在vizualiza开头就关中断,两个delay(10)加起来就是20ms,这段时间内按键触发的中断会被完全忽略,不仅防抖逻辑失效,按键操作根本传不到中断函数里,计数器当然没法递增/递减。

  3. 共享变量的竞争问题
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 07:40:05