Использование функции millis() вместо функции delay() при воспроизведении мелодии

Я хочу, чтобы мой Arduino воспроизводил несколько простых мелодий, а также мог пропустить мелодию нажатием кнопки и перейти к следующей песне. поэтому я не могу использовать delay (), потому что код должен постоянно проверять, нажата ли кнопка. Поэтому я хотел использовать функцию millis(), но не знаю, как ее реализовать.

вот как выглядит код песни с delay():

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
//etc...

int tempo=144; 
 
// измените это значение на любой контакт, который вы хотите использовать
int buzzer = 11;
 

int melody[] = {
 
  
  NOTE_E5, 4,  NOTE_B4,8,  NOTE_C5,8,  NOTE_D5,4,  NOTE_C5,8,  NOTE_B4,8,
  // etc...
 
};
 
// sizeof дает количество байтов, каждое значение int состоит из двух байтов (16 бит)
// есть два значения на ноту (шаг и длительность), поэтому для каждой ноты есть четыре байта
int notes=sizeof(melody)/sizeof(melody[0])/2; 
 
// это вычисляет продолжительность целой ноты в мс (60с/темп)*4 удара
int wholenote = (60000 * 4) / tempo;
 
int divider = 0, noteDuration = 0;
 
void setup() {
  // повторите ноты мелодии. 
  // Помните, что массив в два раза больше количества заметок (заметки + длительность)
  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
 
    // вычисляет длительность каждой ноты
    divider = melody[thisNote + 1];
    if (divider > 0) {
      // обычная заметка, просто продолжайте
      noteDuration = (wholenote) / divider;
    } else if (divider < 0) {
      // пунктирные ноты представлены с отрицательной длительностью!!
      noteDuration = (wholenote) / abs(divider);
      noteDuration *= 1.5; // increases the duration in half for dotted notes
    }
 
    // играем ноту только на 90% продолжительности, оставляя 10% в качестве паузы
    tone(buzzer, melody[thisNote], noteDuration*0.9);
 
    // Подождать заданную продолжительность, прежде чем играть следующую ноту.
    delay(noteDuration);
    
    // остановить генерацию сигнала перед следующей нотой.
    noTone(buzzer);
  }
}

Я нашел этот код в Интернете, но не знаю, как реализовать его в приведенном выше коде. когда я пытаюсь, я слышу какой-то проклятый звук.

unsigned long previousMillis = 0;
boolean outputTone = false;     
         
if (millis() - previousMillis >= noteDuration) {
    previousMillis = millis();
    if (outputTone) {
        noTone(buzzer);
        outputTone==false;              
    } else {
        tone(buzzer, melody[thisNote], noteDuration*0.9);
        outputTone==true;
    }
}

edit1: Я попробовал вот что:

    unsigned long previousMillis = millis();
    int thisNote = 0;
    
    while (thisNote < notes * 2) {
        
        // вычисляет продолжительность каждой ноты
        divider = lied1[thisNote + 1];
        if (divider > 0) {
            // обычная заметка, просто продолжайте
            noteDuration = (wholenote) / divider;
        } else if (divider < 0) {
            // пунктирные ноты представлены с отрицательной длительностью!!
            noteDuration = (wholenote) / abs(divider);
            noteDuration *= 1.5; // increases the duration in half for dotted notes
        }

        // воспроизведение ноты
        tone(buzzer, melody[thisNote]);
        
        
        check();//проверьте, нажата ли кнопка, и если да, меняет число на 2
        if (number ==2){
            thisNote=notes*2;
        }  
        if ((millis() - previousMillis) >= noteDuration){
           thisNote = thisNote + 2;  
           // остановите генерацию сигнала перед следующей нотой.
           noTone(buzzer);
        }
        
     }

но это не работает, и я действительно не понимаю, почему

edit2: Я что-то изменил, это, вероятно, самая близкая мне рабочая мелодия без "delay()". Но это очень медленно.

    boolean outputTone = false;
    unsigned long previousMillis = millis();
    int thisNote = 0;
    
    while (thisNote < notes * 2) {
        
        // calculates the duration of each note
        divider = lied1[thisNote + 1];
        if (divider > 0) {
            // regular note, just proceed
            noteDuration = (wholenote) / divider;
        } else if (divider < 0) {
            // dotted notes are represented with negative durations!!
            noteDuration = (wholenote) / abs(divider);
            noteDuration *= 1.5; // increases the duration in half for dotted notes
        }
        
        if (millis() - previousMillis >= noteDuration) {
            previousMillis = millis();
            if (outputTone) {
                noTone(buzzer);
                outputTone==false;              
            } else {
                tone(buzzer, melody[thisNote], noteDuration*0.9);
                outputTone==true;
                thisNote = thisNote + 2; 
            }
        }

        check(); //checks if button is pressed, and if so, changes number to 2.
        if (number ==2){ 
            thisNote=notes*2;
        }  
     }

, 👍1


2 ответа


1

По сути, вам нужно будет разорвать цикл for открытым и использовать глобальную переменную для этой заметки, увеличивая ее каждый цикл (в примере с миллисом, чтобы все еще соблюдалось правильное время).

Это не единственный подход к решению данной проблемы. Ваш существующий код может проверять наличие нажатия кнопки в цикле for и выходить из цикла for при нажатии.

,

спасибо за ваш комментарий! Я не совсем понял, что вы имели в виду, но я кое-что попробовал (я отредактировал свой пост). Я изменил свой цикл for в цикле while. но это все равно не работает., @Rsoc2002


1

Я не совсем понимаю фрагменты кода, которые вы добавили к своему вопросу позже (они частично неполны). Но я предложу свое собственное решение проблемы.

Мелодия - это список нот и пауз. Каждая нота имеет высоту и длительность. Пауза имеет только продолжительность. Теперь мы можем определить структуру для хранения заметки:

struct Note {
    unsigned int pitch;
    unsigned int duration;
};

Поскольку пауза не имеет высоты тона, мы можем использовать нашу структуру для ноты, установив высоту тона на недопустимое значение (например, ноль). Для построения мелодии мы теперь определяем массив нотs. Но поскольку нам нужно несколько мелодий, чтобы мы могли переключаться между ними, мы используем 2-мерный массив для определения всех мелодий в одном массиве:

#define NR_MELODIES  2
#define MELODY_LENGTH 8
Note melody[NR_MELODIES][MELODY_LENGTH] = {
    { // мелодия 0
        {0      , 4}, //pause
        {NOTE_E5, 4},
        {NOTE_B4, 8},
        {NOTE_C5, 8},
        {NOTE_D5, 4},
        {NOTE_C5, 8},
        {0      , 8}, // pause
        {NOTE_B4, 8}
    },
    { // мелодия 1
        {0      , 4}, //pause
        {NOTE_B4, 8},
        {NOTE_C5, 8},
        {NOTE_D5, 8},
        {NOTE_C5, 4},
        {NOTE_B4, 8},
        {0      , 8}, // pause
        {NOTE_E5, 4}
    }
};

Здесь я использовал разделитель (часть всей ноты), как и вы в своем коде. 4 означает четверть ноты, 8-восьмерку. Чтобы не потерять первую ноту, я вставил четверть паузы в начале.

Нам также нужна переменная для удержания текущей позиции в нашей мелодии, одна для текущей мелодии и переменная временной метки для временной метки последней сыгранной ноты:

unsigned int current_note = 0;
unsigned int current_melody = 0;
unsigned long last_note_timestamp = 0;

Теперь в void loop() мы проверяем нашу метку времени и продолжительность текущей ноты. Если текущая нота должна закончиться, мы заканчиваем ее, переходим к следующей ноте и начинаем играть ее:

if(millis() - last_note_timestamp > wholenote/melody[current_melody][current_note].duration){
    last_note_timestamp += wholenote/melody[current_melody][current_note].duration; // increment timestamp to start of next note
    noTone(buzzer); // stop current note
    current_note++; // increment to next note
    if(melody[current_melody][current_note].pitch){ // next note is not a pause
        tone(buzzer, melody[current_melody][current_note].pitch, wholenote/melody[current_melody][current_note].duration); // start note
    }
}

А после оператора if мы можем добавить оператор if для проверки кнопки изменения мелодии. Вы не включили код проверки кнопки в свой вопрос, поэтому я просто использую здесь функцию-заполнитель.

if(change_melody_pressed()){
    // увеличение текущей мелодии и обертывание, если мы достигли последней мелодии
    current_melody = (current_melody + 1) % NR_MELODIES;
    // reset current note to play melody from start
    current_note = 0;
}

Теперь нам еще нужно подумать о том, что происходит, когда мелодия заканчивается. 3 возможности:

  • Мы могли бы перезапустить текущую мелодию: нам нужно проверить наличие последней ноты в мелодии, а затем сбросить current_note:

      if(current_melody >= MELODY_LENGTH ){
          current_note = 0;
      }
    
  • Мы можем перейти к следующей мелодии: Как и выше, но мы добавляем строку увеличения мелодии в оператор if:

      current_melody = (current_melody + 1) % NR_MELODIES;
    
  • Мы можем перестать играть: Здесь я бы использовал вышеприведенный оператор if и установил current_note в значение -1. Затем нам нужно проверить наш первый оператор if, если current_note не равен -1:

      if(current_note != -1 && millis() - last_note_timestamp > wholenote/melody[current_melody][current_note].duration)
    

Примечание: Я не тестировал приведенный выше код, но скомпилировал его, чтобы попытаться найти синтаксические ошибки.

,