Генерация звука с помощью AVR. Озвучь свой девайс
15/03/2011 - 18:36
Pavel Bobkov
Введение
Написал программный модуль, позволяющий добавить функцию воспроизведения мелодий или последовательностей звуков практически в любой проект на микроконтроллере 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-ти разрядных чисел и имеет следующую структуру
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_PAUSE. Последующая подача команды SOUND_PLAY возобновляет воспроизведение мелодии с места, на котором произошла остановка.
В принципе данный функционал не особо нужен (это уж я просто навернул) и при работе с модулем достаточно функции SOUND_PlaySong(unsigned char numSong);
Файлы
Примеры использования звукового модуля вы можете скачать по ссылкам ниже. Схему рисовать не стал, потому что там все просто. Пьезоизлучатель подключен к выводу PB0, кнопка запуска мелодий подключена к выводу PD3. В проектах определено 4 мелодии. Нажатие на кнопку запускает каждый раз новую мелодию. Используется микроконтроллер atmega8535. Изначально хотел заморочиться на проект с четырьмя кнопками - PLAY, STOP, PAUSE и NEXT, но потом подумал, что это лишнее.
Исходники звукового модуля для трех компиляторов
PS: Модуль не проходил расширенное тестирование и предоставляется “как есть“. Если есть какие-то рациональные предложения - давайте его доработаем.
PS: Модуль не проходил расширенное тестирование и предоставляется “как есть“. Если есть какие-то рациональные предложения - давайте его доработаем.
Tagged under
Comments
20 байт ОЗУ.
При желании можно сделать облегченную версию, урезав функционал. Например, ограничить количество нот и записывать их восьми разрядными цифрами. Но это уже потребует допиливание кода.
Так держать!
Вот еще пара мелодий:
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
};
Если есть возможность пожалуйста напишите больше теории про генерацию звука с диаграммами и эл. схемой для примера. спс
Но вот генерить музыку один в один по этим исходникам к сожалению не удалось. Точнее принцип-то работает, но...
Переполнения, отвечающие за частоту ноты отсчитываются правильно, а вот досчет (когда остаток уже меньше 0хFF) уже не происходит. Поэтому многие соседние ноты звучат одинаково (на слух просто фальшь). Копал долго, но исправить так и не удалось. Переделал генерацию частоты под 16-битный Т1 и все стало Ок. И Грибоедов фальшивить тоже перестал.
Правда может это только у меня так вышло?
PS:Где то тут видел пост с понятием "Низкоуровневое мышление". Блин, ну точнее не скажешь! После ассемблера приходится просто постоянно таки его перебарывать.
Все косяки вычистить не удалось. Делал модуль ради спортивного интереса.
На самом деле знание ассемблера очень помогает при программировани и на Си. Ясно представляешь, что происходит в контроллере. Это важно.
Нужно сделать работу: воспроизвести (в системе PROTEUS) заданную мелодию "Сакура". Сможете помочь? За вознаграждение.
Спасибо.
1) Самое главное - в функции SOUND_tone оператор tone = toneNote; надо заменить на оператор tone += toneNote; Первый полупериод выходного сигнала генерируется правильно, например 2,5 периода работы таймера, пауза и все последующие импульсы будут генерироваться длительностью 3 периода.
2) Второе - разрешать прерывание по компаратору в модулях SOUND_Com и SOUND_PlaySong преждевременно и вредно, лучше разрешение переместить в модуль SOUND_Duration и поставить его вместо команды
Тогда включение звука будет производиться корректно.
замаскировано. Программа обработки клавиши работает по OVF она формирует состояние STATE_PLAY.
По следующему OVF срабатывает SOUND_dur, которая считывает из массива первый звук.
Эта программа должна выполниться перед SOUND_tone. В этом и есть смысл перестановки команд разрешения прерывания. В момент, когда SOUND_dur установит разрешение прерывания,
начнет работать SOUND_tone (по этому же OVF).
SOUND_tone установит на выходе сигнал и начнет работать параллельно SOUND_dur. На
первых циклах OVF прерывания будут приходить одновременно. Поскольку COMP имеет старший приоритет, SOUND_tone будет обслуживаться первой. Но затем прерывания разбегутся во времени, поскольку компаратор не будет равен нулю.
На подавляющем числе проходов м модуле SOUND_dur делается три проверки, инкремент счетчика и обрезание его старшей части. вместе со входом и выходом - не более 50 тактов - 3 мкс.
Каждые 16 мкс контроллер обслуживает два прерывания, на них из 16 мкс тратится 5. Все работает.
вполне достаточно. А затраты по времени уменьшатся с 31 до 4%.
компилятор ругается, например, на TIMSK=0xC0: Error: '(' not expected. Т.е. идет пересечение имен тона нот С0 с шестнадцатеричн ой записью 0хС0. Как только я из главного файла убираю include sound.h, все норм..... Проект в CodeVision.
На баскоме есть куча мелодий, переведенных с сименса. Пробовал такой макрос, чуш какая то получается.
#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 - подставляя ваши циферки - тоже фигння получается. А так - отличная бибилиотека, спасибо.
RSS feed for comments to this post