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

Arduino项目技术问询:如何实现4位7段数码管滚动显示文本与压电蜂鸣器同步播放音乐

解决Arduino同步执行数码管滚动和蜂鸣器乐曲的问题

嘿,我懂你遇到的问题了!现在你的代码里song()和text()是串行执行的——先把文本滚动完,才会开始播放乐曲,这是因为这两个函数都是「阻塞式」的,一旦运行起来就会占着CPU不放,直到自己执行完。要让它们同步跑起来,我们得把这两个功能改成非阻塞的,简单说就是把长任务拆成一小步一小步,每次主循环loop()里只做一点点,这样两个任务就能交替进行啦!

为什么现在不能同步?

你当前的代码逻辑是:

if (digitalRead(knopTekst) == LOW) { song(); text(); }

这里song()会先从头到尾执行(比如里面用了delay()来控制音符时长),完全结束后才会调用text(),同样text()里的delay()也会阻塞整个程序,所以两个功能只能先后跑,没法同步。

核心解决方案:非阻塞编程

Arduino是单线程的,没法真正意义上的“并行”,但我们可以用「时间片轮转」的思路:用millis()(获取当前系统运行毫秒数)来跟踪每个任务的执行时间,每次循环只检查是否到了该执行任务下一步的时间,如果到了就执行一小步,然后立刻让出CPU给另一个任务。

步骤1:改造蜂鸣器乐曲为非阻塞

原来的song()可能是这样的(阻塞式):

void song() {
  int melody[] = {262, 294, 330};
  int durations[] = {4,4,4};
  for(int i=0; i<3; i++){
    int duration = 1000/durations[i];
    tone(buzzerPin, melody[i], duration*0.9);
    delay(duration); // 这里会阻塞!
    noTone(buzzerPin);
  }
}

我们要把它改成非阻塞,需要保存当前播放到哪个音符、上次播放的时间:

// 全局变量,保存蜂鸣器状态
int melody[] = {262, 294, 330, 349, 392, 440}; // 你的乐曲音符
int noteDurations[] = {4, 4, 4, 8, 8, 4}; // 你的音符时长(4=四分音符)
int currentNote = 0;
unsigned long lastNoteTime = 0;
const int buzzerPin = 9; // 你的蜂鸣器引脚

// 非阻塞的音符播放函数:每次只检查是否要播放下一个音符
void playNextNote() {
  // 如果乐曲已经播放完,重置索引
  if (currentNote >= sizeof(melody)/sizeof(melody[0])) {
    currentNote = 0;
    noTone(buzzerPin);
    return;
  }

  unsigned long now = millis();
  long noteDuration = 1000 / noteDurations[currentNote]; // 计算当前音符时长

  // 检查是否到了切换音符的时间
  if (now - lastNoteTime >= noteDuration) {
    noTone(buzzerPin);
    // 播放当前音符,留10%的间隙避免连音
    tone(buzzerPin, melody[currentNote], noteDuration * 0.9);
    // 更新时间和当前音符索引
    lastNoteTime = now;
    currentNote++;
  }
}

步骤2:改造数码管滚动为非阻塞

同样,原来的text()里的delay()会阻塞,我们改成用millis()跟踪滚动时间:

// 全局变量,保存数码管滚动状态
String scrollContent = "HELLO"; // 你要滚动的文本
int currentCharPos = 0;
unsigned long lastScrollTime = 0;
const long scrollInterval = 500; // 每500ms滚动一次
// 假设你用的是4位数码管,比如TM1637,这里替换成你的数码管初始化逻辑
// #include <TM1637Display.h>
// TM1637Display display(CLK_PIN, DIO_PIN);

// 非阻塞的滚动更新函数:每次只更新一次显示
void updateScrollText() {
  // 如果文本滚动完,重置位置
  if (currentCharPos >= scrollContent.length()) {
    currentCharPos = 0;
    return;
  }

  unsigned long now = millis();
  // 检查是否到了滚动时间
  if (now - lastScrollTime >= scrollInterval) {
    // 这里根据你的数码管库调整显示逻辑,比如显示当前开始的4个字符
    String displayText = scrollContent.substring(currentCharPos, currentCharPos + 4);
    // 补空格确保显示4位(如果剩余字符不足)
    while(displayText.length() <4) displayText += " ";
    // display.showString(displayText); // 替换成你的数码管显示代码
    // 更新时间和滚动位置
    lastScrollTime = now;
    currentCharPos++;
  }
}

步骤3:主循环里协调两个任务

最后,我们用一个状态变量来控制是否开启同步执行,在loop()里交替调用两个非阻塞函数:

bool isRunning = false;
const int knopTekst = 2; // 你的按钮引脚

void setup() {
  pinMode(knopTekst, INPUT_PULLUP); // 按钮用上拉输入,按下时为LOW
  pinMode(buzzerPin, OUTPUT);
  // display.setBrightness(7); // 初始化数码管
}

void loop() {
  // 检测按钮按下(防抖处理:等待按钮释放再触发)
  if (digitalRead(knopTekst) == LOW) {
    while(digitalRead(knopTekst) == LOW); // 等待按钮松开
    isRunning = !isRunning; // 切换运行状态
    // 如果开始运行,重置两个任务的初始状态
    if (isRunning) {
      currentNote = 0;
      lastNoteTime = millis();
      currentCharPos = 0;
      lastScrollTime = millis();
    } else {
      noTone(buzzerPin);
      // display.clear(); // 停止后清空数码管
    }
  }

  // 如果处于运行状态,每次循环都执行两个非阻塞函数
  if (isRunning) {
    playNextNote();
    updateScrollText();
  }

  // 这里可以放其他你的任务代码
}

关键注意事项

  • 彻底去掉所有delay():delay()会阻塞整个程序,是同步执行的最大敌人,全部用millis()替代
  • 状态变量要全局:需要保存每个任务的当前进度(比如播放到哪个音符、滚动到哪个字符),所以要用全局变量或者类成员变量
  • 按钮防抖:一定要处理按钮的抖动,否则一次按下可能会触发多次状态切换
  • 适配你的硬件:上面的示例是通用逻辑,需要根据你用的数码管库(比如TM1637、MAX7219)和蜂鸣器引脚调整具体代码

这样改造后,每次loop()循环都会同时检查乐曲和滚动文本是否需要更新,两个功能就会同步运行啦!

内容的提问来源于stack exchange,提问作者Ruben g.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 17:28:12