Nextion显示屏非阻塞打字机效果异常:仅输出单个字符求助
Nextion非阻塞打字机效果故障修复
问题背景
为Nextion显示屏开发打字机效果,最初用for循环实现功能正常,但会阻塞主循环(导致倒计时在打印时冻结)。改用非阻塞if方案后,每次触发提示仅能输出一个字符,原核心代码如下:
原打字机效果函数
void typewriterEffect(const char* message, NexText& textComponent, unsigned long delayTime) { static String currentText = ""; static int charIndex = 0; static unsigned long previousMillis = 0; static bool messageComplete = false; unsigned long currentMillis = millis(); if (!messageComplete && (currentMillis - previousMillis >= delayTime)) { previousMillis = currentMillis; if (message[charIndex] != '\0') { currentText += message[charIndex]; charIndex++; if (charIndex % 30 == 0 && message[charIndex] == ' ') { currentText += "\r\n"; } textComponent.setText(currentText.c_str()); Serial.print("Current Text: "); Serial.println(currentText); } else { messageComplete = true; Serial.println("Message complete"); } } // Reset for the next message if (messageComplete) { currentText = ""; charIndex = 0; messageComplete = false; } }
原主循环关键片段
case MANUAL_PHASE: if (!manualPromptDisplayed) { typewriterEffect("See Manual", promptText, 100); manualPromptDisplayed = true; previousMillis = millis(); // Start the timer for the 3-second display } if (millis() - previousMillis >= 3000) { previousMillis = millis(); Serial.println("Transitioning to BUTTON_PHASE1"); phase = BUTTON_PHASE1; } break;
核心问题分析
- 状态重置时机错误:原函数中
messageComplete为true时立即重置所有static变量,导致函数刚完成一次字符输出就清空状态,无法继续推进后续字符。 - 主循环调用逻辑错误:各个phase中仅在初始化时调用一次
typewriterEffect,而非持续调用。非阻塞函数需要在主循环每一轮都执行,才能逐步输出后续字符。 - 多消息冲突:部分phase中连续调用两次
typewriterEffect,会覆盖static变量状态,导致消息输出混乱。
修复方案
1. 修改打字机效果函数
调整状态重置逻辑,仅在切换新消息时重置状态;新增返回值标识消息是否完成,方便主循环判断。
bool typewriterEffect(const char* message, NexText& textComponent, unsigned long delayTime) { static const char* lastMessage = nullptr; static String currentText = ""; static int charIndex = 0; static unsigned long previousMillis = 0; static bool messageComplete = false; // 切换新消息时重置所有状态 if (message != lastMessage) { lastMessage = message; currentText = ""; charIndex = 0; previousMillis = millis(); messageComplete = false; } if (!messageComplete) { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= delayTime) { previousMillis = currentMillis; if (message[charIndex] != '\0') { currentText += message[charIndex]; charIndex++; // 修正换行逻辑:避免越界访问,跳过空格避免重复输出 if (charIndex > 0 && charIndex % 30 == 0 && message[charIndex] == ' ') { currentText += "\r\n"; charIndex++; } textComponent.setText(currentText.c_str()); Serial.print("Current Text: "); Serial.println(currentText); } else { messageComplete = true; Serial.println("Message complete"); } } } return messageComplete; // 返回消息是否完成 }
2. 调整主循环逻辑
在各个phase中持续调用typewriterEffect,直到消息输出完成后再执行后续逻辑;避免连续触发多个消息。
修改后的MANUAL_PHASE示例
case MANUAL_PHASE: static bool manualMessageDone = false; if (!manualMessageDone) { // 持续调用直到消息输出完成 manualMessageDone = typewriterEffect("See Manual", promptText, 100); } else { // 消息完成后等待3秒切换phase if (millis() - previousMillis >= 3000) { previousMillis = millis(); Serial.println("Transitioning to BUTTON_PHASE1"); phase = BUTTON_PHASE1; manualMessageDone = false; // 重置标志,下次进入phase时重新触发 } } break;
修改后的BUTTON_PHASE1示例(单条消息处理)
case BUTTON_PHASE1: static bool phase1MessageDone = false; static bool phase1PromptShown = false; if (!phase1PromptShown) { phase1MessageDone = typewriterEffect("If Y is a high Z, press green button 1; if it is a low, press red button ", promptText, 100); if (phase1MessageDone) { phase1PromptShown = true; } } else { // 原按钮防抖及处理逻辑保持不变 int reading1 = digitalRead(BUTTON1); int reading2 = digitalRead(BUTTON2); if (reading1 != lastButtonState1) { lastDebounceTime1 = millis(); } if ((millis() - lastDebounceTime1) > debounceDelay) { if (reading1 == LOW) { if (correctAnswer1 == BUTTON1) { typewriterEffect("Correct", promptText, 100); phase = BUTTON_PHASE2; } else { countdownValue -= 30; typewriterEffect("Incorrect: Time deducted", promptText, 100); } } } lastButtonState1 = reading1; // 按钮2处理逻辑同理... } break;
关键修复点说明
- 状态持久化:通过
lastMessage跟踪当前处理的消息,仅在切换新消息时重置状态,保证单条消息能持续输出直到完成。 - 主循环持续调用:非阻塞函数需要在主循环每一轮都执行,才能逐步推进字符输出,因此去掉一次性触发的标志,改为持续调用直到消息完成。
- 返回状态标识:函数返回
messageComplete,让主循环能判断消息是否输出完毕,从而执行后续逻辑(如切换phase、显示按钮交互)。 - 换行逻辑修正:原代码中
message[charIndex]可能越界,新增charIndex > 0判断,并跳过空格避免重复输出。
内容的提问来源于stack exchange,提问作者PhippsTech
相关产品推荐
相关产品推荐

