Генерация звука с помощью AVR. Озвучь свой девайс

15/03/2011 - 18:36

Введение

Написал программный модуль, позволяющий добавить функцию воспроизведения мелодий или последовательностей звуков практически в любой проект на микроконтроллере AVR. 
 
Особенности модуля:
 
- простая интеграция с готовым проектом 
- задействован только 8-ми разрядный таймер т2, при этом остается возможность использовать его для опроса или формирования временных интервалов
- модуль настраивается практически на любую частоту тактового генератора
- высота нот задается в виде символических констант (С0, А2 и т.д) или в Герцах 
- длительности задаются в стандартном виде (четверти, восьмые и т.д.) или в миллисекундах
- имеется возможность задавать темп воспроизведения мелодии и количество ее повторений
- в процессе воспроизведения мелодия может быть поставлена на паузу


Подключение звукового модуля

1. Переписываем все файлы модуля (tone.h, sound.h, sound.c)  в папку проекта.
 
2. Подключаем файл sound.c к проекту. 
Для IAR `a – кликнуть правой кнопкой мышке в окне workspace и выбрать Add > Add Files…
Для WINAVR примерно то же самое, только sound.c нужно еще прописать в make файл: 
 
    # List C source files here. (C dependencies are automatically generated.)
    SRC = $(TARGET).c sound.c
 
3. Включаем заголовочный файл sound.h в соответствующий модуль. Например, в main.c
 
   #include "sound.h"
 
4. Задаем настройки модуля в файле sound.h
 
   //если закомментировать - длительность нот будет     
   //рассчитываться из BPM`а  заданного в мелодии 
   //если оставить, то из значения заданного ниже
   //#define SOUND_BPM       24  
 
   //тактовая частота мк                              
   #define SOUND_F_CPU     16U 
 
   //вывод микроконтроллера, на котором будет генерироваться звук 
   #define PORT_SOUND PORTB
   #define PINX_SOUND 0
 
   //количество заданных мелодий.  
   #define SOUND_AMOUNT_MELODY 4
 
5. Добавляем в sound.c свои мелодии и прописываем названия мелодий в массив melody[].

Добавление мелодий

   Мелодия представляет собой массив 16-ти разрядных чисел и имеет следующую структуру 
генерация звука на avr. структура мелодии
   BPM (количество четвертных нот в минуту) – это константа, используемая для расчета длительности нот и определяющая скорость воспроизведения мелодии. 
   BPM может принимать значения от 1 до 24, что соответствует 10 и 240 четвертным нотам в минуту соответственно. 
   Если длительность нот/звуков задается в миллисекундах, то BPM, прописанный в массиве, должен быть равен 1.
   Если в заголовочном файле sound.h константа SOUND_BPM закомментирована, то длительность нот рассчитывается в процессе выполнения программы по BPM `у  заданному в массиве. Если SOUND_BPM  не закомментирована – длительность нот рассчитывается еще на этапе компиляции, исходя из значения этой константы, при этом все мелодии будут воспроизводиться в одинаковом темпе. Это ограничивает функционал, но позволяет сэкономить несколько байт кода. 
 
   Количество повторений. Может принимать значения 1 ... 254 и LOOP (255). LOOP - означает, что мелодия будет повторяться бесконечно, пока не будет подана команда SOUND_STOP или SOUND_PAUSE. 
 
   Длительность ноты – время в течение, которого генерируется заданный тон звука или выдерживается пауза. Может задаваться в ms, с помощью макроса ms(x), или в виде стандартных длительностей нот – восьмых, шестнадцатых и т.д. Ниже приведен список поддерживаемых длительностей. Если возникнет нужда в каких-то экзотических длительностях, их всегда можно добавить в файле tone.h 
 
n1  - целая нота
n2  - половинная нота
n4  - четверть
n8  - восьмая
n3  - восьмая триоль
n16 - шестнадцатая
n6  - секстоль
n32 - тридцать вторая
 
   Высота ноты задается с помощью символических констант описанных в файле tone.h, например C2, A1 и т.д. Также высота нот может задаваться в Герцах с помощью макроса f(x).
В программе есть ограничения на минимальную и максимальную частоту звука!  
 
   Маркер конца мелодии. Значение последнего элемента массива обязательно должно быть нулевым. 

Использование звукового модуля

   В начале main`a нужно обязательно вызывать функцию SOUND_Init(). Эта функция настраивает вывод микроконтроллера на выход, конфигурирует таймер Т2  и  инициализирует переменные модуля. 
   Затем нужно установить флаг разрешения прерываний - __enable_interrupt(), ведь в модуле используется прерывания таймера Т2 по переполнению и совпадению.
    После этого можно запускать воспроизведение мелодий.
Например, так: 
 
   SOUND_SetSong(2); //установить указатель на 2-ю мелодию
   SOUND_Com(SOUND_PLAY);  //воспроизвести мелодию
 
Или так:
 
   //установить указатель на 2-ю мелодию
   //и запустить воспроизведение
   SOUND_PlaySong(2);
 
    Воспроизведение мелодии можно в любой момент остановить, подав команду SOUND_STOP.
   Также мелодию можно поставить на паузу с помощью команды  SOUND_PAUSE. Последующая подача команды SOUND_PLAY возобновляет воспроизведение мелодии с места, на котором произошла остановка. 
   В принципе данный функционал не особо нужен (это уж я просто навернул) и при работе с модулем достаточно функции  SOUND_PlaySong(unsigned char numSong);

Файлы

   Примеры использования звукового модуля вы можете скачать по ссылкам ниже. Схему рисовать не стал, потому что там все просто. Пьезоизлучатель подключен к выводу PB0, кнопка запуска мелодий подключена к выводу PD3. В проектах определено 4 мелодии. Нажатие на кнопку запускает каждый раз новую мелодию. Используется микроконтроллер atmega8535. Изначально хотел заморочиться на проект с четырьмя кнопками - PLAY, STOP, PAUSE и NEXT, но потом подумал, что это лишнее. 

 
Исходники звукового модуля для трех компиляторов

PS: Модуль не проходил расширенное тестирование и предоставляется “как есть“. Если есть какие-то рациональные предложения - давайте его доработаем. 

Comments   

# ciscobsci 2011-03-15 22:04
очень ждал эту статью!
# foxit 2011-03-16 04:16
Спасибо. Буду изучать.
# TERMIN 2011-03-16 04:21
пригодится, спасибо)
# Crazy Synergetic 2011-03-16 04:34
Эээх, вспомнился проект АнтиАОН... Жалко, что нельзя без доработок обеспечить воспроизведение нескольких нот одновременно (взять аккорд), а то можно было бы применить)
# Pashgan 2011-03-16 07:06
А что за проект и почему вспомнился?
# AleGro 2011-03-16 23:17
Можно и аккорд взять, если очень захотеть :-)
# nnn 2011-03-16 05:39
и сколько это жрет flash и ram ?
# Pashgan 2011-03-16 07:04
550-700 байт флэша без мелодий.
20 байт ОЗУ.
При желании можно сделать облегченную версию, урезав функционал. Например, ограничить количество нот и записывать их восьми разрядными цифрами. Но это уже потребует допиливание кода.
# Dmi3 2011-04-29 10:36
Лучше посчитайте сколько это жрёт процессорного времени!
# Гость 2011-03-18 09:39
Хочу поблагодарить автора за очень интересный и содержательный сайт. Люди, которые стремятся поделиться своими знаниями с другими, вызывают уважение.
# Pashgan 2011-03-19 20:25
Спасибо.
# shs 2011-03-25 11:57
Pashgan молодца.
Так держать!
Вот еще пара мелодий:
Code:
Griboedov[] = {
16, 2,
n8,b2, n8,c3, n8,b2, n8,g2, n8,e2, n8,b2, n8,g2, n8,b2, n8,b1, n8,b2, n8,g2, n8,b2,
n8,a2, n8,b2, n8,a2, n8,xf2, n8,xd2, n8,a2, n8,xf2, n8,a2, n8,b1, n8,a2, n8,xf2, n8,a2,
n8,g2, n8,a2, n8,g2, n8,e2, n8,b1, n8,g2, n8,xf2, n8,g2, n8,xf2, n8,c2, n8,a1, n8,xf2,
n8,e2, n8,xf2, n8,e2, n8,xc2, n8,g1, n8,e2, n8,xd2, n8,b1, n8,xa2, n8,b1, n8,b2, n8,b1,

n8,b2, n8,c3, n8,b2, n8,g2, n8,e2, n8,b2, n8,g2, n8,b2, n8,b1, n8,b2, n8,g2, n8,b2,
n8,a2, n8,b2, n8,a2, n8,xf2, n8,xd2, n8,a2, n8,xf2, n8,a2, n8,b1, n8,a2, n8,xf2, n8,a2,
n8,g2, n8,a2, n8,g2, n8,e2, n8,b1, n8,g2, n8,xf2, n8,g2, n8,xf2, n8,c2, n8,a1, n8,xf2,
n8,xd2, n8,e2, n8,xd2, n8,b1, n8,xf1, n8,xd2, n2,e2, n4,p,
0
};

unsigned int Augustin[] = {
16,2,
n4,g1, n8,g1, n8,a1, n8,g1, n8,f1, n4,e1, n4,c1, n4,c1, n4,d1, n4,g0, n4,g0, n4,e1, n4,c1, n4,c1,
n4,g1, n8,g1, n8,a1, n8,g1, n8,f1, n4,e1, n4,c1, n4,c1, n4,d1, n4,g0, n4,g0, n2,c1, n2,p,
n4,d1, n4,g0, n4,g0, n4,e1, n4,c1, n4,c1, n4,d1, n4,g0, n4,g0, n4,e1, n4,c1, n4,c1,
n4,g1, n8,g1, n8,a1, n8,g1, n8,f1, n4,e1, n4,c1, n4,c1, n4,d1, n4,g0, n4,g0, n2,c1, n2,p,
0
};
# Pashgan 2011-03-29 19:20
Здорово. Послушал. А в Грибоедове точно все верно? В конце обоих кусков не очень созвучные ноты. Не знаю, может это задумка такая. Если еще что-то наваяешь выкладывай на форуме http://chipenable.ru/index.php/forum-chipenable/6-kompilyator_IAR_AVR/1811-generatziya_zvuka_s_pomoshyu_AVR.html
# ciscobsci 2011-03-29 14:09
Pashgan, помоги переделать настройку для Timer1, Mega32, 2-ой занят. Сирена очень нужна. Перенастроил сам, но слышны щелчки в динамике. С тонами похоже не разберусь сам. В форуме задал вопрос.
# Pashgan 2011-03-29 19:22
Выложил проект на форуме. Вообще эту либу доработать нужно еще дорабатывать. Добавить директиву для переключения таймеров и директиву для выключения таймера, если звук не воспроизводится . Времени нет, да и не знаю, нужно ли это кому-то кроме меня.
# justnsn 2011-05-06 12:02
Отличная статья.
Если есть возможность пожалуйста напишите больше теории про генерацию звука с диаграммами и эл. схемой для примера. спс
# Pashgan 2011-09-17 21:13
Спасибо. По теории генерации звука я вообщем то не спец, но если разберусь, то обязательно накатаю материал.
# AlexNT 2011-10-03 20:27
Хороший проект. Как раз ломаю голову как сгенерировать звук "авто колокольчика, зуммера" для сигнализации включенного света.
# Gromozeka 2011-10-04 15:58
Нет слов, выражающих в полной мере мои благодарности автору. За умение, за труд и за то что жлобство в Вас абсолютно отсутствует. После нескольких лет ассемблера познавал С именно с помощью этого сайта. Первые несколько проектов удались во многом благодаря здешним публикациям.
Но вот генерить музыку один в один по этим исходникам к сожалению не удалось. Точнее принцип-то работает, но...
Переполнения, отвечающие за частоту ноты отсчитываются правильно, а вот досчет (когда остаток уже меньше 0хFF) уже не происходит. Поэтому многие соседние ноты звучат одинаково (на слух просто фальшь). Копал долго, но исправить так и не удалось. Переделал генерацию частоты под 16-битный Т1 и все стало Ок. И Грибоедов фальшивить тоже перестал.
Правда может это только у меня так вышло?
PS:Где то тут видел пост с понятием "Низкоуровневое мышление". Блин, ну точнее не скажешь! После ассемблера приходится просто постоянно таки его перебарывать.
# Pashgan 2011-10-11 10:22
Спасибо.
Все косяки вычистить не удалось. Делал модуль ради спортивного интереса.
На самом деле знание ассемблера очень помогает при программировани и на Си. Ясно представляешь, что происходит в контроллере. Это важно.
# Татьяна 2015-01-06 18:46
Нужна помощь!
Нужно сделать работу: воспроизвести (в системе PROTEUS) заданную мелодию "Сакура". Сможете помочь? За вознаграждение.
Спасибо.
# Gromozeka 2011-10-11 10:45
Может, конечно и не стоит время на эти косяки тратить. Время заниматься полифонией :D .
# ROM 2011-10-21 18:13
А для протеруса нет исходников? :sad:
# Igoryosha 2011-12-29 09:52
Помогите понять, как изменяется частота воспроизводимог о звука (тональность), если в настройках таймера стоит обычный режим работы(не СТС)?
# wukrlvy 2012-02-02 11:21
/* Я проанализировал "глазками" работу программы и у меня следующие замечания

1) Самое главное - в функции SOUND_tone оператор tone = toneNote; надо заменить на оператор tone += toneNote; Первый полупериод выходного сигнала генерируется правильно, например 2,5 периода работы таймера, пауза и все последующие импульсы будут генерироваться длительностью 3 периода.

2) Второе - разрешать прерывание по компаратору в модулях SOUND_Com и SOUND_PlaySong преждевременно и вредно, лучше разрешение переместить в модуль SOUND_Duration и поставить его вместо команды
# wukrlvy 2012-02-02 11:24
[TIFR |=(1
# wukrlvy 2012-02-02 11:34
команды [TIFR |=(1 сдвиг влево OCF2);] сброса флага прерывания
Тогда включение звука будет производиться корректно.
# wukrlvy 2012-02-02 11:36
3) В исходном состоянии значение компаратора равно нулю. Поскольку компаратор крутится, прерывания по переполнению OVF и компаратору COMP возникают одновременно. Но последнее
замаскировано. Программа обработки клавиши работает по OVF она формирует состояние STATE_PLAY.
По следующему OVF срабатывает SOUND_dur, которая считывает из массива первый звук.
Эта программа должна выполниться перед SOUND_tone. В этом и есть смысл перестановки команд разрешения прерывания. В момент, когда SOUND_dur установит разрешение прерывания,
начнет работать SOUND_tone (по этому же OVF).
SOUND_tone установит на выходе сигнал и начнет работать параллельно SOUND_dur. На
первых циклах OVF прерывания будут приходить одновременно. Поскольку COMP имеет старший приоритет, SOUND_tone будет обслуживаться первой. Но затем прерывания разбегутся во времени, поскольку компаратор не будет равен нулю.
# wukrlvy 2012-02-02 11:37
4) Окончание выдачи звука будет отрабатываться так, как и было запланировано ранее. Я бы еще и запрет прерывания в модуле SOUND_com убрал. Так, как он стоит, есть большая вероятность, что последний генерируемый импульс будет обрезанным. В результате будет слышен щелчок. Лучше в модуле SOUND_dur доработать проверку состояния state таким образом, чтобы прерывание выключалось не только по концу записи, но и после отработки каждого dur, если состояние не SOUND_PLAY.
# wukrlvy 2012-02-02 11:39
5) Итак, на подаляющем числе проходов в модуле SOUND_Tone делается одна проверка и одно 16-разрядное вычитание. По окончании генерации полупериода вычитание заменяется на отсылку в порт и установку признака. С обработкой входа в прерывание и выхода - это примерно 30 тактов, 2 мкс на частоте 16 МГц.
На подавляющем числе проходов м модуле SOUND_dur делается три проверки, инкремент счетчика и обрезание его старшей части. вместе со входом и выходом - не более 50 тактов - 3 мкс.
Каждые 16 мкс контроллер обслуживает два прерывания, на них из 16 мкс тратится 5. Все работает.
# wukrlvy 2012-02-02 11:40
6) Но я бы предварительный делитель таймера включил на 8. Тогда период будет отрабатываться с дискретом 0,5 мкс - для максимальной звуковой частоты 5 кГц (200мкс) - это точность 0,25%, что
вполне достаточно. А затраты по времени уменьшатся с 31 до 4%.
# wukrlvy 2012-02-02 11:45
Заключение кода в квадратные скобки мало чего дает, так символ двойного сдвига влево не смог передать правильно
# Sergei 2012-12-20 12:44
При внедрении данных файлов в проект, и последующей компиляции, появляются ошибки:
компилятор ругается, например, на TIMSK=0xC0: Error: '(' not expected. Т.е. идет пересечение имен тона нот С0 с шестнадцатеричн ой записью 0хС0. Как только я из главного файла убираю include sound.h, все норм..... Проект в CodeVision.
# Pashgan 2012-12-25 05:36
Выложи свой проект на форуме, я посмотрю. И укажи версию CodeVision, которую используешь.
# Andy 2013-08-28 12:29
Как делать паузы между нотами?

На баскоме есть куча мелодий, переведенных с сименса. Пробовал такой макрос, чуш какая то получается.
#define C1 fn(262)
#define Cis1 fn(277)
#define D1 fn(294)
#define Dis1 fn(311)
#define E1 fn(330)
#define F1 fn(349)
#define Fis1 fn(370)
#define G1 fn(392)
#define Gis1 fn(415)
#define A1 fn(440)
#define Ais1 fn(466)
#define H1 fn(494)

#define C2 fn(523)
#define Cis2 fn(554)
#define D2 fn(587)
#define Dis2 fn(622)
#define E2 fn(659)
#define F2 fn(698)
#define Fis2 fn(740)
#define G2 fn(784)
#define Gis2 fn(831)
#define A2 fn(880)
#define Ais2 fn(932)
#define H2 fn(988)

#define C3 fn(1047)
#define Cis3 fn(1109)
#define D3 fn(1175)
#define Dis3 fn(1245)
#define E3 fn(1319)
#define F3 fn(1397)
#define Fis3 fn(1480)
#define G3 fn(1568)
#define Gis3 fn(1661)
#define A3 fn(1720)
#define Ais3 fn(1865)
#define H3 fn(1976)

В чем задаются ноты в tone.h? На герцы не похоже, частота не соответствует нотам. И как перевести ноты в сименсовские? Пробовал в лоб - С1, С2, С3 - подставляя ваши циферки - тоже фигння получается. А так - отличная бибилиотека, спасибо.
# FreshMan 2013-11-23 14:54
а где брать массивы с новыми мелодиями ?
# Pashgan 2013-11-23 20:20
Самому писать.
# FreshMan 2013-11-24 07:08
может подскажешь как ?
# Pashgan 2013-11-24 23:20
Берешь ноты с одноголосной мелодией. Смотришь какую длительность и высоту имеет каждая нота мелодии и записываешь ее в массив. Если в этом не разбираешься, воспользуйся таблицей, где расписано как выглядят разные ноты. Например восьмая нота Ми второй октавы - n8,e2.
# Klim 2014-07-28 13:54
Доброе время суток. Не подскажите, побывал загрузить на attiny45, ничего не вышло.Дело в таймере?
# Klim 2014-07-28 13:56
В МК зашил, все молчит.
# mixan23 2014-08-17 20:13
Спасибо за такие классные библиотеки! Использую много ваших материалов в изучении. А библиотека для Atmel Studio будет? Взял от WinAVR, подключил. Ошибки вылетают, ругается на то, что массивы с мелодиями лежат в памяти только для чтения.
# dima 2015-10-08 12:42
Сейчас попробовал твой код, как ни крути но он работает. Получилось запустить за 10 мин. Суперский сайт. Много полезного и интересного кода.

У вас недостаточно прав для комментирования.