Аудио проигрыватель на Atmega8 и WTV020. Аудио проигрыватель на Atmega8 и WTV020 Atmega8 мелодии

В этой статье мы рассмотрим способ воспроизведения тональных сигналов и научимся проигрывать монофоническую мелодию.

Подготовка к работе

В программе объявляются два массива. Массив с нотами notes содержит простое перечисление нот. Этим нотам сопоставляется длительность звучания в массиве beats . Длительность в музыке определяется делителем ноты по отношению к целой ноте. За целую ноту принимается значение 255 . Половинки, четверти, восьмые получаются путем деления этого числа.
Обратите внимание, что длительность первой же ноты не получается путем деления 255 на степень двойки. Тут придется переключиться на теорию музыки. Ноты исходной мелодии можно посмотреть . Эти ноты объединены в триоли. При таком объединении три ноты по одной восьмой звучат также, как одна четвертая. Поэтому их относительная длительность 21.
Также пользователю необходимо явно указать количество нот в последовательности директивой:

# define SEQU_SIZE 19

В основной программе в первую очередь происходит пересчет массивов частот и длительность в периоды сигналов и длительность нот.
С периодами сигналов (массив signal_period ) все просто. Чтобы получить длительность периода в микросекундах достаточно разделить 1000000 на частоту сигнала.
Для расчета абсолютной длительности звучания нот необходимо, чтобы был указан темп музыкального произведения. Делается это директивой

# define TEMPO 108

Темп в музыке, это количество четвертей за минуту. В строке

# define WHOLE_NOTE_DUR 240000 / TEMPO

рассчитывается длительность целой ноты в миллисекундах. Теперь достаточно по формуле пересчитать относительные значения из массива beats в абсолютные массива note_duration .
В основном цикле, переменная elapsed_time инкрементируется после каждого периода воспроизводимого сигнала на длительность этого периода до тех пор, пока не превысит длительность звучания ноты. Стоит обратить внимание на эту запись:

while (elapsed_time < 1000 * ((uint32_t) note_duration[ i] ) )

Переменная elapsed_time 32ух-битная, а элементы массива notes_duration 16ти-битная. Если 16ти битное число умножить на 1000, то гарантированного наступит переполнение и переменная elapsed_time будет сравниваться с мусором. Модификатор (uint32_t) преобразует элемент массива notes_duration[i] в 32ух-битное число и переполнение не наступает.
В цикле воспроизведения звука вы можете увидеть еще одну особенность. В нем не получится использовать функцию _delay_us() , так как ее аргументом не может быть переменная.
Для создания таких задержек используется функция VarDelay_us() . В ней цикл с задержкой в 1мкс прокручивается заданное количество раз.

void VarDelay_us(uint32_t takt) { while (takt- - ) { _delay_us(1 ) ; } }

При воспроизведении мелодии используется еще две задержки. Если ноты будут воспроизводиться без пауз, то они будут сливаться в одну. Для этого между ними вставлена задержка 1мс, заданная директивой:

# define NOTES_PAUSE 1

После каждого полного цикла проигрывания мелодии программа делает паузу в 1с и начинает воспроизведение заново.
В итоге мы получили код, в котором легко изменить темп, поправить длительности или полностью переписать мелодию. Для этого достаточно будет только преобразовывать только часть программы с директивами и объявлением переменных.

Индивидуальные задания

  1. В предложенной мелодии попробуйте изменить темп исполнения и сделайте паузу 5 секунд между повторами.
  2. Элементы массива beats принимают значения только от 0 до 255. Измените разрядность элементов массива и посмотрите в выводе компилятора, как это повлияет на объем памяти, занимаемой программой.
  3. Теперь попробуйте самостоятельно изменить мелодию. Например, вот “Имперский марш” из того же кинофильма: int notes = { A4, R, A4, R, A4, R, F4, R, C5, R, A4, R, F4, R, C5, R, A4, R, E5, R, E5, R, E5, R, F5, R, C5, R, G5, R, F5, R, C5, R, A4, R} ; int beats = { 50 , 20 , 50 , 20 , 50 , 20 , 40 , 5 , 20 , 5 , 60 , 10 , 40 , 5 , 20 , 5 , 60 , 80 , 50 , 20 , 50 , 20 , 50 , 20 , 40 , 5 , 20

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

    Представляю две такие простые схемы на микроконтроллерах AVR ATmega8 и Attiny2313, точнее схема одна, просто реализована для работы на этих двух микроконтроллерах. Кстати в архиве вы найдете два варианта прошивок для микроконтроллера Атмега8, из которых первый воспроизводит звук похожий на автомобильную сигнализацию, а второй звук похож на охранную сигнализацию здании(быстрый и резкий сигнал).

    Все прошивки можете скачать ниже в архиве (они все подписаны), в архиве вы также найдете симуляцию схем в Proteus, а это значит что послушав все мелодии вы сами из списка сможете подобрать то что больше понравился.

    Ниже схема сигнализации на Atmega8

    Список применяемых радиокомпонентов в схеме Атмега8

    U1- Микроконтроллер AVR 8-бит ATmega8-16PU, кол. 1,
    R1- Резистор с номиналом 47 Ом, кол. 1,
    R2, R3- Резистор с номиналом 270 Ом, кол. 2,
    D2,D3-светодиод, кол. 2,
    LS1-динамик, кол. 1,
    S1- датчик.

    А в схеме сигнализации на Attiny2313 изменен только мк.
    U1- Микроконтроллер AVR 8-бит ATtiny2313-20PU, кол. 1.

    Печатная плата для Atmega8 выглядит следующим образом:

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

    Принцип работы следующее. Как только мы подаем питание, сразу же загорается или начнет мигать(в зависимо от прошивки) светодиод(в схеме D3), и если датчика не будем трогать, то сигнализация будет молчать. Вот если сработал датчик то и сирена заработает, также будет мигать светодиод, но уже D2.

    Если хотите чтобы при работе сигнализации моргал также фары автомобиля, то для этого нужно вывод микроконтроллера 24 РС1 подключить к реле через транзистор, а сам реле уже к фарам. Чтобы отключить сирену необходима выключить и снова включить прибор, или просто нажать на кнопку. Для работы микроконтроллера нужен внутренний генератор на 8МГЦ,

    Если хотите как то усилить звук сигнализации, то с транзисторами можете собрать усилитель и подключить к схеме. Я именно так и сделал, только в данной схеме этого не изобразил.

    Перейдем к схеме на Attiny 2313, в нем как и сказал раньше все те же детали и тот же принцип работы, только изменен МК, в следствие и подключенные выводы.Такой микроконтроллер работает от внутреннего генератора 4МГц, хотя можно и на 1Мгц прошить.

    Ниже схема подключении уже на Attiny2313

    Для данной мк написал всего одну версию прошивки, собрал все на какетной плате, проверил, все нормально работает.
    А фьюзи нужны выставить ниже представленным образом:


    В статье описываются принципы синтеза музыки на AVR. Прилагаемое ПО позволяет сконвертировать любой midi файл в исходный код на C для микроконтроллеров AVR, чтобы добавить воспроизведение музыкальных фрагментов готовые разработки. Рассмотрен пример использования ПО в музыкальной шкатулке.

    Для начала, небольшое видео, как всё работает:

    Что позволяет ПО

    ПО для PC позволяет получить исходник на C для CodeVision AVR, который воспроизводит выбранный midi файл:

    1. В свой проект подключаем common\hxMidiPlayer.h, common\hxMidiPlayer.c. Копируем заготовки ATMega8Example\melody.h, ATMega8Example\melody.c, ATMega8Example\hxMidiPlayer_config.h и подключаем.
    2. Запускаем MidiToC.exe
    3. Загружаем Midi файл.
    4. Настраиваем проигрыватель: sampling rate, количество каналов, waveform и др. ПО воспроизводит мелодию так же, как будет играть AVR.
    5. Нажимаем “Create player config” и пастим исходник в hxMidiPlayer_config.h.
    6. Нажимаем “Create melody code” и пастим исходник в melody.c
    7. В своём проекте реализуем метод Player_Output() для вывода звука через PWM или внешний ЦАП.
    8. Настраиваем таймер на частоту Sampling rate, из прерывания вызываем Player_TimerFunc().
    9. Вызываем Player_StartMelody(&s_melody, 0).

    Мелодия воспроизводится из прерывания таймера. Это значит, что во время воспроизведения микроконтроллер также может заниматься полезной работой.

    Как это работает

    В остальной части статьи я постараюсь кратко объяснить, как всё это реализовано. К сожалению, совсем кратко не получится – материала очень много. Если не интересно – можно сразу перейти к разделам “Описание ПО” и “API плейера”.

    Что такое музыка

    Музыка – это последовательность звуков различной частоты и длительности. Частота основной гармоники звука должна соответствовать частоте определённой ноты. Если частота колебаний звуков отличается от частот нот – нам кажется, что музыкант “фальшивит”.

    Таблица. Частоты нот, Гц.

    Все ноты поделены на октавы, по 7 нот в каждой + 5 полутонов (чёрные клавиши на пианино). Частоты нот соседних октав отличаются ровно в 2 раза.

    Простейший музыкальный плейер содержит таблицу с последовательностью нот(нота+длительность) мелодии и таблицу с частотами нот. Для синтеза звука используется один из каналов таймера, который формирует меандр:

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

    Реальная мелодия содержит как минимум две партии (соло + бас), к тому же при игре на пианино предыдущая нота всё ещё продолжает звучать, когда началась следующая. Это легко понять, вспомнив устройство пианино – каждой ноте соответствует отдельная струна. Мы можем заставить звучать несколько струн одновременно, проведя рукой по клавишам.

    В некоторых микроконтроллерах есть несколько каналов таймера, их можно использовать для воспроизведения нескольких нот одновременно. Но обычно эти каналы являются ценным ресурсом, и использовать их все – нежелательно. Если мы, конечно, не делаем просто музыкальную шкатулку.
    Итого, чтобы получить полифонию и различные звуки музыкальных инструментов, нужно использовать синтез звука.

    Синтез звука на AVR

    hxMidiPlayer использует синтез звука и может воспроизводить полифонию с различными формами волны. Плейер рассчитывает амплитуду выходного сигнала в обработчике прерывания от таймера с частотой 8-22КГц (насколько хватит мощности процессора; также зависит от формы волны и количества каналов).

    Принцип синтеза звука можно объяснить на примере синтеза синусоиды.

    Возьмём таблицу размером 64, в каждой ячейке которой записаны значения амплитуды синуса в точках index * 2 * PI / 64 (один период):

    Static const flash uint8_t s_sineTable[ 64 ] = { 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8D, 0x8F, 0x90, 0x91, 0x93, 0x93, 0x94, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x93, 0x93, 0x91, 0x90, 0x8F, 0x8D, 0x8C, 0x8A, 0x88, 0x86, 0x84, 0x82, 0x80, 0x7E, 0x7C, 0x7A, 0x78, 0x76, 0x74, 0x73, 0x71, 0x70, 0x6F, 0x6D, 0x6D, 0x6C, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6C, 0x6D, 0x6D, 0x6F, 0x70, 0x71, 0x73, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E };

    128 (0x80) соответствует нулю, 255 (0xff) – наибольшей положительной точке, 0 – наибольшей отрицательной точке.

    Теперь допустим, мы будем выводить значения и з таблицы на внешний ЦАП в прерывании от таймера, вызываемого с частотой 1000 Гц:

    Static uint8_t s_index = 0; // Timer1 output compare A interrupt service routine interrupt void timer1_compa_isr(void) { SetDac(s_sineTable[ s_index]); if (s_index == 63) { s_index = 0; } else { s_index++; } }

    Что мы получим на выходе? Мы получим синусоидальные колебания с частотой 1000/64 Гц.

    Теперь давайте увеличивать индекс в прерывании не на 1, а на два.
    Очевидно, что частота выходных колебаний будет уже 1000/64 *2 Гц.

    В общем случае, чтобы получить частоту F, нужно увеличивать индекс в таблице на:
    add = F / 1000 * 64

    Это число может быть дробным, но для получения высокой скорости работы используют fixed point arithmetic.

    Количество записей в таблице и частота таймера влияют на качество синтезированного звука. В нашем случае достаточны 64 записи в таблице на период, и частота таймера 12kHz. Минимально приемлемая частота таймера – 8кГц, идеальная – 44кГц.

    Очевидно, что при частоте таймера 12кГц мы сможем сгенерировать максимум 6кГц меандр, так как нужно сделать как минимум два переключения за период. Однако, частоты выше всё равно будут узнаваемы, если правильно рассчитывать состояние выхода на каждом тике таймера.

    В таблицу можно внести значения для периода колебаний несинусоидальной формы и получить другой звук.

    Затухание

    Если музыкальный инструмент основан на струнах(например, пианино), то после нажатия клавиши звук плавно затухает. Чтобы получить более естественное звучание синтезатора, необходимо плавно уменьшить амплитуду колебаний после старта ноты (“обернуть” колебания в форму затухания – “envelope”).

    Плейер содержит таблицу затухания, которую использует для уменьшения амплитуды синуса (или другой формы волны) с момента старта ноты.
    “Синус” “завёрнутый” в такую оболочку напоминает звук механической музыкальной шкатулки.

    Синтез меандра

    Особая форма волны меандра позволяет значительно упростить синтез. Таблицы при этом не используются. Достаточно рассчитать, какое состояние (1 или 0) должен иметь выход при заданной частоте на текущем тике таймера. Это делается с помощью целочисленной арифметики, работает очень быстро, чем и объясняется популярность использования меандра для воспроизведения мелодий в 8-бит приставках.

    Пример: объявляем счетчик:

    Static uint16_t s_counter = 0;

    который будем увеличивать на 0x8000 в каждом прерывании от таймера, а в порт будем выводить старший бит счётчика:

    // Timer1 output compare A interrupt service routine interrupt void timer1_compa_isr(void) { PORTA.0 = (s_counter >> 15) & 1; s_counter += 0x8000; }

    Поскольку 0x8000 + 0x8000 = 0x10000, происходит переполнение переменной s_counter, 17-й бит отбрасывается, и в переменную записывается 0x0000.
    Таким образом, при частоте таймера 8КГц на выходе получится меандр 4КГц.
    Если увеличивать счётчик на 0x4000, то получится меандр 2КГц.

    В общем случае, можно получить частоту F, если прибавлять:
    add = F / 8000 * 0x10000

    Например, чтобы получить меандр частотой 1234Гц, нужно прибавлять 0x277C. Реальная частота немного будет отличаться от заданной, потому что мы округляем слагаемое до целого числа. В синтезаторе это допустимо.

    Синтез звуков реальных инструментов

    Можно оцифровать звук ноты До пианино (с помощью АЦП сохранить в памяти значения амплитуды звука через равные промежутки времени):
    а потом воспроизвести звук (с помощью ЦАП вывести записанные значения через равные промежутки времени).

    В общем случае, для синтеза ударных необходимо записать звуки барабанов и воспроизводить их в нужный момент. В 8-бит приставках вместо звуков барабанов используют “белый шум”. Значения амплитуды для “белого шума” получают с помощью генератора случайных чисел. Затраты по памяти при этом – минимальны.
    hxMidiPlayer использует “белый шум” для синтеза ударных.

    Микширование каналов

    Амплитуда звука на данном тике таймера рассчитывается для каждого канала отдельно. Чтобы получить финальное значение амплитуды, необходимо прибавить значения всех каналов. По-правильному необходимо корректировать сумму, так как воспринимаемая громкость подчиняется логарифмической зависимости, но в таком простом синтезаторе придётся обойтись простым сложением. Поэтому максимальная амплитуда каждого канала равна 255/N.

    Вывод звука из AVR

    После проведения всех необходимый вычислений, плейер получает уровень сигнала, который нужно перевести в аналог. Для этих целей можно использовать внешний DAC или PWM.
    Здесь следует заметить, что в обоих случаях полученный сигнал желательно отфильтровать – убрать высокочастотные шумы, которые возникают из-за низкой частоты синтеза и округлений.

    Вывод на внешний параллельный DAC

    Поскольку не имеет смысла использовать точные микросхемы DAC, в таких проектах обычно обходятся R2R матрицей:

    При такой схеме мы просто выводим вычисленную амплитуду в порт:

    PORTB = sample;

    Недостатки:
    1) на выходе R2R матрицы получается слишком слабый сигнал, использование аналогового усилителя обязательно;
    2) необходимо использовать как минимум 5 выводов (а лучше 8);
    Этот метод оправдан только тогда, когда нет свободных PWM каналов.

    (для экономии выводов можно воспользоваться внешним АЦП с SPI интерфейсом).

    PWM

    Если есть свободный PWM канал, то проще всего воспользоваться именно этим способом.

    Инициализация PWM (ATMega8):

    // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: 20000,000 kHz // Mode: Fast PWM top=0xFF // OC2 output: Non-Inverted PWM ASSR=0x00; TCCR2=0x69; TCNT2=0x00; OCR2=0x00; И вывод сампла: void Player_Output(uint8_t sample) { OC2 = sample. }

    Обычная практика использования PWM подразумевает сглаживание выходного сигнала с помощью RC фильтра:

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

    Чтобы упростить схему, лучше оставаться “в цифре” до самого динамика. Поскольку дешёвый динамик всё равно не может воспроизводить частоты выше 30кГц – фильтровать их не нужно. Диффузор сам “отфильтрует” высокие частоты PWM.

    Если необходимо получить больший ток, можно применить усилитель на транзисторе. R1 выбирается так, чтобы обеспечить необходимый ток на динамике.

    Так можно подключать небольшие динамики из игрушек:

    Для динамиков побольше лучше собрать раскачку на 2-х транзисторах и поставить LC – фильтр, чтобы убрать шумы:

    Конденсатор С1 служит для ограничения тока через динамик, когда ШИМ не работает. Также благодаря включению последовательного конденсатора, на динамик попадает сигнал, симметричный относительно нуля. Таким образом, диффузор динамика будет двигаться относительно центрального “расслабленного” положения, что положительно сказывается на качестве звука.
    В данном случае транзисторы работают в ключевом режиме, поэтому компенсировать смещение базы не нужно.

    PWM, подключение к двум выводам

    Недостатком первых двух схем является то, что на динамик подаётся ток одного направления. Если мы будем изменять направление тока, то громкость можно увеличить в 2 раза не превышая допустимую мощность. Для этого динамик подключается к двум выводам микроконтроллера – неинвертированному и инвертированному, например OC1A и /OC1A. Если неинвертированный вывод отсутствует, можно использовать второй канал в инвертированном режиме (OC1B):

    // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 24500,000 kHz // Mode: Fast PWM top=0x00FF // OC1A output: Non-Inv. // OC1B output: Inverted // Noise Canceler: Off // Input Capture on Falling Edge // Timer1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0xB1; TCCR1B=0x09; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; void Player_Output(uint8_t sample) { OCR1A = sample; OCR1B = sample; }

    PWM, Два вывода, усилитель D-класса

    Недостатком предложенных схем является потребляемый ток во время тишины.
    “Тишина” у нас соответствует уровню сигнала 128, то есть ШИМ с 50% заполнением – ток через динамик течёт всегда!

    Немного изменив программную часть, можно получить достаточно мощный программно-аппаратный усилитель D класса:

    Void Player_Output(uint8_t sample) { if (sample >= 128) { TCCR2=0x21; //normal, clear on compare match TCCR2=0x21 | 0x80; //CLEAR OC2 PORTC.0 = 0; TCCR2=0x69; //non-inverting PWM OCR2 = (sample-128) * 2; } else// if (sample < 128) { TCCR2=0x31; //normal, set on compare match TCCR2=0x31 | 0x80; //SET OC2 PORTC.0 = 1; TCCR2=0x79; //inverting PWM OCR2 = (128-sample) *2; } }

    При этом одна пара транзисторов подключается к выходу PWM, вторая – к обычному цифровому выходу.

    Как видно из кода, мы рассматриваем сигнал выше 128 – как ток, направленный в одну сторону, а сигнал ниже 128 – как ток, направленный в другую сторону. При уровне 128 оба вывода динамика подключены к одному и тому же выводу источника питания, и ток отсутствует. При отклонении от уровня 128 заполнение PWM увеличивается, и через динамик течёт ток соответствующей полярности.

    Важным моментом реализации является принудительное переключение вывода PWM в нужное состояние в момент переключения второго (обычного цифрового) вывода (PORTC.0). Запись в регистр OCR2 буфферизируется, чтобы исключить “глюки” PWM. Нам же необходимо переключить вывод PWM сразу, не дожидаясь окончания периода.

    Последняя схема IMHO является наилучшим вариантом в плане простоты, экономии энергии, и выходной мощности.

    Вывод звука с формой волны SquareWave

    При синтезе меандра применяются упрощённые алгоритмы.

    Каждый канал (включая ударные) выводит на выход либо 0, либо 1. Таким образом, 3-х канальный проигрыватель выводит на выход значения в диапазоне 0..3. Поэтому при использовании PWM процедура вывода выглядит:

    Void Player_Output(uint8_t sample) { OCR2 = sample * (255 / HXMIDIPLAYER_CHANNELS_COUNT); }

    Если же не использовать PWM, то для вывода 3-х канальной мелодии достаточно двух обычных цифровых выходов и 2-х битовой R2R матрицы.

    Формат MIDI

    Если посмотреть на полученный код мелодии, то легко заметить, что в массиве используются повторяющиеся числа из небольшого диапазона. Это и понятно: в мелодии используется ограниченное количество нот в пределах 1-2 октав, темп мелодии фиксированный – одинаковые задержки, количество каналов находится в диапазоне 0..15.
    Всё это означает, что полученный массив можно значительно уменьшить, применив какой-нибудь алгоритм сжатия.
    Алгоритмы типа ZIP дают хорошее сжатие, но также требуют много памяти для работы (словарь ZIP – 64Кб). Мы можем применить очень простой метод сжатия, практически не требующий памяти, суть которого состоит в следующем.

    В одной байте все числа равномерно распределены в диапазоне 0…255, и каждое число представляется 8-мью битами. В нашем случае некоторые числа встречаются гораздо чаще, чем другие. Если кодировать часто встречающиеся числа меньшим количеством битов, а реже встречающиеся – большим, можно получить выигрыш по памяти.

    Выбираем фиксированный способ кодирования: комбинации битов 000,001 и 010 (длина – 3 бита) будут представлять 3 наиболее часто встречающиеся числа. Комбинации битов 0110, 0111 (длина – 4 бита) – следующие 2 наиболее часто встречающиеся числа и т.д:

    //000..010 - 0..2 //011 x 3..4 //100 xx 5..8 //101 xxx 9..16 //110 xxx 17..24 //111 immediate

    Комбинация, начинающаяся с 111 (длина – 11 бит) будет кодировать все остальные числа.
    Способ кодирования битами может быть другой. Я перепробовал несколько методов и выбрал этот, как дающий наилучшие результаты на таких данных.

    Процедура сжатия выглядит так:
    1. Подсчитать общее количество числа X в потоке для X = .
    2. Отсортировать по уменьшению частоты появления в потоке.
    3. Взять первые 25 чисел. Они будут кодироваться меньшим количеством бит.
    4. Закодировать входной поток.

    На выходе получаем массив из 25 наиболее часто встречающихся чисел и битовый поток.
    Такая компрессия позволяет получить 50% сжатие при мизерных затратах памяти и производительности. К сожалению, код плейера при этом увеличивается, поэтому для коротких мелодий применять сжатие не рекомендуется.

    Хранение частот нот

    Довольно накладно по памяти хранить частоты всех нот в таблице. На самом деле существует формула для определения частоты ноты по её midi номеру:

    F = 2^((N - 69)/12) * 440, Гц

    Но вычислять дробную степень достаточно сложно. Вместо этого плейер хранит 12 частот нот верхней октавы. Частоты нот нижних октав определяются уменьшением частоты в 2^Y более раз, где Y – количество октав вниз.

    Дальнейшее развитие компресии

    В мелодии часто встречаются повторяющиеся фрагменты (“припевы”, “куплеты”). Найдя повторяющиеся фрагменты и представив мелодию в виде фрагментов, можно уменьшить мелодию ещё процентов на 50%, почти не затрачивая оперативную память и производительность. Я не стал реализовывать такой алгоритм, чтобы не усложнять проект.

    Описание ПО

    Основное окно программы-конвертора:

    Кнопка Load Midi позволяет загрузить midi файл. Программа сразу начинает воспроизведение файла с текущими выбранными параметрами, имитируя звук, который будет в “железе”.

    Окно информации (4) отображает:
    – Length – длина выбранного фрагмента мелодии в мс;
    – Max Active syntezer channels – максимальное количество одновременно активных каналов синтезатора;
    – Max active drum channels – максимальное количество одновременно активных каналов синтезатора, воспроизводящих “ударные”;
    – Max active stereo notes – максимальное количество каналов, воспроизводящих одну и ту же ноту (см. ниже);
    – Estimated size, bytes – размер мелодии в байтах. В режиме “Custom Sample” размер отображается в виде A+B, где А – размер мелодии, B – размер семпла. Размер кода проигрывателя здесь не указывается.

    Окно прогресса отображает текущее положение воспроизведения.
    Можно кликнуть на progressbar, чтобы начать воспроизведение с указанного момента.
    Input Box слева и справа позволяют указать начало и конец фрагмента мелодии в мс.

    Метка “Not enought channels to play melody” красным цветом указывает, что для воспроизведения мелодии при текущих настройках недостаточно каналов синтезатора. Если проигрыватель не находит свободный канал, то выключает самую давнюю ноту. Во многих случаях это будет работать нормально. Увеличивать количество каналов имеет смысл только тогда, когда мелодия на слух звучит неправильно.

    Настройки условно можно разделить на настройки проигрывателя и настройки обработки midi файла. Проигрыватель сможет воспроизвести полученный код мелодии, если конфигурация проигрывателя и код мелодии были созданы с одинаковыми настройками проигрывателя. Кроме того, проигрыватель сможет воспроизвести мелодию, код которой создан для проигрывателя с меньшим (но не бОльшим) количеством каналов.

    Аппаратные настройки проигрывателя включают:

    – Sampling Rate – частота синтеза. Максимальная частота синтеза определяется экспериментально. В расчёте на Atmega 16MHz, можно начать с 12000Гц для проигрывателя с 6 каналами, и повышать по желанию, пока в аппаратном проигрывателе на слух не станут заметны искажения мелодии. Максимальная частота зависит от количества каналов, формы волны и сложности самой мелодии.

    – Waveform – форма волны:
    – Square wave – меандр;
    – Sine – синус;
    – Sine + Envelope – синус с затуханием;
    – Waveform * + Envelope – различные варианты несинусоидальных волн с затуханием и без;
    – Custom Sample – использовать семпл инструмента.

    Кнопка “Load Sample” позволяет загрузить семпл из WAV файла. WAV файл должен быть в формате PCM 8-bit mono, 4173Гц, нота До-5. Hint: Можно повысить частоту и понизить ноту, но в настройках проигрывателя изменить Pitch. Никаких проверок формата не производится – если формат другой, то звук будет воспроизводиться неправильно.
    Pitch – позволяет изменить высоту звука. Например, чтобы играть на 1 октаву выше, нужно выставить Pitch +12.

    Use compression – использовать сжатие мелодии.
    Enable drums synteser – включить синтезатор ударных.

    Player Channels: количество каналов синтезатора (максимальное количество нот, которые будут звучать одновременно).

    Настройки обработки midi файла включают:

    Обычно, такая тонка настройка не требуется. Эти настройки можно оставить по умолчанию.

    API плейера

    Реализация плейера находится в файлах Common\hxMidiPlayer.c и Common\hxMidiPlayer.h. Эти файлы необходимо подключить к проекту. Также необходимо создать файл hxMidiPlayer_config.h, в который нужно поместить конфигурацию.
    Плейер написан на C без ассемблерных вставок, что позволит легко портировать его на другие микроконтроллеры.

    Extern void Player_StartMelody(const flash TMelody* _pMelody, uint16_t _delay);

    Начать воспроизведение мелодии. _delay задаёт начальную задержку перед воспроизведением, 255 единиц = 1 секунда.

    Void Player_Stop();

    Прекратить воспроизведение мелодии.

    Extern bool Player_IsPlaying();

    Возвращает false, если воспроизведение мелодии завершено.

    Extern void Player_WaitFinish();

    Подождать, пока воспроизведение мелодии завершится.

    Extern void Player_TimerFunc();

    Эту функцию нужно вызывать в прерывании от таймера с частотой семплирования, заданной в конфигурации. Когда воспроизведение мелодии завершено, вызовы можно не делать.

    Extern void Player_Output(uint8_t sample);

    Должна быть реализована пользователем. Вызывается плейером, когда необходимо вывести следующий семпл.

    Extern void Player_Started();

    Должна быть реализована пользователем. Вызывается, когда плейер начинает воспроизводить мелодию. Может быть использована для настройки прерываний от таймера.

    Extern void Player_Finished();

    Должна быть реализована пользователем. Вызывается, когда плейер завершил воспроизведение мелодии. Может быть использована, чтобы отключить прерывания от таймера, или запустить воспроизведение другой мелодии.

    //#define NOTES_TO_EEPROM //#define SINETABLE_TO_EEPROM //#define ENVELOPE_TO_EEPROM

    Эти строки нужно раскомментировать в файле hxMidiPlayer_config.h, если таблицу нот, таблицу синуса и таблицу затухания необходимо расположить в eeprom.

    Проекты-примеры

    ATMega644Example – проект для ATMega644, 25MHz, вывод PWM на PB3.

    Требования к памяти

    Таблица. Размер плейера и мелодий во flash.

    *при добавлении плейера в имеющийся непустой проект, размер кода будет меньше

    **для нормального воспроизведения мелодии не хватает каналов

    Мелодия 1: bach_minuet_in_g.mid, 35 сек
    Мелодия 2: yiruma-river_flows_in_you.mid, 165 сек
    Мелодия 3: Franz Schubert – Serenade.mid, 217 сек

    Как видно из таблицы, в минимальной конфигурации можно втиснуть достаточно длинную мелодию даже в ATTiny2313. Компрессия может дать более чем двукратное уменьшение мелодии, но размер кода плейера при этом увеличивается на ~600 байт.

    Таблицы нот синуса и затухания можно поместить в EEPROM, сэкнономив примерно 16, 50 и 100 байт flash соответственно.

    При использовании семпла из wav файла, к размеру кода плейера нужно добавить собственно размер семпла в байтах.

    Пример использования

    В качестве примера использования плейера расcмотрим процесс создания музыкальной шкатулки.

    Берём готовую шкатулку из МДФ:

    В качестве микроконтроллера берём ATTiny85 в SO-8 корпусе как наиболее дешёвый с достаточно большим количеством памяти. Мы разгоним его до 27МГц, чтобы получить частоту синтеза 18Кгц при 4-х каналах Sine+Envelope.

    Усилитель будет D-класса на на 4-х транзисторах, чтобы экономить батареи.

    Транзисторы работают в ключевом режиме и могут быть любые. Дроссель L1 и конденсатор C6 подбираются по вкусу для получения звука без высокочастотных шумов. R1 и R2 можно поднять до 2К, чтобы снизить громкость и уменьшить дребезг динамика.

    Конечный выключатель из дисковода подходит идеально, будто специально для шкатулки и создан (работает на размыкание – при открывании крышки на плату подаётся питание):

    Исходники прошивки находятся в каталоге ATTiny85MusicBox.

    В 8Кб поместились:
    1) плейер: 18000Гц, 4 канала, Sine+Envelope, Pitch+12, сжатие, воспроизводит мелодии по очереди (последняя сохраняется в EEPROM)
    2) Yiruma – River Flows in You
    3) Франц Шуберт – Серенада
    4) П.И. Чайковский “Октябрь”

    Результат на видео:

    Дальнейшее развитие

    В принципе, плейер можно и дальше «наворачивать», доведя до полноценного Midi или MOD плейера. Я лично считаю, что для получения высококачественной мелодии проще будет подключить SD карту и играть с нее любые WAV файлы с намного лучшим качеством, чем вообще возможно получить программным синтезом. И такой плейер программно и аппаратно на порядок проще. Ниша hxMidiPlayer – добавление неплохого звука в готовые прокты, когда осталось пару ножек и немного места во flash. С этой задачей он справляется на “отлично” уже в существующем виде.

    Думаю, на этом вопрос создания всяких музыкальных шкатулок/звонков на AVR можно закрыть 🙂

    Сидел я на днях думал, чего бы такого к своему скутеру "присобачить": музыка есть, подсветка есть, но чего то не хватает, и тут я вспомнил про сигнализацию, точно! Ведь как раз ее то у меня и нету! Предлагаю и вам тоже собрать сигнализацию для своего двухколесного – например велосипеда, а может быть и четырехколесного друга. Сигнализация собрана на микроконтроллере AVR ATmega8, проект так же повторен на микроконтроллере Attiny2313. Для варианты схемы на Atmega8 я написал три варианта прошивок, одна прошивка воспроизводит звук напоминающий сигнализацию автомобиля, а другой похож на сирену охранной сигнализации расположенной в здании (более быстрая и резкая мелодия). Все прошивки подписаны и лежат ниже в архиве, думаю вы в них разберетесь. Кроме того, в архиве содержится симуляция схем в протеусе, так что вы сможете прослушать звуки и подобрать свой вариант, который вам больше по душе.

    Схема на Atmega8:

    Как видите, ничего особенного, микроконтроллер, три резистора и два светодиод с динамиком. Вместо кнопки на схеме можно использовать например геркон, или другой контакт. Схема работает следующим образом, если подать питание то загорится (или замигает – в зависимости от варианта схемы) светодиод D3, если датчик не тронут, то сирена будет молчать. Как только сработает датчик сработает сигнализация и одновременно с этим будет мигать светодиод D2. Лично я вывод 24 PС1 через транзисторный ключ подключил к релюшке, а реле последовательно передней фаре скутера, так чтобы когда сработает сигнализация мигала фара скутера. Для того чтобы остановить сирену нужно выключить и включить схему или снова нажать на кнопку. Хочу заметить, что сигнал с контроллера можно усилить несколькими транзисторами собрав небольшой усилитель – что я в принципе и сделал, правда на схеме эту цепь не изобразил. Микроконтроллер работает от внутреннего генератора 8 МГц, фьюзы выставляем соответствующие.

    Печатная плата для Atmega8 выглядит следующим образом:

    Схема на Attiny2313 не сильно отличаются от первого варианта, просто там другие порты вывода.

    Схема на Attiny2313:

    Для этого варианта схемы я написал всего одну прошивку, с одним вариантом сигнала, схему на всякий случай собрал навесным монтажом и проверил работоспособность. Микроконтроллер работает от внутреннего генератора 4 МГц (можно прошить на 1 МГц), фьюзы при программировании выставляем следующие:

    Так как под рукой не было живого контроллера Atmega8, я собрал схему на Attiny2313, схема заработала сразу, собирал схему навесным монтажом, ниже фото:

    Ну и видео работы схемы, видео правда не самого лучшего качества и на нем не видно мигания светодиода, потому что частота кадров низкая.

    Скачать проекты в , прошивки и файлы печатных плат вы можете ниже

    Список радиоэлементов

    Обозначение Тип Номинал Количество Примечание Магазин Мой блокнот
    U1 МК AVR 8-бит

    ATmega8-16PU

    1 В блокнот
    R1 Резистор

    47 Ом

    1 В блокнот
    R2, R3 Резистор

    270 Ом

    2 В блокнот
    Схема на Attiny2313
    U1 МК AVR 8-бит

    Модуль работает с отформатированными в FAT16 SD-картами объемом не более 2 Гб и воспроизводит в любой последовательности звуковые фрагменты в форматах.ad4 или.wav. Предусмотрено питание внутренних цепей модуля от встроенного стабилизатора на 3.3 В, что очень удобно, поскольку это позволяет на сам модуль подавать напряжение 5 В. (Для этого нужно замкнуть площадку «5V» со средней площадкой на плате модуля, предварительно, разомкнув перемычку из припоя с площадкой «3.3V», как показано на Рисунке 2).

    Управлять модулем можно как «вручную», так и с помощью микроконтроллера. В «ручном» режиме достаточно подключить к устройству кнопки, согласно схеме, представленной на Рисунке 3. В техническом описании модуля WTV020 можно найти другие варианты подключения, которые по функциональности мало чем отличаются от предложенной схемы.

    Динамик подключается к выходам ШИМ или к встроенному 16-разрядному ЦАП. В последнем случае нужно подключать внешний ОУ и усилитель (Рисунок 4). При подключении к ШИМ каналу разрешается коммутировать динамики сопротивлением 8 Ом и мощностью до 0.5 Вт.

    Назначение выводов модуля WTV020 показано в Таблице 1. Каналы двухпроводного интерфейса связи могут использоваться как для подключения кнопок, так и для внешнего микроконтроллера.

    Таблица1.

    Номер
    вывода

    Назначение

    Аудио выход с ЦАП

    Не используется

    ШИМ выход

    ШИМ выход

    Не используется

    Громкость «+» / CLK

    Воспроизведение - пауза

    Громкость «-» / DI

    Не используется

    Следующий файл

    Не используется

    Для визуального контроля были использованы LCD дисплей 2×16 и согласующий контроллер . Общая схема проигрывателя представлена на Рисунке 5. Микроконтроллер и модуль питаются напряжением 3.3 В, дисплей - 4 В, поскольку для выбранного LCD напряжения 3.3 В было недостаточно. На прием данных от МК эта разница напряжений никак не влияет. Внутренний стабилизатор модуля WTV020 автор решил не активировать.

    Для передачи данных в модуль WTV020 используются линии CLK и DI. Согласно техническому описанию (Рисунок 6а), 16 бит данных должны передаваться с периодичностью 200 мкс, однако на практике эти значение нужно увеличить до 2 мс (Рисунок 6б).

    Исходя из документации, после подачи питания рекомендуется на выход «Reset» модуля подать отрицательный импульс длительностью 5 мс, и по истечении 300 мс отправлять команды. Но это явная ошибка, поскольку время инициализации модуля WTV020 составляет порядка 600 мс. Если подавать команды раньше, чем через 600 мс после сброса, модуль их просто не воспринимает.

    Список основных команд, принимаемых модулем, представлен в Таблице 2. Из таблицы видно, что максимальное количество воспроизводимых аудио файлов составляет 512, однако автор ограничился тридцатью. Громкость регулируется в 7 диапазонах. На практике с адреса FFF0 по FFF3 наблюдаются искажения звука, причем как с ШИМ-выхода, так и с ЦАП. Команды FFFE (Play/Pause) и FFFF (Stop/Play) - триггерные.

    На экран LCD выводятся номер воспроизводимого файла и громкость в виде шкалы из 7 закрашенных прямоугольников. Фото готового устройства показано на Рисунке 8.

    Демонстрационное видео:

    Программное обеспечение МК, виртуальная модель Proteus и аудиофайл формата.ad4 -

    Программа для конвертирования аудио записи в формат.ad4 -

Что еще почитать