Arduino Uno CAN总线数据LCD显示切换按钮故障排查求助
按键切换功能失效故障排查与修复方案
问题概述
使用Arduino Uno搭配SEEED CAN-bus shield和Adafruit I2C 4x20 LCD,实现CAN消息接收与LCD显示切换功能。CAN数据接收正常,但按键切换显示完全失效:按键接引脚3(公共端接5V,常开端接引脚,引脚通过10K电阻下拉),设置为上升沿触发中断,按下按键无反应。
核心问题点分析
- 中断服务程序(ISR)滥用阻塞延迟:
bingus()中断函数中使用delay(),但delay()依赖系统定时器中断,ISR执行时会暂停其他中断,导致delay()失效,还会阻塞整个系统,影响CAN数据接收和LCD更新。 - 全局变量未加
volatile修饰:screennum和faultpresent在ISR中修改、主循环中读取,未声明为volatile会被编译器优化,导致主循环无法获取变量的最新值。 - 条件判断错误:多处用赋值操作
=代替比较操作==(如if (faultpresent = 0)),直接修改变量值导致逻辑完全偏离预期。 - 未定义关键变量:主循环中调用
data[screennum]但未定义该数组,会引发编译错误。 - 按键消抖方式错误:在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(); }
关键修复说明
- ISR简化:
bingus()仅设置按键触发标志和记录触发时间,所有复杂逻辑移至主循环,避免阻塞系统。 volatile变量:screennum、faultpresent、buttonPressed均用volatile修饰,确保主循环能获取ISR修改后的最新值。- 非阻塞消抖:在主循环中通过时间差判断按键是否稳定触发,替代ISR中的
delay()。 - 修复条件判断:所有赋值操作
=改为比较操作==,修正逻辑错误。 - 补充
data数组:定义对应参数的存储数组,解决编译错误。
内容的提问来源于stack exchange,提问作者mysteryflavor133
相关产品推荐
相关产品推荐

