Формирование звука с помощью микроконтроллера не составляет большого труда. Достаточно взять за основу программу мигающего светодиода и подключить вместо него динамик, а в самой программе поменять константу задержки таким образом, чтобы частота на выходе повысилась до звукового диапазона. Человек может услышать звуки с частотой от 50Hz до 15kHz. Светодиод в одном из наших уроков мигает с частотой 4Hz, а если уменьшить время задержки в 1000 раз, то можно получить частоту на выходе 4kHz.

Задача: разработаем программу, при помощи которой микроконтроллер Atmega8 будет воспроизводить простую однотональную мелодию. Тактовая частота микроконтроллера 8МГц.

Воспроизведение однотональных мелодий с помощью AVRМузыкальный ряд делится на октавы, каждая октава делится на 12 нот, это 7 основных(До, Ре, Ми, Фа, Соль, Ля, Си) и пять дополнительных(До диез, Ре диез, Фа диез, Соль диез, Ля диез). Частоты двух соседних нот отличаются друг от друга в одинаковое количество раз, а частоты двух одноименных нот двух соседних октав отличаются в 2 раза.

Для формирования звука используем шестнадцатиразрядный таймер/счетчик1, он будет работать в режиме СТС (сброс при совпадении), для управления коэффициентом пересчета используем регистр ICR1. Режим СТС позволяет осуществлять непосредственное управление частотой сигнала. Для активизации этого режима биты WGM13 и WGM12 устанавливаем в единицу.

Для того чтобы в режиме СТС на выходе формировался периодический сигнал, необходимо настроить выход OC1A таким образом, чтобы при каждом совпадении сигнал на выходе менял свое значение на противоположное. Для этого установим бит COM1A0 в единицу и подключим к нему динамик, а также настраиваем порт PB1 на выход.

Частота сигнала на выходе OC1A определяется по формуле:



где N - коэффициент пересчета предварительного делителя, вместо OCR1A в нашем случае ICR1.

Исходя из этой формулы расчитываем коэффициенты для ICR1, зная частоты основных нот. Так как для большинства разобраться с нотной портитурой не очень просто, названия нот я определил как в редакторе мелодий старого телефона Сименс. Коды таких мелодий еще можно найти в интернете и в дальнейшем поменять в исходном тексте. Также важно понятие как длительность тона - это время звучания одной ноты, она выражается долями от целой. В общем за проигрывание ноты у нас отвечает функция play_note.

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

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

// Воспроизведение однотональных мелодий с помощью AVR
#include <avr/io.h>

// 1 октава 
#define A2      9008 // Ля
#define Ais2    8580 // Ля диез
#define B2      8098 // Си
// 2 октава
#define C3      7644 // До
#define Cis3    7214 // До диез
#define D3      6810 // Ре
#define Dis3    6427 // Ре диез
#define E3      6066 // Ми
#define F3      5726 // Фа
#define Fis3    5404 // Фа диез
#define G3      5101 // Соль
#define Gis3    4815 // Соль диез
#define A3      4544 // Ля
#define Ais3    4289 // Ля диез
#define B3      4049 // Си
// 3 октава
#define C4      3821 // До
#define Cis4    3607 // До диез
#define D4      3404 // Ре
#define Dis4    3213 // Ре диез
#define E4      3033 // Ми
#define F4      2862 // Фа
#define Fis4    2702 // Фа диез
#define G4      2550 // Соль
#define Gis4    2407 // Соль диез
#define A4      2272 // Ля
#define Ais4    2144 // Ля диез
#define B4      2024 // Си
// 4 октава
#define C5      1910 // До
#define Cis5    1803 // До диез
#define D5      1702 // Ре
#define Dis5    1606 // Ре диез
#define E5      1516 // Ми
#define F5      1431 // Фа
#define Fis5    1350 // Фа диез
#define G5      1275 // Соль
#define Gis5    1203 // Соль диез
#define A5      1135 // Ля
#define Ais5    1072 // Ля диез
#define B5      1011 // Си
// 5 октава
#define C6      955 // До
#define Cis6    901 // До диез
#define D6      850 // Ре
#define Dis6    803 // Ре диез
#define E6      757 // Ми
// Длительность тона 
#define LE32    1*3 // 1/32
#define LE16    2*3 // 1/16
#define LE16D   3*3
#define LE16T   2*2
#define LE8     4*3 // 1/8
#define LE8D    6*3
#define LE8T    4*2
#define LE4     8*3 // 1/4
#define LE4D    12*3
#define LE4T    8*2
#define LE2     16*3 // 1/2
#define LE2D    24*3
#define LE1     32*3 // 1

// Функция задержки в us 
void Delay_us(unsigned char time_us)
{ register unsigned char i;
 
  for(i = 0; i < time_us; i++)   // 4 цикла
    { asm (" PUSH  R0 ");       // 2 цикла
      asm (" POP   R0 ");       // 2 цикла
        // 8 циклов = 1 us для 8MHz
    }
}

// Функция задержки в ms 
void Delay_ms(unsigned int time_ms)
{ register unsigned int i;
 
  for(i = 0; i < time_ms; i++)
    { Delay_us(250);
      Delay_us(250);
      Delay_us(250);
      Delay_us(250);
    }
}
 
unsigned char temp;

// Функция установки темпа и паузы 
void set_tempo(unsigned char number)
{
temp = number;                      // Установка темпа
TCCR1A = (1 << COM1A0);             // Используем вывод таймера OC1A
TCCR1B = 0;                         // Звук выключен
}

// Функция проигрывания ноты 
void play_note(unsigned int note, unsigned int LE)
{
ICR1 = note;                        // Установка ICR1
TCNT1 = 0x0000;                      // Очистка Timer/Counter1
TCCR1B |= (1 << WGM13)|(1 << WGM12)  // CTC режим
                      |(1 << CS10);  // Без предделителя
 
Delay_ms(LE*temp*7);                 // Длительность ноты
TCCR1B = 0;                          // Звук выключен
}
 
int main(void)
{
DDRB |= (1 << PB1);
PORTB = 0x00;
 
Delay_ms(50);
 
set_tempo(3);       // Установка темпа
play_note(Dis4,LE8);
play_note(Cis4,LE8);
play_note(Fis3,LE4);
play_note(Fis4,LE4);
play_note(Fis4,LE4);
play_note(Dis4,LE8);
play_note(Cis4,LE8);
play_note(Fis3,LE4);
play_note(Fis4,LE4);
play_note(Fis4,LE4);
play_note(Dis4,LE8);
play_note(Cis4,LE8);
play_note(Fis3,LE4);
play_note(Fis4,LE4);           
play_note(Dis3,LE4);
play_note(Fis4,LE4);
play_note(Cis3,LE4);
play_note(F4,LE4); 
play_note(F4,LE4);

while(1);

}