Суть


Я создал уже некоторое количество разных хоббийных электронных устройств, и у меня есть странная особенность: если на плате присутствует звуковой пьезоэлектрический излучатель(buzzer), я, по окончании основных работ над проектом, начинаю страдать ерундой и заставлять его проигрывать различные мелодии(насколько это возможно). Особенно бывает полезно включить мелодию, по окончании какого либо продолжительного процесса для привлечения внимания. Например, я это использовал, когда построил самодельную экспозиционную камеру для засветки фоторезиста и т.д.

Но когда я начал искать примеры генерации частот для AVR в сети, почему-то мне попадались монструозные, либо недостаточно лаконичные проекты, реализующие генерацию звуковых частот чисто программным образом. И тут я решил во всём разобраться сам…

Лирическое отступление


Моё хобби включает в себя создание различных устройств на микроконтроллерах, т.к это никак не пересекается с моей проф. деятельностью(разработка софта), я считаю себя абсолютным самоучкой, да и в электронике не слишком силен. На самом деле я предпочитаю PIC микроконтроллеры, но так уж случилось, что у меня накопилось некоторое количество AVR микроконтроллеров компании Atmel(сейчас уже Microchip). Сразу оговорюсь, что никогда не держал в руках AVR, т.е. это первый мой проект на MCU Atmel, а именно Atmega48pa. Сам проект выполняет некоторую полезную нагрузку, но здесь я опишу лишь его часть, относящуюся к генерации звуковых частот. Тест для генерации частот я назвал «buzic», как сокращение от buzzer's music. Да чуть не забыл: на Хабре есть пользователь c ником buzic, хотел сразу предупредить, что данная памятка никак не относится к нему и на всякий случай, сразу прошу прощения за использование буквосочетания «Buzic».

Итак, поехали


Я ознакомился с большим количеством примеров из сети — все они построены либо на простейшем цикле в основном теле микропрограммы, либо в прерывании таймера. Но все они используют один и тот же подход для генерации частоты:

  1. подают высокий уровень на ногу микроконтроллера
  2. делают задержку
  3. подают низкий уровень на ногу микроконтроллера

Изменяя задержки и параметры таймера — подгоняют частоту.

Меня данный подход крайне не устраивал, т.к. у меня не было желания писать код для ручного управления ногой микроконтроллера. Я бы хотел, чтобы за меня звуковую частоту генерировал сам «камень», а я просто выставлял значения определенных регистров, тем самым меняя её(частоту).

При изучении даташита(далее ДШ), я таки нашел нужный мне режим таймера — и этим режимом, как Вы уже догадались, является режим CTC(Clear Timer on Compare Match). Так как функция проигрывания музыки является, мягко говоря, не основным функционалом я предпочел выделить для неё таймер 2(пункт 22 ДШ).

Все прекрасно знают, что практически у любого микроконтроллера режим генерации ШИМ сигнала реализован на таймерах и он вполне себе аппаратный. Но в данной задаче ШИМ не подходит т.к. аппаратно будет генерироваться только одна частота. Поэтому нам нужен ЧИМ(частотно импульсная модуляция). Некоторым подобием ЧИМ и является CTC режим таймера(пункт 22.7.2 ДШ).

CTC режим


Таймер 2 в микроконтроллере Atmega48pa 8ми битный, т.е он «тикает» от 0 до 255 и затем идет по кругу. К слову таймер может идти и в другом направлении, но не в нашем случае. Следующим обязательным компонентом является модуль сравнения(Compare Unit). Если говорить совсем грубо, то этот модуль и является инициатором любых событий связанных с таймером. События могут быть разными — такими как прерывания, изменения уровня определенных ног микроконтроллера и т.д.(Очевидно нам интересно второе). Как нетрудно догадаться, модуль сравнения, не просто так назван — он сравнивает определенное значение выбираемое разработчиком микропрограммы с текущим значением таймера. Если значение таймера достигло заданной нами величины, то происходит событие. Также события могут происходить при переполнении таймера либо при сбросе. Ок, мы пришли к тому, что нам удобно, чтобы в определенные моменты таймер вместе с модулем сравнения самостоятельно менял уровень на ноге микроконтроллера на противоположный — генерируя таким образом импульсы.

Второй задачей является задание промежутков между этими импульсами — т.е. управление частотой генерации. Вся уникальность режима CTC заключается в том, что в этом режиме таймер не идет до конца(255), а сбрасывается при достижении заданного значения. Соответственно, изменяя это значение, мы можем фактически управлять частотой. Например если значение модуля сравнения мы задаем в 10, то изменение уровня на ноге микроконтроллера будет происходить в 20 раз чаще, чем если бы мы задали его(значение модуля сравнения) в 200. Теперь мы можем управлять частотой!



Железо



По распиновке микроконтроллера видно, что нам нужно подключить наш buzzer либо к ноге PB3(OC2A) либо к ноге PD3(OC2B), т.к. OC2A и OC2B означает именно то, что на этих ногах таймер 2 может генерировать сигналы.

Схема, которой я обычно пользуюсь для подключения buzzer'а:


И вот мы собрали устройство.

Регистры


В предыдущем пункте мы определились с выбором ноги — это PB3(OC2A), будем с ней работать. Если Вам нужна PD3, то для нее всё будет аналогично, что будет хорошо видно из повествования.

Настройку нашего таймера 2 мы будем производить меняя 3 регистра:
  1. TCCR2A — настройки режима и выбор поведения
  2. TCCR2B — настройки режима и делитель частоты таймера(ещё FOC биты — мы их не используем)
  3. OCR2A (OCR2B для случая с ногой PD3) — значение модуля сравнения


Рассмотрим для начала регистры TCCR2A и TCCR2B

Как видно мы имеем 3 группы битов значащих для нас — это биты серии COM2xx, WGM2x и CS2x
Первое, что мы должны менять — это WGM2x — это главное, чтобы выбрать режим генерации — именно эти биты служат для выбора нашего CTC режима.


примечание: очевидно в ДШ опечатка в «Update of OCR0x at» должно быть OCR2x

Т.е. код будет такой:
TCCR2A = _BV(WGM21) ;

Как видно TCCR2B мы пока не используем т.к. WGM22 должен быть равен нулю, но он и так равен нулю.

Следующим шагом необходимо настроить биты COM2xx, точнее COM2Ax — т.к. мы работает с ногой PB3(для PD3 аналогично используются COM2Bx). От них зависит то, что будет происходить с нашей ногой PB3.

Биты COM2xx зависят от режима, который мы выбрали битами WGM2x, поэтому нам придется найти соответствующий раздел в ДШ. Т.к. у нас режим CTC, т.е. не ШИМ, то ищем табличку «Compare Output Mode, non-PWM», вот она:

Здесь необходимо выбрать «Toggle» — чтобы уровень на ноге менялся на противоположный при достижении таймером заданного значения. Постоянное изменение уровня и реализует генерацию необходимой нам частоты.

Т.к. биты COM2xx также лежат в регистре TCCR2A — то меняется только он:
TCCR2A = _BV(COM2A0) | _BV(WGM21) ;

Естественно, необходимо также выбрать делитель частоты битами CS2x, ну и конечно настроить ножку PB3 на выход… но мы этого делать пока не будем, чтобы при включении МК мы не получили пронзительный визг на непонятной частоте, а вот когда мы будем производить все остальные настройки и включать ножку на выход — будет рассказано ниже.

Итак давайте приведем нашу инициализацию к завершенному виду:

#include <avr/io.h>

//set bit - using bitwise OR operator
#define sbi(x,y) x |= _BV(y)
//clear bit - using bitwise AND operator
#define cbi(x,y) x &= ~(_BV(y))

#define BUZ_PIN PB3

void timer2_buzzer_init()
{
   //обнуляем PB3
   cbi(PORTB, BUZ_PIN);
   //делаем PB3 пока входом, а не выходом
   cbi(DDRB, BUZ_PIN);

   //настройка режимов
   TCCR2A = _BV(COM2A0) | _BV(WGM21) ;

   //обнуляем значение модуля сравнения(хотя я думаю можно и не обнулять)
   OCR2A = 0;
}

Я использовал макросы cbi и sbi(подсмотрел где-то в сети) для установки отдельных битов, да так и оставил. Эти макросы разумеется у меня вынесены в заголовочный файл, но для наглядности я поместил их сюда.

Расчет частоты и длительности нот


Теперь мы подходим к самой сути вопроса. Некоторое время назад, знакомые музыканты пытались вбить в мой мозг программиста некоторое количество информации по поводу нотного стана, мозг у меня чуть не вскипел, но всё же полезную крупицу я вынес из этих бесед.
Сразу предупреждаю — возможны огромные неточности.
  1. каждый такт, состоит из 4 четвертей
  2. У каждой мелодии есть темп — т.е. число таких четвертей в минуту
  3. Каждая нота может проигрываться как весь целый такт, так и его часть 1/2, 1/3, 1/4 и т.д.
  4. У каждой ноты, само собой, есть определенная частота

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

Ну да ладно, будем работать с тем что есть. Самое главное для нас это в конечном итоге получить частоту ноты(на самом деле значение регистра OCR2A) и её длительность, например в миллисекундах. Соответственно необходимо сделать некоторые расчеты.

Т.к. мы находимся в рамках языка программирования, мелодии проще всего хранить в массиве. Логичней всего задавать каждый элемент массива в формате — нота + длительность. Необходимо посчитать размер элемента в байтах, ведь мы пишем под микроконтроллер и с ресурсами тут туго — значит размер элемента в байтах должен быть адекватным.

Частота


Начнем с частоты. Т.к. таймер 2 у нас 8-битный, регистр сравнения OCR2A также 8-битный. То есть наш элемент массива мелодии будет уже, как минимум, 2 байта, потому что нужно же ещё длительность сохранять. На самом деле 2 байта — это предел для подобного рода поделок. Хорошего звучания мы всё равно, мягко говоря, не получим, а тратить больше байт неразумно. Итак, мы остановились на 2х байтах.

При подсчете частоты, на самом деле, вылезает ещё одна большая проблема.
Если посмотреть частоты нот, то мы увидим, что они делятся на октавы.

Для большинства несложных мелодий, достаточно 3х октав, я же решил извернуться и реализовать 6ть: большую, малую и следующие 4е.

Теперь отвлечемся от музыки и окунемся обратно в мир программирования микроконтроллеров.
Любой таймер в AVR(и подавляющим большинстве других МК) привязан к частоте самого МК. Частота кварца в моей схеме 16Mhz. Эта же частота определена «дефайном» F_CPU равным в моём случае 16000000. В регистре TCCR2B мы можем выбрать делители частоты, чтобы наш таймер 2 «тикал» не с бешеной скоростью 16000000 раз в секунду, а чуть медленнее. Делитель частоты выбирается битами CS2x, как говорилось выше.


примечание: очевидно в ДШ опечатка вместо «CA2x» должно быть CS2x

Возникает вопрос — как настроить делитель?

Для этого необходимо понять как вычислять значения для регистра OCR2A. А вычислять его достаточно просто:
OCR2A = F_CPU / (делитель частоты кварца * 2) / частота ноты
Например, берем ноту ДО первой октавы и делитель 256(CS22 = 1, CS21 = 1, CS20 = 0):
OCR2A = 16000000 / (256 * 2) / 261 = 119

Сразу поясню — откуда взялось умножение на 2. Дело в том, что мы выбрали режим «Toggle» регистрами COM2Ax, а это значит, что смена уровней на ноге с низкого на высокий(или наоборот) и обратно будет происходить за 2 прохода таймера: сначала таймер дойдет до значения OCR2A и поменяет ногу микроконтроллера, допустим, с 1 на 0, сбросится и лишь на втором круге поменяет 0 обратно на 1. Поэтому на каждую полную волну уходит 2 круга таймера, соответственно делитель нужно умножить на 2, иначе мы получим лишь половину частоты нашей ноты.

Отсюда и появляется вышеупомянутая беда…

Если мы возьмем ноту ДО большой октавы и оставим делитель 256:
OCR2A = 16000000 / (256 * 2) / 65 = 480!!!
480 — это число явно больше чем 255 и физически не влезет в 8-битный регистр OCR2A.

Что же делать? Очевидно менять делитель, но если мы поставим делитель 1024, то с большой октавой будет всё хорошо. Проблемы начнутся с верхними октавами:
ЛЯ 4й октавы — OCR2A = 16000000 / (1024 * 2) / 3520 = 4
ЛЯ диез 4й октавы — OCR2A = 16000000 / (1024 * 2) / 3729 = 4
Значения OCR2A перестали отличаться, а значит и звук также перестанет отличаться.

Выход только один: для частоты нот нужно хранить не только значения регистра OCR2A, но и биты делителя частоты кварца. Т.к. для разных октав будет разное значение делителя частоты кварца, которое мы должны будем установить в регистре TCCR2B!

Теперь всё встаёт на свои места — и я наконец то объяснил, почему мы сразу не могли заполнить значение делителя в функции timer2_buzzer_init().

К сожалению, делитель частоты — это ещё 3 бита. И их придется занять во втором байте элемента массива мелодии.

Да здравствуют макросы
#define DIV_MASK (_BV(CS20) | _BV(CS21) |  _BV(CS22))

#define DIV_1024 (_BV(CS20) | _BV(CS21) |  _BV(CS22))
#define DIV_256 (_BV(CS21) |  _BV(CS22))
#define DIV_128 (_BV(CS20) |  _BV(CS22))
#define DIV_64 _BV(CS22)
#define DIV_32 (_BV(CS20) | _BV(CS21))

#define NOTE_1024( x ) ((F_CPU / (1024 * 2) / x) | (DIV_1024 << 8))
#define NOTE_256( x ) ((F_CPU / (256 * 2) / x) | (DIV_256 << 8))
#define NOTE_128( x ) ((F_CPU / (128 * 2) / x) | (DIV_128 << 8))
#define NOTE_64( x ) ((F_CPU / (64 * 2) / x) | (DIV_64 << 8))
#define NOTE_32( x ) ((F_CPU / (32 * 2) / x) | (DIV_32 << 8))

//большая октава
#define DOB NOTE_1024( 65 )
#define DO_B NOTE_1024( 69 )
#define REB NOTE_1024 ( 73 )
#define RE_B NOTE_1024 ( 78 )
#define MIB NOTE_1024 ( 82 )
#define FAB NOTE_1024 ( 87 )
#define FA_B NOTE_1024 ( 93 )
#define SOLB NOTE_1024 ( 98 )
#define SOL_B NOTE_1024 ( 104 )
#define LAB NOTE_1024 ( 110 )
#define LA_B NOTE_1024 ( 116 )
#define SIB NOTE_1024 ( 123 )

//малая октава
#define DOS NOTE_256( 131 )
#define DO_S NOTE_256( 138 )
#define RES NOTE_256 ( 146 )
#define RE_S NOTE_256 ( 155 )
#define MIS NOTE_256 ( 164 )
#define FAS NOTE_256 ( 174 )
#define FA_S NOTE_256 ( 185 )
#define SOLS NOTE_256 ( 196 )
#define SOL_S NOTE_256 ( 207 )
#define LAS NOTE_256 ( 219 )
#define LA_S NOTE_256 ( 233 )
#define SIS NOTE_256 ( 246 )

//первая октава
#define DO1 NOTE_256( 261 )
#define DO_1 NOTE_256( 277 )
#define RE1 NOTE_256 ( 293 )
#define RE_1 NOTE_256 ( 310 )
#define MI1 NOTE_256 ( 329 )
#define FA1 NOTE_256 ( 348 )
#define FA_1 NOTE_256 ( 369 )
#define SOL1 NOTE_256 ( 391 )
#define SOL_1 NOTE_256 ( 414 )
#define LA1 NOTE_256 ( 439 )
#define LA_1 NOTE_256 ( 465 )
#define SI1 NOTE_256 ( 493 )

//вторая октава
#define DO2 NOTE_128( 522 )
#define DO_2 NOTE_128( 553 )
#define RE2 NOTE_128 ( 586 )
#define RE_2 NOTE_128 ( 621 )
#define MI2 NOTE_128 ( 658 )
#define FA2 NOTE_128 ( 697 )
#define FA_2 NOTE_128 ( 738 )
#define SOL2 NOTE_128 ( 782 )
#define SOL_2 NOTE_128 ( 829 )
#define LA2 NOTE_128 ( 878 )
#define LA_2 NOTE_128 ( 930 )
#define SI2 NOTE_128 ( 985 )

//третья октава
#define DO3 NOTE_64( 1047 )
#define DO_3 NOTE_64( 1109 )
#define RE3 NOTE_64 ( 1175 )
#define RE_3 NOTE_64 ( 1245 )
#define MI3 NOTE_64 ( 1319 )
#define FA3 NOTE_64 ( 1397 )
#define FA_3 NOTE_64 ( 1480 )
#define SOL3 NOTE_64 ( 1568 )
#define SOL_3 NOTE_64 ( 1661 )
#define LA3 NOTE_64 ( 1760 )
#define LA_3 NOTE_64 ( 1865 )
#define SI3 NOTE_64 ( 1976 )

//четвертая октава
#define DO4 NOTE_32( 2093 )
#define DO_4 NOTE_32( 2217 )
#define RE4 NOTE_32 ( 2349 )
#define RE_4 NOTE_32 ( 2489 )
#define MI4 NOTE_32 ( 2637 )
#define FA4 NOTE_32 ( 2794 )
#define FA_4 NOTE_32 ( 2960 )
#define SOL4 NOTE_32 ( 3136 )
#define SOL_4 NOTE_32 ( 3322 )
#define LA4 NOTE_32 ( 3520 )
#define LA_4 NOTE_32 ( 3729 )
#define SI4 NOTE_32 ( 3951 )



А на длительность ноты у нас осталось всего 5 бит, так давайте же посчитаем длительность.

Длительность


Для начала необходимо перевести значение темпа во временные единицы(например в миллисекунды) — я сделал это так:
Длительность музыкального такта в мс = (60000 мс * 4 четверти) / значение темпа.

Соответственно, если мы говорим о долях такта, то данное значение необходимо делить и по началу я думал, что обычного сдвига влево для делителей будет достаточно. Т.е. код был таков:

uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note)
{
   return (precalced_tempo / _BV((note >> 11) & 0b00111));
} 


Т.е. я использовал 3 бита(из оставшихся 5ти) и получил части музыкального такта из степеней 2ки аж до 1/128. Но когда я отдал товарищу с просьбой написать мне какой нибудь рингтон под мою железяку, возникли вопросы почему нет 1/3 или 1/6й и я начал думать…

В конце концов, я сделал хитрую систему, чтобы получить такие длительности. Один бит из оставшихся 2х — я потратил на признак умножения на 3 для делителя такта, получившегося после сдвига. А последний бит — для индикации того, нужно ли вычитать 1. Это сложно описать, проще посмотреть код:
uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note)
{
   note >>= 11;
   uint8_t divider = _BV(note & 0b00111);
   note >>= 3;
   divider *= ((note & 0b01) ? 3 : 1);
   divider -= (note >> 1);
   return (precalced_tempo / divider);
}

Затем я «задефайнил» все возможные(кроме тех, которые меньше 1/128) длительности нот.
Вот они
#define DEL_MINUS_1 0b10000
#define DEL_MUL_3 0b01000

#define DEL_1 0
#define DEL_1N2 1
#define DEL_1N3 (2 | DEL_MINUS_1)
#define DEL_1N4 2
#define DEL_1N5 (1 | DEL_MINUS_1 | DEL_MUL_3)
#define DEL_1N6 (1 | DEL_MUL_3)
#define DEL_1N7 (3 | DEL_MINUS_1)
#define DEL_1N8 3

#define DEL_1N11 (2 | DEL_MUL_3 | DEL_MINUS_1)
#define DEL_1N12 (2 | DEL_MUL_3)

#define DEL_1N15 (4 | DEL_MINUS_1)
#define DEL_1N16 4

#define DEL_1N23 (3 | DEL_MUL_3 | DEL_MINUS_1)
#define DEL_1N24 (3 | DEL_MUL_3)

#define DEL_1N31 (5 | DEL_MINUS_1)
#define DEL_1N32 5

#define DEL_1N47 (4 | DEL_MUL_3 | DEL_MINUS_1)
#define DEL_1N48 (4 | DEL_MUL_3)

#define DEL_1N63 (6 | DEL_MINUS_1)
#define DEL_1N64 6

#define DEL_1N95 (5 | DEL_MUL_3 | DEL_MINUS_1)
#define DEL_1N96 (5 | DEL_MUL_3)

#define DEL_1N127 (7 | DEL_MINUS_1)
#define DEL_1N128 7



Собираем всё вместе


Итого, мы имеем следующий формат элемента массива нашего рингтона.

  • 1bit: delay divider — 1
  • 1bit: delay divider * 3
  • 3bit: delay divider shift
  • 3bit: cpu clock divider
  • 8bit: OCR2A value

Всего 16 бит.

Уважаемый читатель при желании может пофантазировать над форматом сам, может быть родится что-то более ёмкое, чем у меня.

Мы забыли ещё добавить пустую ноту, т.е. тишину. И вот наконец-то я объяснил почему мы в самом начале в функции timer2_buzzer_init() специально установили ногу PB3 на вход а не на выход. Меняя регистр DDRB, мы и будем включать и выключать проигрывание «тишины» или композиции в целом. Т.к. ноты со значением 0 у нас быть не может — она и будет являться «пустой» нотой.

Определим недостающие макросы и функцию включения генерации звука:
#define EMPTY_NOTE 0

#define NOTE(delay, note) (uint16_t)((delay << 11) | note)
........
........
........
void play_music_note(uint16_t note)
{
   if (note)
   {
      TCCR2B = (note >> 8) & DIV_MASK;
      OCR2A = note & 0xff;
      sbi(DDRB, BUZ_PIN);
   }
   else
      cbi(DDRB, BUZ_PIN);
}


Теперь я Вам покажу как выглядит рингтон, написанный по такому принципу:

const uint16_t king[] PROGMEM =
{
   NOTE(DEL_1N4, MI3),
   NOTE(DEL_1N4, FA_3),
   NOTE(DEL_1N4, SOL3),
   NOTE(DEL_1N4, LA3),

   NOTE(DEL_1N4, SI3),
   NOTE(DEL_1N4, SOL3),
   NOTE(DEL_1N2, SI3),

   NOTE(DEL_1N4, LA_3),
   NOTE(DEL_1N4, FA_3),
   NOTE(DEL_1N4, LA_3),
   NOTE(DEL_1N4, EMPTY_NOTE),

   NOTE(DEL_1N4, LA3),
   NOTE(DEL_1N4, FA3),
   NOTE(DEL_1N2, LA3),

   NOTE(DEL_1N4, MI3),
   NOTE(DEL_1N4, FA_3),
   NOTE(DEL_1N4, SOL3),
   NOTE(DEL_1N4, LA3),

   NOTE(DEL_1N4, SI3),
   NOTE(DEL_1N4, SOL3),
   NOTE(DEL_1N4, SI3),
   NOTE(DEL_1N4, MI4),

   NOTE(DEL_1N4, RE4),
   NOTE(DEL_1N4, SI3),
   NOTE(DEL_1N4, SOL3),
   NOTE(DEL_1N4, SI3),

   NOTE(DEL_1N2, RE4),
   NOTE(DEL_1N2, EMPTY_NOTE),
};


Проигрывание рингтона


У нас осталась одна задача — проигрывание мелодии. Для этого нам нужно «бежать» по массиву рингтона, выдерживая соответствующие паузы и переключая частоты нот. Очевидно, нам нужен ещё один таймер, который, к слову, можно использовать и для других общих задач, как обычно делаю я. Причем переключаться между элементами массива можно либо в прерывании этого таймера, либо в основном цикле, а таймер использовать для вычисления времени. В этом примере я использовал 2й вариант.

Как известно тело любой программы для МК включает в себя бесконечный цикл:
int main(void)
{
    for(;;)
    {
      //здесь будет проигрыватель
    }
    return 0;
}

В нем мы и будем «бежать» по нашему массиву. Но нам нужна функция, подобная GetTickCount из WinApi, возвращающая количество миллисекунд в операционных системах семейства Windows. Но естественно в мире МК нет никаких таких функций «из коробки», поэтому мы должны написать её сами.

Таймер 1


Для подсчета временных промежутков(я намеренно не пишу миллисекунд, позднее Вы поймете почему) я использовал таймер 1 совместно с, уже известным нам, CTC режимом. Таймер 1 является 16-битным таймером, а это значит, что значение модуля сравнения для него указывается уже 2мя 8-битными регистрами OCR1AH и OCR1AL — для старшего и младшего байта соответственно. Я не хочу подробно описывать работу с таймером 1, так как это не относится к основной теме данной памятки. Поэтому расскажу всего лишь в 2х словах.

Нам, фактически нужны 3 функции:
  • Инициализация таймера
  • Обработчик прерывания таймера
  • функция, возвращающая количество временных промежутков.

Код С файла
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "timer1_ticks.h"

volatile unsigned long timer1_ticks;

//обработчик прерывания
ISR (TIMER1_COMPA_vect)
{
    timer1_ticks++;
}

void timer1_ticks_init()
{
    // настройка таймера
    // CTC режим, делим частоту кварца на 8
    TCCR1B |= (1 << WGM12) | (1 << CS11);
    
    //устанавливаем значение для модуля сравнения
    OCR1AH = (uint8_t)(CTC_MATCH_OVERFLOW >> 8);
    OCR1AL = (uint8_t) CTC_MATCH_OVERFLOW;

    // Разрешить прерывание таймера
    TIMSK1 |= (1 << OCIE1A);
}

unsigned long ticks()
{
    unsigned long ticks_return;

    //должно быть атомарно, чтобы значение ticks_return было корректным 
    //во время внезапного возникновения прерывания 
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        ticks_return = timer1_ticks;
    }

    return ticks_return;
}



Прежде чем я покажу заголовочный файл с определенной константой CTC_MATCH_OVERFLOW, нам нужно немного вернуться во времени к разделу «Длительность» и определить самый главный для мелодии макрос, который вычисляет темп мелодии. Я долго ждал для того, чтобы его определить, так как он непосредственно связан с проигрывателем, а значит с таймером 1.
В первом приближении он выглядел так(см. вычисления в разделе «Длительность»):
#define TEMPO( x ) (60000 * 4 / x)

Значение которое мы получаем на выходе мы должны в последствии подставить первым аргументом в функцию calc_note_delay. Теперь внимательно посмотрим на функцию calc_note_delay, а именно на строчку:
return (precalced_tempo / divider);

Мы видим, что значение, полученное в результате вычисления макроса TEMPO, делится на некий делитель. Вспомним, что максимальный делитель который у нас определен — это DEL_1N128, т.е. делитель будет 128.

Теперь возьмем распространенное значение темпа равное 240 и проведем нехитрые вычисления:
60000 * 4 / 240 = 1000
О ужас! У нас получилось всего 1000, в виду того, что это значение ещё будет делиться на 128, мы рискуем скатиться в 0, при высоких значениях темпа. Это вторая проблема длительности.

Как же её решить? Очевидно, чтобы расширить диапазон значений темпа, нам каким то образом надо увеличить число, получающиеся в результате вычисления макроса TEMPO. Это можно сделать только одним способом — уйти от миллисекунд и считать время в неких временных промежутках. Теперь Вы поняли, почему я всё это время избегал упоминания «миллисекунд» в рассказе. Давайте определим ещё один макрос:
 #define MS_DIVIDER 4

Пусть он будет нашим делителем миллисекунды — разделим миллисекунду, допустим, на 4(250 мкс).
Тогда необходимо поменять макрос TEMPO:
#define TEMPO( x ) (60000 * MS_DIVIDER * 4 / x)


Теперь я с чистой совестью приведу заголовочный файл для работы с таймером 1:
#ifndef TIMER1_TICKS_H_INCLUDED
#define TIMER1_TICKS_H_INCLUDED

#define MS_DIVIDER 4
#define CTC_MATCH_OVERFLOW ((F_CPU / 1000) / (8 * MS_DIVIDER))

void timer1_ticks_init();
unsigned long ticks();

#endif // TIMER1_TICKS_H_INCLUDED

Теперь Мы можем, меняя MS_DIVIDER, подгонять диапазон под наши задачи — у меня в коде стоит 4 — для моих задач этого хватило. Внимание: если у Вас будут ещё какие либо задачи «завязанные» на таймер 1, не забывайте контрольные значения времени для них умножать/делить на MS_DIVIDER.

Проигрыватель


Теперь напишем наш проигрыватель. Я думаю из кода и комментариев будет всё понятно.

int main(void)
{
    timer1_ticks_init();
    // разрешаем прерывания
    sei();
    timer2_buzzer_init();

    //в миллисекундах деленных на MS_DIVIDER
    long time_since = ticks();

    //текущая длительность ноты в миллисекундах деленных на MS_DIVIDER
    uint16_t note_delay = 0;
    //текущая позиция в массиве мелодии
    uint16_t note_pos = 0;
    //длина мелодии
    uint16_t length = sizeof(king) / sizeof(king[0]);
	//устанавливаем значение темпа под рингтон
    uint16_t tempo = TEMPO(240);

    for(;;)
    {
            unsigned long time_current = ticks();
            if (time_current - time_since > note_delay)
            {
                //читаем элемент массива
                uint16_t note = pgm_read_word(&king[note_pos]);
                //установить частоту ноты
                play_music_note(note);
                //вычислить длительность проигрывания ноты
                note_delay = calc_note_delay(tempo, note);
                //зациклить мелодию
                if (++note_pos >= length)
                    note_pos = 0;
                time_since = time_current;
            }
    }
    return 0;
}


Заключение


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

Ну и традиционно видео и исходный код(разрабатывал я это в среде Code Blocks, так что не пугайтесь непонятных файлов):



Исходники
Поделиться с друзьями
-->

Комментарии (38)


  1. IronHead
    24.03.2017 16:06

    У Белова читал про этот (или очень похожий) принцип генерации
    image
    http://radiohata.ru/other/400-a-v-belov-sozdaem-ustroystva-na-mikrokontrollerah.html


  1. Alexeyslav
    24.03.2017 16:31
    -1

    Я бы последовательно R1 поставил бы конденсатор(и соответственно резистор ещё в цепь эмиттера) микрофарад на 5...10, мешать звуку он не будет зато если остановишь таймер и он зависнет в состоянии "1" схема не будет зря греть катушку излучателя.


    1. IronHead
      24.03.2017 17:01

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

      Я создал уже некоторое количество разных хоббийных электронных устройств, и у меня есть странная особенность: если на плате присутствует звуковой пьезоэлектрический излучатель(buzzer)


      1. vbif
        24.03.2017 18:24
        -2

        Только на первой фотке — не пьезоэлектрические излучатели.


        1. IronHead
          24.03.2017 18:28

          потому что умею читать текст

          А не только смотреть картинки


          1. Serge78rus
            24.03.2017 20:09

            Приведенная схема явно не для пьезоизлучателя. Если это пьезоизлучатель, то непонятно, как будет протекать коллекторный ток Q2 и зачем нужен D1. Пьезоизлучатель можно было бы напрямую подключить к ноге контроллера. Да и диапазон октав у автора явно не для пьезо.


          1. Error1024
            24.03.2017 20:23

            Однако то что в видео не похоже на пьезоэлектрический излучатель…


    1. YMA
      24.03.2017 17:19
      +1

      Так-то у пьезоэлектрического излучателя нет катушки, электрически он скорее конденсатор. Соответственно, смысла в конденсаторе тут немного…


      1. Alexeyslav
        24.03.2017 21:49

        Если на схеме пьезоэлектрический излучатель то работать он не будет от слова совсем. Там схема нарисована для электродинамического да и напряжения в 5 вольт маловато для раскачки пьезоизлучателя, там бы вольт 20...50 как минимум. Да и по спектру в видео и по форме видно что это динамический. Пьезоизлучателей такого малого диаметра я не видел, УЗ и те в 2 раза больше диффузор имеют. Это скорей ошибка именно в тексте, т.к. всё остальное указывает на классический электродинамический. Впрочем, пьезо и так можно включить ТОЛЬКО если через трансформатор, у меня в велозвонке именно так и сделано. Или дроссель параллельно излучателю.


        1. kail
          24.03.2017 22:59

          Нет. На всё нет.

          Пьезоизлучатель противно-громко пищит при питании от 5В даже через резистор в пару десятков килоом, если частота резонансная. Как готовый с резонаторной камерой, так и обычная пластинка, прижатая к пустой коробке.

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

          Пьезоизлучатель такого размера можно найти в любых китайских часах-будильниках. Они ещё и работают всего от 1,5 В. Ну или в радиомагазине. Например, вот муратовский 12,6мм, 9 рублей в чипдипе в моём городе, там же ссылка на целый каталог. Вот ещё на выбор, в том числе электромагнитные.

          Включение излучателя на схеме — почти такое, как вы увидите по запросу «подключение пьезоизлучателя», разве что вместо резистора диод. Но в том же муратовском каталоге предлагается опционально ставить диод.
          А трансформатор ставят там, где требуется высокая громкость — в сиренах, звонках.


          1. Alexeyslav
            25.03.2017 02:12

            В часах дроссель стоит, который в приведённой схеме(только диод надо выкинуть) даёт выбросы до 15В.
            Электродинамические так и звучат, диффузор мал и резонансная частота в районе 2кГц-2.5кГц.
            Хм, и правда там пьезы такие мелкие. Но в даташите приводится другая схема включения, там параллельно резистор в 1кОм используют, чтобы пьезоэлемент работал. Иначе транзистор его накачает(как конденсатор) а разряжаться некуда. Или выход надо двухтактный делать. В любом случае, напряжение надо снимать в паузе.
            И на видео всё-таки электродинамический — отличительная черта монолитный корпус, а пьезы в сборных.


            1. wormball
              25.03.2017 10:48

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


              1. Serge78rus
                25.03.2017 14:33

                Пьезоизлучатель не проводит постоянный ток, таким образом постоянной составляющей коллекторного тока Q2 просто некуда течь. Т. е. ключ открываться и закрываться будет, но коммутировать ему будет попросту нечего.


                1. Alexeyslav
                  26.03.2017 01:46

                  Пьезоизлучатель обратим. Транзистор его загнал в одну сторону, а пластинка разгибаясь даёт отдачу. И вообще механические колебания гаснут после снятия напряжения и пьезоэлектрик снова готов к деформации.
                  Просто с времён 3П-1 впервые вижу столь низковольтные материалы. 3Vpp и 70дБ на расстоянии 10 сантиметров! На резонансной частоте, правда.


                  1. Serge78rus
                    26.03.2017 10:30

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

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

                    и непонятно к чему первая часть Вашего комментария:
                    Пьезоизлучатель обратим. Транзистор его загнал в одну сторону, а пластинка разгибаясь даёт отдачу. И вообще механические колебания гаснут после снятия напряжения и пьезоэлектрик снова готов к деформации.

                    С электрической точки зрения пьезоизлучатель — это пластинка из пьезокерамики с двумя проводящими обкладками. Надеюсь, здесь никто не будет утверждать, что через него возможно протекание постоянной составляющей тока? А чтобы схема работала, она должна через что-то протекать. Как вариант (отнюдь не лучший) — заменить диод на резистор. Другой вариант, как Вы писали — поставить дроссель, но тогда во первых управлять им лучше не меандром, а импульсами с бОльшей скважностью, а во вторых, как опять же Вы писали, принять аппаратные меры по ограничению длительности нахождения ключа в открытом состоянии, иначе он будет спален еще на этапе наладки.

                    Во второй части комментария Вы, похоже, за давностью лет перепутали
                    ЗП-1 и ЗП-3



                    1. Alexeyslav
                      26.03.2017 18:32

                      Постоянная составляющая протекать всё же не будет, но это и не простой конденсатор а такой что даёт отдачу. Чем сильнее пнуть тем сильнее отдача. Вот от этой отдачи и стоит диод, чтобы не попалить транзистор. Да и резистор с той же целью. Раньше излучатели не такие эффективные были, и с отдачей не заморачивались и больше походили на конденсатор. Но сейчас повидимому дела обстоят лучше.


                      1. Serge78rus
                        26.03.2017 19:38

                        Нет, резистор стоит (точнее — должен стоять и не обязательно резистор) не для защиты, а именно потому, что без него работать не будет. Вот смотрите: ключ открылся, подтянул нижний вывод излучателя к земле. Ключ закрылся, но потенциал нижнего вывода, если не считать утечек, так и остался на уровне земли, тк его «вверх» (к потенциалу шины питания) никто не тянет. Поэтому и нужно что-то, что подтянет его «вверх». Это может быть или резистор, или дроссель, или еще один ключ, работающий в противофазе.

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


            1. kail
              25.03.2017 11:14

              В простых часах есть 4 электронных элемента: микросхема-клякса, кварц, соленоид, пьезоизлучатель если реализован будильник. Всё.
              Наверное, можно как-то снимать напряжение с соленоида, но импульс был бы слишком короткий.

              В даташите на муратовские пьезоизлучатели есть несколько схем. Вот одна из них.


              И я так делал буквально на этой неделе, даже без диода, именно с этим излучателем, при питании от 5В через резистор 39кОм. И оно пищало и действовало на нервы.

              Более того, есть специальные пьезы с 3 контактами, и пищалка в упрощённом случае состоит всего из 3 элементов: транзистора, пьезы и батарейки. Правильней добавить 2-3 резистора. И это я тоже как-то проверял, с питанием то ли от литиевого аккумулятора, то ли от usb.

              В том же даташите мы можем увидеть график зависимости давления звука от частоты при питании меандром 3 Вольта пик-пик и синусом с размахом в 1 (!!!) Вольт.



              Кидать горох об стену закончил.


  1. stalinets
    24.03.2017 17:20

    Интересно! А возможно ли выводить живой звук, например, с mp3-файла, посредством ШИМ? Раньше были такие драйверы+программы для DOS, Windows и Linux, позволяющие превращать PC-speaker в полноценное звуковое устройство, играющее wave-файлы. После тонкой настройки параметров под конкретный динамик (с учётом его размера, импеданса, АЧХ) удавалось получить довольно приличный звук, пусть и с хрипотцой. Понятно, что пьезопищалка в роли PC-спикера будет играть гораздо хуже, чем полноценный динамик, пусть и маленький.

    Для DOS умела программа «DSS Player», для Windows — BaWaMI Player, кроме того, для Win2000 вроде существовал нативный драйвер для этого.

    Я вот всё мечтаю, что кто-нибудь напишет драйвер для современной винды, чтобы в системе появлялось аудиоустройство «PC speaker», на которое методом ШИМ можно было бы выводить любые звуки. :)))


    1. YMA
      24.03.2017 18:06

      А смысл? (картинка про буханку хлеба и троллейбус) Когда сейчас на практически любой ПК-шной платформе есть кодек, умеющий почти без затрат процессорного времени играть PCM вплоть до 192/24.

      Вы еще про covox вспомните, он хоть качественнее играл, чем PC squeaker…


      1. stalinets
        24.03.2017 20:35

        Смысл — для ностальгирующих любителей старых игр, например. Или для музыкантов, которые хотят получить такой звук для включения в свои треки. Или просто чтоб удивить друзей. Ковокс тоже хорошая штука, но его надо делать, а спикер есть почти в любом ПК.


        1. stalinets
          24.03.2017 20:40

          Вот, кстати, как это было в играх:
          PC speaker (динамик ПК) (Old-Hard — выпуск 37)


    1. Serge78rus
      24.03.2017 20:15

      А много mp3 Вы втиснете в память контроллера?


      1. iig
        24.03.2017 20:44
        +1

        В дополнительную памятьтна SD карте — сколько угодно. Только у контроллера не хватит быстро действия играть mp3. Wav без сжатия — есть проект такого плеера.


        1. Sun-ami
          25.03.2017 02:08

          Это смотря у какого контроллера. У AVR — не хватит, а STM32F103 вполне играет, на easyelectronics есть пример проекта.


    1. Alexeyslav
      24.03.2017 23:34

      Вообще-то "при помощи ШИМ" это называется усилитель D-класса, и как правило они стояли чуть ли не в каждом CD-роме как усилитель для наушников.


    1. boojum
      25.03.2017 08:49
      -1

      возможно ли выводить живой звук, например, с mp3-файла, посредством ШИМ?

      Можно. Цена вопроса 50 центов с учетом доставки


  1. iig
    24.03.2017 17:45
    +1

    http://radiokot.ru/circuit/digital/game/51/ — тут тема музыки на AVR раскрыта полностью.


  1. kx13
    24.03.2017 17:46

    Для attiny уже реализован плеер с помощью Dss


    http://elm-chan.org/works/mxb/report.html


  1. syrompe
    24.03.2017 18:36

    А с громкость как в подобной схеме регулируется?


    1. AndyKorg
      25.03.2017 16:12

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


  1. ariz0na
    24.03.2017 19:09

    тут описание как формировать полифонический звук
    «Пианино» на ОСРВ

    особенно нравится сопровождающее проект видео ))


  1. GloooM
    24.03.2017 20:57

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

    У программиста разрабатывавшего прошивку брелка автосигнализации Scher-Khan Logicar 2 похоже такой же недуг, играет дурацкую мелодию при каждом успешном резервировании автозапуска ))


  1. REPISOT
    24.03.2017 21:46

    Вальс имеет размер 3 четверти


    1. REPISOT
      25.03.2017 11:30

      И вообще их довольно много, и они сильно разные.
      image


      1. barkalov
        25.03.2017 12:47

        Ой, уж ладно, сильно разные.

        99% музыки — это четыре четверти (либо две четверти, что по сути одно и тоже).
        Ещё 1% — это три четверти (или шесть восьмых, что тоже одно и тоже).
        И на уровне статистической погрешности остались всякие пять четвертей (3+2, что, опять же, одно и тоже).
        Семь восьмых. Девять восьмых.
        И всё.


        1. REPISOT
          27.03.2017 05:58

          Спасибо, поржал.
          100% музыки это 7 нот. Еще скажите, что все мелодии

          по сути одно и тоже


  1. NickB53
    24.03.2017 23:23

    Помнится еще в «Вектор-06С», а это всего 600 оп/с подобным образом был реализован трехголосный синтезатор на микосхеме 580ВИ53. И расширение для Бейсика — команда PLay.А вся обработка — в прерывании 50Гц