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

如何清除RP2040的ICSR寄存器第22位解决按键抖动问题

解决RP2040按键抖动导致LCD背光频繁切换的问题

我使用Arduino IDE(C语言)开发树莓派Pico项目,项目包含16x2 LCD及控制其背光的按键。按键功能正常,但每次按压时背光会闪烁,需多次随机按压才能稳定切换开关状态,推测是按键抖动导致。希望在中断返回前清除RP2040的ICSR寄存器第22位,以清空中断缓冲区中的待处理项。

当前代码

#include <Adafruit_BMP280.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
#include "hardware/regs/m0plus.h"
//#include <pico/stdlib.h>
//#include <hardware/pwm.h>

Adafruit_BMP280 bmp; //I2C

hd44780_I2Cexp lcd(0x27, 16, 2);

const uint16_t AirValue = 1023;
const uint16_t WaterValue = 660; //measured fully submerged in water
const uint16_t DarkValue = 0;
const uint16_t LightValue = 1023;
uint16_t soilMoisture;
uint16_t soilMoisturePercent;
uint16_t lightIntensity;
uint16_t lightIntensityPercent;
volatile bool lcdBacklightStatus = false;

byte pressureChar[] = { 0b01000, 0b11110, 0b11100, 0b01000, 0b00011, 0b01111, 0b00000, 0b11110
};

byte moistureChar[] = { 0b00000, 0b00100, 0b01110, 0b11111, 0b11111, 0b11111, 0b01110, 0b00000
};

byte lightIntensityChar[] = { 0b00000, 0b01110, 0b10001, 0b11011, 0b10101, 0b01110, 0b01110, 0b00100
};

byte temperatureChar[] = { 0b01000, 0b10111, 0b10100, 0b11111, 0b11100, 0b11100, 0b11100, 0b01000
};

byte separatorWall[] = { 0b10101, 0b01010, 0b10101, 0b01010, 0b10101, 0b01010, 0b10101, 0b01010
};

byte degreeChar[] = { 0b00111, 0b00101, 0b00111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000
};

void backlight();

void setup() {
  lcd.begin(0x27, 16, 2);

  lcd.createChar(0, pressureChar);
  lcd.createChar(1, moistureChar);
  lcd.createChar(2, lightIntensityChar);
  lcd.createChar(3, temperatureChar);
  lcd.createChar(4, separatorWall);
  lcd.createChar(5, degreeChar);

  lcd.home();

  pinMode(26, INPUT); //soilMoisture
  pinMode(27, INPUT); //lightIntensity
  pinMode(17, INPUT); //button

  attachInterrupt(digitalPinToInterrupt(17), backlight, RISING);
  
  //Serial.begin(9600);

  bmp.begin(0x76);
  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
                  Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
                  Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
                  Adafruit_BMP280::FILTER_X16,      /* Filtering. */
                  Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */

  lcd.clear();
  lcd.backlight(); //lcd.noBacklight();

  lcd.setCursor(1,0);
  lcd.print("Plant Station");
  lcd.setCursor(6,1);
  lcd.print("V0.8");

  delay(2000);

  lcd.noBacklight();
  lcd.clear();

  lcd.setCursor(0,0);
  lcd.write(byte(3));
  lcd.setCursor(10,0);
  lcd.write(byte(2));
  lcd.setCursor(0,1);
  lcd.write(byte(0));
  lcd.setCursor(10,1);
  lcd.write(byte(1));
  lcd.setCursor(7,0);
  lcd.write(byte(5));
  lcd.setCursor(8,0);
  lcd.print("C");
  lcd.setCursor(15,0);
  lcd.print("%");
  lcd.setCursor(6,1);
  lcd.print("hPa");
  lcd.setCursor(15,1);
  lcd.print("%");
}

void loop() {
  soilMoisture = analogRead(26);
  soilMoisturePercent = map(soilMoisture, AirValue, WaterValue, 0, 100);

  lightIntensity = analogRead(27);
  lightIntensityPercent = map(lightIntensity, DarkValue, LightValue, 0, 100);

  if(soilMoisturePercent >= 100) soilMoisturePercent = 100;
    else if(soilMoisturePercent <= 0) soilMoisturePercent = 0; 

  if(lightIntensityPercent >= 100) lightIntensityPercent = 100;
    else if(lightIntensityPercent <= 0) lightIntensityPercent = 0;

  //Serial.print("Moisture: ");
  //Serial.println(soilMoisture);
  //Serial.print("Percentage: ");
  //Serial.println(soilMoisturePercent);
  //Serial.print("LightIntensity: ");
  //Serial.println(lightIntensity);
  //Serial.print(soilMoisture);
  //Serial.println(soilMoisturePercent);

  //BANNED CURSOR POSITIONS: 0,0  9,0  0,1  9,1  6,0  14,0  7,0  15,0  5,1  15,1

  lcd.setCursor(3,0);
  lcd.print(bmp.readTemperature(),1);
  lcd.setCursor(2,1);
  lcd.print(((bmp.readPressure()/100)),0);
  if(soilMoisturePercent < 10) {
    lcd.setCursor(13,1);
    lcd.print(" ");
    lcd.print(soilMoisturePercent);
  } else if(soilMoisturePercent < 100) {
    lcd.setCursor(13,1);
    lcd.print(soilMoisturePercent);
  } else {
    lcd.setCursor(12,1);
    lcd.print(soilMoisturePercent);
  }
  if(lightIntensityPercent < 10) {
    lcd.setCursor(13,0);
    lcd.print(" ");
    lcd.print(lightIntensityPercent);
  } else if(lightIntensityPercent < 100) {
    lcd.setCursor(13,0);
    lcd.print(lightIntensityPercent);
  } else {
    lcd.setCursor(12,0);
    lcd.print(lightIntensityPercent);
  }

  delay(60000);
}

void backlight() {
  //noInterrupts();
  detachInterrupt(digitalPinToInterrupt(17));
  if(lcdBacklightStatus == true) {
    lcd.noBacklight();
  } else if(lcdBacklightStatus == false) {
    lcd.backlight();
  }
  delayMicroseconds(500000);
  //interrupts();
  attachInterrupt(digitalPinToInterrupt(17), backlight, RISING);
  lcdBacklightStatus = !lcdBacklightStatus;
  //want to clear ICSR register here i guess
}

解决方案

要清除RP2040的ICSR寄存器第22位(对应PENDSTCLR位,用于清空中断挂起状态),直接在中断函数末尾添加寄存器操作代码即可。同时建议去掉当前中断函数中detachInterrupt+delayMicroseconds+attachInterrupt的写法,这种方式效率低且可能影响其他中断响应。

修改后的backlight中断函数:

void backlight() {
  // 切换背光状态
  if(lcdBacklightStatus) {
    lcd.noBacklight();
  } else {
    lcd.backlight();
  }
  lcdBacklightStatus = !lcdBacklightStatus;
  
  // 清除ICSR寄存器第22位,清空中断缓冲区待处理项
  *(volatile uint32_t *)M0PLUS_ICSR_REG |= (1 << 22);
}

补充说明

  • M0PLUS_ICSR_REG是RP2040提供的ICSR寄存器地址宏,来自你已包含的hardware/regs/m0plus.h头文件
  • 使用volatile修饰指针是为了防止编译器优化该寄存器操作,确保代码直接对硬件寄存器进行写入
  • 写入1到第22位即可清除对应的挂起中断,避免抖动产生的重复中断触发

另外,更彻底的按键防抖方案还包括:

  • 硬件层面:给按键添加RC滤波电路(10k电阻+100n电容)
  • 软件层面:在中断中记录触发时间,仅当两次触发间隔超过20-50ms时才执行状态切换逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 00:45:56