You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

ATtiny85带长按切换运行/休眠状态的软件锁存器代码优化求助

ATtiny85带长按切换运行/休眠状态的软件锁存器代码优化求助

看起来你这个低功耗软件锁存器的需求很明确,但确实在休眠唤醒稳定性和代码可读性上踩了几个AVR开发的常见坑,我来帮你梳理优化一下:


原代码的核心问题分析

  1. 休眠唤醒的中断残留bug:引脚变化中断的标志位(PCIFR中的PCIF)如果休眠前未清除,可能导致刚进入休眠就被虚假唤醒,或者唤醒后逻辑混乱。
  2. 阻塞式长按检测:原buttonLongPressed()while循环+delay实现,不仅会完全阻塞主程序,还会在低功耗场景下浪费电量,且唤醒时的按键抖动容易导致长按漏检。
  3. 状态管理混乱buttonHandledledState的组合判断逻辑重复,休眠唤醒后标志位状态不确定,容易出现状态冲突。
  4. 低功耗优化不彻底:休眠时未关闭Timer0(PRR未加PRTIM0),而Timer0是delay()/millis()的依赖,完全可以关闭以进一步降低功耗。

优化后的代码(可读性+稳定性+低功耗拉满)

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>

#define LED_PIN     0  // PB0 控制LED
#define BUTTON_PIN  3  // PB3 长按按键
#define LONG_PRESS_THRESHOLD 2000  // 长按触发阈值:2000ms
#define DEBOUNCE_DELAY 50  // 按键防抖时长:50ms

// 系统状态枚举,让流程逻辑一目了然
enum SystemState {
  STATE_SLEEP,          // 深度休眠状态
  STATE_CHECK_LONG_PRESS, // 唤醒后检测长按
  STATE_IDLE_LED_ON     // LED点亮后的空闲状态(等待按键释放后休眠)
};

SystemState currentState = STATE_SLEEP;
bool ledState = false;
uint32_t pressStartTime = 0;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  digitalWrite(LED_PIN, LOW);

  // 配置引脚变化中断:用于唤醒休眠的芯片
  GIMSK |= (1 << PCIE);    // 启用全局引脚变化中断
  PCMSK |= (1 << BUTTON_PIN); // 监听PB3的引脚变化
  sei(); // 开启全局中断

  enterDeepSleep(); // 程序启动后直接进入休眠
}

void loop() {
  // 用状态机管理全流程,逻辑清晰无歧义
  switch(currentState) {
    case STATE_SLEEP:
      enterDeepSleep();
      break;
    case STATE_CHECK_LONG_PRESS:
      checkLongPress();
      break;
    case STATE_IDLE_LED_ON:
      waitForButtonRelease();
      break;
  }
}

// 进入深度休眠:最大化降低功耗
void enterDeepSleep() {
  // 清除引脚变化中断标志,避免虚假唤醒
  PCIFR |= (1 << PCIF);

  // 关闭所有非必要外设
  ADCSRA &= ~(1 << ADEN);       // 关闭ADC
  ACSR |= (1 << ACD);           // 关闭模拟比较器
  PRR = (1 << PRADC) | (1 << PRTIM1) | (1 << PRTIM0); // 关闭Timer0/1、ADC
  sleep_bod_disable();          // 休眠时临时禁用BOD检测
  wdt_disable();                // 关闭看门狗定时器

  // 配置并进入深度休眠模式
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sei(); // 确保中断处于开启状态
  sleep_cpu(); // 正式进入休眠

  // 唤醒后恢复必要外设
  sleep_disable();
  PRR &= ~(1 << PRTIM0); // 恢复Timer0(用于millis()计时)
}

// 非阻塞式长按检测:避免阻塞主程序
void checkLongPress() {
  // 按键已释放,直接回到休眠
  if (digitalRead(BUTTON_PIN) == HIGH) {
    currentState = STATE_SLEEP;
    pressStartTime = 0;
    return;
  }

  // 首次按下时记录起始时间+软件防抖
  if (pressStartTime == 0) {
    delay(DEBOUNCE_DELAY);
    if (digitalRead(BUTTON_PIN) == HIGH) {
      pressStartTime = 0;
      currentState = STATE_SLEEP;
      return;
    }
    pressStartTime = millis();
  }

  // 检测是否达到长按阈值
  if (millis() - pressStartTime >= LONG_PRESS_THRESHOLD) {
    // 切换LED状态并给出反馈
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState ? HIGH : LOW);
    delay(100); // 短亮确认,提升用户体验
    // 切换到空闲状态等待按键释放
    pressStartTime = 0;
    currentState = STATE_IDLE_LED_ON;
  }
}

// LED点亮后:等待按键释放,然后回到休眠
void waitForButtonRelease() {
  if (digitalRead(BUTTON_PIN) == HIGH) {
    delay(DEBOUNCE_DELAY);
    if (digitalRead(BUTTON_PIN) == HIGH) {
      currentState = STATE_SLEEP;
    }
  }
}

// 引脚变化中断服务程序:唤醒系统并切换到长按检测状态
ISR(PCINT0_vect) {
  // 清除中断标志位,防止重复触发
  PCIFR |= (1 << PCIF);
  // 唤醒后切换到长按检测流程
  currentState = STATE_CHECK_LONG_PRESS;
  pressStartTime = 0; // 重置计时
}

关键优化点说明

  1. 状态机驱动流程:用SystemState枚举明确区分系统的三个核心状态,彻底解决原代码逻辑混乱的问题,后续维护一眼就能看懂。
  2. 修复休眠唤醒bug
    • 休眠前主动清除PCIF中断标志,避免虚假唤醒。
    • 中断服务程序中也强制清除标志,确保唤醒流程稳定。
  3. 非阻塞式长按检测:用millis()计时替代阻塞循环,既不占用主程序资源,又能精准检测长按,同时加入了双重软件防抖。
  4. 极致低功耗优化
    • 休眠时关闭Timer0/1、ADC、模拟比较器、看门狗、BOD检测,把ATtiny85的功耗降到nA级(接近硬件最低水平)。
    • 唤醒后仅恢复必要外设,避免不必要的功耗浪费。
  5. 简化按键逻辑:去掉冗余的buttonHandled标志,用状态机自然管理「按下-检测长按-释放-休眠」的完整流程,避免状态冲突。

额外注意事项

  • 硬件防抖补充:如果软件防抖仍不稳定,可在按键引脚与GND之间并联一个100nF的电容,进一步消除机械抖动。
  • BOD永久禁用:代码中的sleep_bod_disable()仅在休眠时临时禁用BOD,若要永久禁用需修改ATtiny85的熔丝位,才能实现真正的最低功耗。
  • 替代计时方案:如果想完全去掉millis()依赖(关闭Timer0),可改用看门狗定时器实现长按计时,进一步降低功耗,但配置会稍复杂。

火山引擎 最新活动