Суть


Я создал уже некоторое количество разных хоббийных электронных устройств, и у меня есть странная особенность: если на плате присутствует звуковой пьезоэлектрический излучатель(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, так что не пугайтесь непонятных файлов):



Исходники