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

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;

核心问题分析

  1. 状态重置时机错误:原函数中messageComplete为true时立即重置所有static变量,导致函数刚完成一次字符输出就清空状态,无法继续推进后续字符。
  2. 主循环调用逻辑错误:各个phase中仅在初始化时调用一次typewriterEffect,而非持续调用。非阻塞函数需要在主循环每一轮都执行,才能逐步输出后续字符。
  3. 多消息冲突:部分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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:13:11