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

Arduino Uno CAN总线数据LCD显示切换按钮故障排查求助

按键切换功能失效故障排查与修复方案

问题概述

使用Arduino Uno搭配SEEED CAN-bus shield和Adafruit I2C 4x20 LCD,实现CAN消息接收与LCD显示切换功能。CAN数据接收正常,但按键切换显示完全失效:按键接引脚3(公共端接5V,常开端接引脚,引脚通过10K电阻下拉),设置为上升沿触发中断,按下按键无反应。

核心问题点分析

  1. 中断服务程序(ISR)滥用阻塞延迟:bingus()中断函数中使用delay(),但delay()依赖系统定时器中断,ISR执行时会暂停其他中断,导致delay()失效,还会阻塞整个系统,影响CAN数据接收和LCD更新。
  2. 全局变量未加volatile修饰:screennum和faultpresent在ISR中修改、主循环中读取,未声明为volatile会被编译器优化,导致主循环无法获取变量的最新值。
  3. 条件判断错误:多处用赋值操作=代替比较操作==(如if (faultpresent = 0)),直接修改变量值导致逻辑完全偏离预期。
  4. 未定义关键变量:主循环中调用data[screennum]但未定义该数组,会引发编译错误。
  5. 按键消抖方式错误:在ISR中用delay()消抖不可行,应改用非阻塞式消抖逻辑。

修复后的完整代码

long previousMillis = 0;
long interval = 100; // 调整为100ms,减少LCD刷新频率
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

#include "Adafruit_LiquidCrystal.h"

Adafruit_LiquidCrystal lcd(0);

#define CAN_2515
const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
const int BUTTON_PIN = 3;

#ifdef CAN_2515
#include "mcp2515_can.h"
mcp2515_can CAN(SPI_CS_PIN);
#endif

// 用volatile修饰ISR中修改的全局变量
volatile int screennum = 0;
volatile bool faultpresent = 0;
volatile bool buttonPressed = false; // 按键触发标志

// 补充定义data数组,存储对应参数值
int SOCa = 0;
int tempa = 0;
int currenta = 0;
int voltagea = 0;
int parallelLedstate = 0;
int flagnum = 0;
int position = 0;
int data[7];

void setup() {
  pinMode(BUTTON_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), bingus, RISING);
  
  lcd.begin(20, 4);
  lcd.print("hello!");
  delay(5000);

  while (CAN_OK != CAN.begin(CAN_250KBPS)) {
    delay(100);
  }
}

void loop() {
  uint8_t len = 8;
  uint8_t buf[8];
  String message[7] = { 
    "State of Charge:", "Battery Temp:", "DC Current:", 
    "Motor Temp:", "Controller Temp:", "Battery Volts:", 
    "Parallel State:" 
  };
  String flag1[8] = { "P0A0F", "P0A02", "P0A9C", "U0100", "P0560", "P0AA6", "P0A05", "P0A06" };
  String flag2[8] = { "P0A07", "P0A08", "P0A09", "P0A0A", "P0A0B", "P0A0C", "P0A0E", "P0A10" };
  String flag3[8] = { "P0A11", "P0A1F", "P0A12", "P0A80", "P0AFA", "P0A04", "P0AC0", "P0A0D" };
  String flag1ID[8] = { "Cell bank fault", "Weak pack fault", "Thermistor fault", "CAN communication", "Redundant power", "High voltage", "Invalid input suppl-", "Charge-enable" };
  String flag1ID2[8] = { " ", " ", " ", "fault", "supply fault", "isolation fault", "y voltage fault", "relay fault" };
  String flag2ID[8] = { "Discharge-enable", "Charger safety", "Internal hardware", "Internal heatsink", "Internal logic", "Highest cell", "Lowest cell", "pack too hot" };
  String flag2ID2[8] = { "relay fault", "relay fault", "fault", "thermistor fault", "fault", "voltage too high", "voltage too low", "fault" };
  String flag3ID[8] = { "Abnormal state of", "Internal cell", "Cell balancing", "Weak cell fault", "Low cell voltage", "Cell open wiring", "Current sensor", "Cell voltage over" };
  String flag3ID2[8] = { "charge behavior", "communication fault", "stuck off fault", " ", "fault", "fault", "fault", "5V fault" };

  // 更新data数组内容
  data[0] = SOCa;
  data[1] = tempa;
  data[2] = currenta;
  data[3] = tempa; // 假设Motor Temp暂用Battery Temp值,需根据实际CAN消息调整
  data[4] = tempa; // Controller Temp同理,需调整
  data[5] = voltagea;
  data[6] = parallelLedstate;

  // 处理CAN消息接收
  if (CAN_MSGAVAIL == CAN.checkReceive()) {
    CAN.readMsgBuf(&len, buf);
    unsigned long canId = CAN.getCanId();

    if (canId == 0x6B0) {
      SOCa = (uint16_t)(buf[4]);
      currenta = (int)buf[0] << 8 | buf[1];
      voltagea = (int)(buf[2]);
    }
    if (canId == 0x6B1) {
      tempa = (int)(buf[4]);
    }
    if (canId == 0x6B2) {
      parallelLedstate = (buf[0] == 2) ? 1 : 0;
    }
    if (canId == 0x666) {
      if (buf[0] > 1) {
        faultpresent = 1;
        if (buf[1] > 1) {
          flagnum = 1;
          // 补充获取position的代码
        } else if (buf[2] > 1) {
          flagnum = 2;
          // 补充获取position的代码
        } else {
          flagnum = 3;
          // 补充获取position的代码
        }
      } else {
        faultpresent = 0;
      }
    }
  }

  // 非阻塞处理按键消抖与切换逻辑
  if (buttonPressed) {
    unsigned long currentTime = millis();
    if (currentTime - lastDebounceTime > debounceDelay) {
      // 确认按键稳定触发
      if (digitalRead(BUTTON_PIN) == HIGH) {
        faultpresent = 0;
        if (screennum < 6) {
          screennum++;
        } else {
          screennum = 0;
        }
      }
      buttonPressed = false;
    }
  }

  // 非阻塞LCD更新
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    lcd.clear();
    
    if (faultpresent == 0) { // 修复条件判断为==
      lcd.setCursor(0, 1);
      lcd.print(message[screennum]);
      lcd.setCursor(0, 2);
      lcd.print(data[screennum]);
    } else {
      if (flagnum == 1) { // 修复条件判断为==
        lcd.setCursor(0, 1);
        lcd.print(flag1[position]);
        lcd.setCursor(0, 2);
        lcd.print(flag1ID[position]);
        lcd.setCursor(0, 3);
        lcd.print(flag1ID2[position]);
      } else if (flagnum == 2) { // 修复条件判断为==
        lcd.setCursor(0, 1);
        lcd.print(flag2[position]);
        lcd.setCursor(0, 2);
        lcd.print(flag2ID[position]);
        lcd.setCursor(0, 3);
        lcd.print(flag2ID2[position]);
      } else if (flagnum == 3) { // 修复条件判断为==
        lcd.setCursor(0, 1);
        lcd.print(flag3[position]);
        lcd.setCursor(0, 2);
        lcd.print(flag3ID[position]);
        lcd.setCursor(0, 3);
        lcd.print(flag3ID2[position]);
      }
    }
  }
}

// 中断函数只设置触发标志,不做复杂操作
void bingus() {
  buttonPressed = true;
  lastDebounceTime = millis();
}

关键修复说明

  1. ISR简化:bingus()仅设置按键触发标志和记录触发时间,所有复杂逻辑移至主循环,避免阻塞系统。
  2. volatile变量:screennum、faultpresent、buttonPressed均用volatile修饰,确保主循环能获取ISR修改后的最新值。
  3. 非阻塞消抖:在主循环中通过时间差判断按键是否稳定触发,替代ISR中的delay()。
  4. 修复条件判断:所有赋值操作=改为比较操作==,修正逻辑错误。
  5. 补充data数组:定义对应参数的存储数组,解决编译错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:40:54