Большие массивы приводят к сбою Arduino

У меня есть три больших массива PROGMEM для хранения музыкальных нот песни. Один массив — это ноты, другой — длительность нот и третий — пауза после ноты.

Первый массив — это int, а два других — это float. Программа работает, но после определенного количества элементов происходит сбой Arduino. То есть программу загружаю без проблем, а устройство ничего не делает.

Когда у меня 227 элементов (во всех трех массивах), очевидно, программа работает. Если я увеличу количество элементов до 228, при загрузке ничего не произойдет...

Когда IDE сообщает avrdude: 10606 байт флэш-памяти проверено, все работает. Когда размер увеличивается до 10616, все рушится. Это нормальное поведение?

РЕДАКТИРОВАТЬ: Код тривиален, единственная важная часть кода:

int number_of_stored_values_in_MelodyArray = sizeof(Crash_Melody) / sizeof(int);
int Melody_Array[number_of_stored_values_in_MelodyArray];
memcpy_P(Melody_Array, Crash_Melody, sizeof Melody_Array);

Я использую memcpy_P() для копирования массивов из PROGMEM в ОЗУ. Я делаю это для int и двух массивов с плавающей запятой. Учитывая 221 элемент в каждом массиве, я думаю, мне не хватает оперативной памяти.

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

Думаю, если бы я выполнял задание постепенно — например, скопировал только 20 элементов из массива, воспроизвел песню, а затем повторил до конца, у меня не было бы проблем.

Есть ли способ для memcpy_P() указать не весь массив для копирования, а индексировать его? Требуется ли начальное значение индекса?

РЕДАКТИРОВАНИЕ 2: Это фрагмент кода, созданный на основе подхода Эдгара Бонне.

int number_of_stored_values_in_MelodyArray = sizeof(My_Melody) / sizeof(int);
for (int i = 0; i < number_of_stored_values_in_MelodyArray; i++)
{
  int note = pgm_read_word(&My_Melody[i]);
  float duration = pgm_read_word(&My_noteDurations[i]);
  float pause_duration = pgm_read_word(&My_Delay[i]);
  playMelodyExtended2(note, duration, pause_duration, number_of_stored_values_in_MelodyArray, 10, 400);
}

int playMelodyExtended2(int melody, float noteDuration, float pauseDuration, int number_of_notes, int music_pin, int delay_var)
{
  for (int thisNote = 0; thisNote < number_of_notes; thisNote++)
  {
    tone( music_pin, melody, noteDuration );
    delay( pauseDuration );
    noTone(music_pin);
  }

  delay(delay_var);
}

РЕДАКТИРОВАНИЕ 3: Это новейшая версия кода:

 int number_of_stored_values_in_MelodyArray = sizeof(My_Melody) / sizeof(int);
 for (int i = 0; i < number_of_stored_values_in_MelodyArray; i++)
 {
    int note = pgm_read_byte_near(&My_Melody[i]);
    note << 8;
    note += pgm_read_byte_near(&My_Melody[i+1]);
    float duration = pgm_read_float_near(&My_noteDurations[i]);
    uint16_t pause_duration = pgm_read_byte_near(&My_Delay[i]);
    pause_duration << 8;
    pause_duration += pgm_read_byte_near(&My_Delay[i+1]);
    playMelodyExtended2(note, duration, pause_duration, number_of_stored_values_in_MelodyArray, 10, 400);
  }

int playMelodyExtended2(int melody, float noteDuration, uint16_t pauseDuration, int number_of_notes, int music_pin, int delay_var)
{
  for (int thisNote = 0; thisNote < number_of_notes; thisNote++)
  {
    tone( music_pin, melody, noteDuration );
    delay( pauseDuration );
    // stop the tone playing:
    noTone(music_pin);
  }

  delay(delay_var);
}

, 👍-1

Обсуждение

почему продолжительность и паузы представлены числами с плавающей запятой? ... они не являются плавающими числами в нотной записи, @jsotola

Какой Ардуино вы используете? Не могли бы вы поделиться своим кодом (без содержимого массива)?, @Edgar Bonet

также делитесь сообщениями в IDE Arduino... скопируйте текст и вставьте его в свой вопрос... НЕ используйте скриншот... не оставляйте комментарий, @jsotola

три массива могут иметь тип byte... вам нужно только два массива... пауза - это просто невысказанная нота... используйте значение 0 для пауз, @jsotola

Пока в последнем сообщении компиляции и ссылке написано, что вы не заливаете флеш более 100%, все в порядке. В любом случае, пожалуйста, [отредактируйте] свой вопрос и предоставьте минимальный, воспроизводимый и полный пример. Он может состоять из достаточно большого массива и пустых setup() и loop(). Добавьте журнал вывода., @the busybee

Какой Ардуино? Это типа важно. Если предположить, что у вас 32 КБ программируемой памяти (например, на Uno), то массив в 2280 байт не должен привести к сбою. Честно говоря, мне было бы интересно узнать о вашем коде. Код, который вы не опубликовали., @Nick Gammon

`Когда IDE сообщает avrdude: 10606 байт флэш-памяти проверено, все работает. Когда размер увеличивается до 10616, все рушится. Это нормальное поведение?» Нет, конечно нет., @Nick Gammon

Я обновил вопрос., @user1584421

Есть ли способ для memcpy_P() указать не весь массив для копирования, а индексировать его? да, вы могли бы сделать: memcpy_P(&Melody_Array[10], Crash_Melody, 10); чтобы получить 10 байт, но то, что сказал Эдгар Боне, является гораздо лучшим решением. Просто получайте доступ к каждой заметке по одной. Нет смысла усложнять код за счет кусков заметок., @Nick Gammon

С вашим «Редактированием 2»: похоже, что для ваших чисел с плавающей запятой считывается неправильное количество байтов, или какая-то проблема с преобразованием числового формата создает в основном случайные числа для вашей длительности. Попробуйте pgm_read_float для чтения чисел с плавающей запятой., @orithena

@orthena Спасибо! Тоже попробовал, но теперь слышу периодический писк. Все сигналы и паузы между ними одинаковы... Я также попробовал pgm_read_byte_near() для переменной int, но с теми же результатами..., @user1584421

@user1584421 user1584421 Похоже, есть какие-то проблемы с преобразованием. Используя методы pgm_read_*, вы по сути говорите: «Прочитайте n байт из Progmem и просто поместите биты в переменную». Это означает, что _вам_ необходимо _знать_ какой размер битов имеют значения в вашем массиве Progmem, сколько бит считывается методами pgm_read_*, сколько бит находится в целевой переменной и является ли это целым числом со знаком, числом без знака или IEEE768- форматированное число с плавающей запятой. Попробуйте вывести числа (Serial.println в качестве первой строки в playMelodyExtended2, выводя все аргументы) и сравните их с входными массивами., @orithena

@user1584421 user1584421 Также может быть хорошей идеей использовать тип int16_t, uint16_t, int32_t или uint32_t вместо простого int. int на самом деле является псевдонимом, обозначающим «стандартный int» для процессора/платформы, для которой вы компилируете; используя один из приведенных выше типов, вы всегда будете знать размер вашей переменной. Но это проблема, далекая от вашего первоначального вопроса, поэтому здесь может быть хорошей идеей задать новый вопрос (пожалуйста, опубликуйте также код, который создает массив PROGMEM, так как там могут быть некоторые подсказки)., @orithena


1 ответ


Лучший ответ:

4

В отредактированной версии вашего вопроса вы написали:

Я использую memcpy_P(), чтобы скопировать массивы из PROGMEM в ОЗУ.

Вся цель PROGMEMизбежать необходимости хранить данные в ОЗУ. Если вы скопируете массивы в ОЗУ, вы победите PROGMEM. Твой У Arduino гораздо больше флэш-памяти, чем оперативной памяти, и она выйдет из строя, если вы ее съедите. вся оперативная память. Вот почему оптимизация PROGMEM полезна всякий раз, когда у вас большие массивы постоянных данных.

Правильный способ использования этих массивов — копировать в ОЗУ по одному элементу. время: получите данные для следующей ноты, которую вам предстоит сыграть, затем сыграйте ее, затем переходите к следующему. Например:

for (int i = 0; i < melody_length; i++) {
    int note = pgm_read_word(&Crash_Melody[i]);
    // и т. д...
}

Копирование небольших фрагментов в ОЗУ, как вы предлагаете в своем вопросе, позволит код усложняется и не добавляет никакой ценности.


Изменить: во втором редактировании вашего вопроса есть следующее:

float duration = pgm_read_word(&My_noteDurations[i]);

Что это делает:

  • прочитайте первые два байта My_noteDurations[i] (который является четырехбайтовое количество, поэтому вы его усекаете)
  • интерпретировать это как целое число (а это не так)
  • преобразуйте это в float

Результат будет иметь мало общего с исходным числом. Верное способ чтения числа с плавающей запятой из PROGMEM — использовать pgm_read_float().

Теперь я вижу, что вы используете это число в качестве параметра для задержка(). Учитывая, что эта функция принимает целое число, нет смысла при сохранении задержки в виде числа с плавающей запятой. Это только добавит (несколько дорогостоящих) преобразование чисел с плавающей точкой в целое число в вашу программу без каких-либо дополнительных преимуществ. Кроме того, поскольку задержки, скорее всего, составят менее 65 секунд, 16-битного целого числа без знака должно быть достаточно.

Далее у вас есть это:

for (int thisNote = 0; thisNote < number_of_notes; thisNote++)
{
  tone(music_pin, melody, noteDuration);
  delay(pauseDuration);
  noTone(music_pin);
}

Я не понимаю цели этого цикла. Вы повторяете каждую ноту количество раз, равное общему количеству нот в песня. Почему?


Ответ на редактирование 3: в этой версии я вижу несколько проблем. кода.

  1. Согласованность типов данных: в предыдущей версии это выглядело как будто длительность паузы была плавающей. Теперь они целые числа. У вас есть чтобы убедиться, что вы используете функцию pgm_read_*(), подходящую для фактический тип данных массива.

  2. Чтение побайтово: нет смысла читать 16-битное целое число побайтно: используйте pgm_read_word(), чтобы прочитать все сразу. Чтение байт за байтом только усложняет код и увеличивает вероятность ошибок. Фактически, ваш способ восстановления чисел таков: глючит.

  3. Цикл в playMelodyExtended2() для меня не имеет смысла. Пожалуйста попробуйте удаляя его.

Вот что я предлагаю на данный момент:

for (int i = 0; i < number_of_stored_values_in_MelodyArray; i++)
{
    int note = pgm_read_word(&My_Melody[i]);
    float duration = pgm_read_float(&My_noteDurations[i]);
    uint16_t pause_duration = pgm_read_word(&My_Delay[i]);
    tone(10, note, duration);
    delay(pause_duration);
}

В этом фрагменте сделаны некоторые предположения относительно остальной части вашего кода, и в частности, о том, как определяются ваши массивы. Если эти предположения неверны, это не сработает. Таким образом, я поддерживаю комментарий @orthena. и призываем вас опубликовать полную, тестируемую версию вашего эскиза (три примечания было бы достаточно).

,

Спасибо! Я попробовал ваш подход, но он не сработал. Я разместил код в своем вопросе. Все, что я слышу, это постоянный гул., @user1584421

Спасибо, Эдгар! Я также попробовал pgm_read_float(), но все, что я услышал, это периодический звуковой сигнал. Все сигналы и паузы между ними одинаковы... Я также попробовал pgm_read_byte_near() для переменной int, но с теми же результатами. Я попробую обрезать массив с плавающей запятой, так как delay() принимает только целые числа... Что касается того, почему код такой... Да, я повторяю каждое количество нот в мелодии. Я нашел онлайн-конвертер MIDI в тон(), который выводит три массива. Высота тона, длительность ноты и задержка. Поэтому я создал эту функцию, чтобы справиться с этим. Это работает, но мне не хватает оперативной памяти, @user1584421

@ user1584421: Эта итерация не имеет смысла. Попробуйте сыграть каждую ноту только один раз., @Edgar Bonet

Но как это сделать? Это работает. Как я уже сказал, веб-сайт, выполняющий преобразование, предоставляет три массива для каждой данной заметки. Высота тона, продолжительность и задержка...., @user1584421

Однако, поскольку эта функция работает и совместима с форматом, зачем ее менять? Вероятно, существует ошибка в том, как я анализирую эти данные из PROGMEM. Я обновил вопрос тем, что пробовал в последний раз., @user1584421

@user1584421 user1584421 Да, это работает, но, вероятно, не так, как вам хочется. Если в вашем массиве 1283 ноты, вы вызываете playMelodyExtended2 с номером number_of_notes = 1283. Затем этот цикл for воспроизводит первую ноту 1283 раза. Затем он возвращается, считывается следующая нота/длительность/задержка, затем снова вызывается playMelodyExtended2 для этой второй ноты. И он играет вторую ноту 1283 раза. Повторяйте, пока все 1283 ноты не будут сыграны 1283 раза каждая. Ваш Edit 3 также не исправляет это., @orithena

Я также попробовал первый подход с memcpy_P, но я сделал массив задержки [] как uint16_t. И это работает, но потом вдруг перестает.... Так и лучше, потому что при оригинальном подходе вообще не играло. Но теперь он все еще не воспроизводит всю мелодию., @user1584421

@orthena Теперь я поняла. Мне следует удалить цикл for внутри функции playMelodyExtended2(). РЕДАКТИРОВАТЬ: Я только что это сделал, и это не работает так, как задумано., @user1584421

@ user1584421 Да. Затем вы также можете удалить аргумент number_of_notes. Или, альтернативно, всегда вызывать playMelodyExtended2 с number_of_notes = 1, если этот аргумент понадобится вам в будущем для других целей., @orithena

@user1584421 user1584421 Тогда пришло время, наконец, опубликовать полный код, особенно фактическое объявление массивов PROGMEN (первых трех значений каждого массива будет достаточно). Кроме того, добавьте отладочный вывод фактических значений в playMelodyExtended2, о котором я упоминал вчера, сравните их с вашими входными массивами, и после этого мы, возможно, сможем помочь вам в дальнейшем., @orithena

@EdgarBonet Большое спасибо! Это было оно!, @user1584421

@orthena Последнее изменение в этом ответе сработало! Мой код был включен в мой вопрос, это было то, что я опубликовал. Определения PROGMEM были тривиальными. const int My_Melody[] PROGMEM = { 55, //... }; const float My_noteDurations[] PROGMEM = { 200.7139, //.... }; const uint16_t My_Delay[] PROGMEM = { 214, //... };, @user1584421