Большинство из вас уже знают функцию tone() из стандартной библиотеки Arduino. Но она просто генерирует прямоугольный сигнал и звучит довольно скучно.

Но с помощью простого трюка мы можем генерировать любую форму сигнала, использовав Arduino и небольшую схему из нескольких компонентов, и с этой новой функцией можно даже имитировать музыкальные инструменты.
В этом проекте мы используем таймер/счетчик 2 микроконтроллера ATmega328 для генерации сигнала ШИМ. После фильтрации с помощью фильтра нижних частот, состоящего из резисторов и конденсаторов, мы получаем синусоидальный, пилообразный или прямоугольный сигнал волну с программируемой частотой и скважностью. Модулируя с помощью ADSR-огибающей, можно даже имитировать любой музыкальный инструмент и играть какую-либо мелодию. Схема простого синтезатора аудиосигналов представлена на следующем изображении.

Далее представлен код для синтезатора на Arduino, состоящий из трех файлов: скетча chimes.ino с примером мелодии и движка синтезатора, содержащегося в файлах chimes.cpp и chimes.h.
chimes.ino
#include "chimes.h"
using namespace Chimes;
// Сумма значений ADSR не должна превышать 100%
uint8_t envelope[] = {
0, // атака[%]
20, //спад[%]
0, //сустейн[%]
80, //высвобождение[%]
16 //уровень сустейна 1..32
};
void setup()
{
init(
TRI, //TRI: Треугольник, RECT: Прямоугольник
50, // коэффициент заполнения 0..100%, имеет значение только для треугольника и прямоугольника
envelope);
}
uint16_t melody[][2] = {{330, 1000}, {415, 1000}, {370, 1000}, {247, 1000}, {0, 1000}, {330, 1000}, {370, 1000}, {415, 1000}, {330, 1000}, {0, 1000}, {415, 1000}, {330, 1000}, {370, 1000}, {247, 1000}, {0, 1000}, {247, 1000}, {370, 1000}, {415, 1000}, {330, 1000}};
void loop()
{
static int i = 0;
if (i < 19 && !isPlaying())
{
play(melody[i][0], melody[i][1]);
i++;
}
}
chimes.h
#ifndef CHIMES_H
#define CHIMES_H
#include "Arduino.h"
enum waveform
{
SINE, // Синус
RECT, // Прямоугольник
TRI, // Треугольник
PAUSE // Внутренняя переменная, не используется
};
#define MAX_VOLUME 32
namespace Chimes
{
void init(uint8_t waveform = SINE, uint8_t duty_cycle = 50, uint8_t *envelope = NULL);
void play(uint16_t freq, uint16_t duration);
// Возвращает true во время воспроизведения ноты
boolean isPlaying();
}
#endif
chimes.cpp
#include <Math.h>
#include "chimes.h"
#define ISR_CYCLE 16 //16s
char strbuf[255];
uint16_t ADSR_default[] = {0, 0, 100, 0, MAX_VOLUME};
uint16_t ADSR_env[5];
uint16_t nSamples;
uint8_t adsrPhase;
uint32_t tPeriod;
uint8_t *samples;
uint8_t *_envelope, _waveform, _duty_cycle;
uint16_t &_sustain_lvl = ADSR_env[4];
enum ADSR_phase
{
ATTACK,
DECAY,
SUSTAIN,
RELEASE
};
namespace Chimes
{
void init(uint8_t waveform, uint8_t duty_cycle, uint8_t *envelope)
{
Serial.begin(115200);
//PWM Signal generation
DDRB |= (1 << PB3) + (1 << PB0); //OC2A, вывод 11
TCCR2A = (1 << WGM21) + (1 << WGM20); // быстрый ШИМ
TCCR2A |= (0 << COM2A0) + (1 << COM2A1); // Установка OC2A для сравнения, очистить OC2A в нижней части (режим инвертирования)
TCCR2B = (0 << CS22) + (0 << CS21) + (1 << CS20);
samples = (uint8_t *)malloc(0);
_waveform = waveform;
_duty_cycle = duty_cycle;
_envelope = envelope;
}
void play(uint16_t freq, uint16_t duration)
{
uint8_t waveform = _waveform;
// инициализация adsr в соответствии с длиной ноты
for (int i = 0; i < 4; i++)
{
if (_envelope)
{
ADSR_env[i] = (uint32_t)_envelope[i] * duration / 100;
}
else
{
ADSR_env[i] = (uint32_t)ADSR_default[i] * duration / 100;
}
//Serial.println(ADSR_env[i]);
}
ADSR_env[4] = _envelope ? _envelope[4] : MAX_VOLUME;
//Serial.println(ADSR_env[4]);
if (freq == 0)
{ // пауза
tPeriod = ISR_CYCLE * 100;
waveform = PAUSE;
}
else
tPeriod = 1E6 / freq;
nSamples = tPeriod / ISR_CYCLE;
realloc(samples, nSamples);
uint16_t nDuty = (_duty_cycle * nSamples) / 100;
switch (waveform)
{
case SINE: // Синус
for (int i = 0; i < nSamples; i++)
{
samples[i] = 128 + 127 * sin(2 * PI * i / nSamples);
}
break;
case TRI: // Треугольник
for (int16_t i = 0; i < nSamples; i++)
{
if (i < nDuty)
{
samples[i] = 255 * (double)i / nDuty; //Rise
}
else
{
samples[i] = 255 * (1 - (double)(i - nDuty) / (nSamples - nDuty)); //Fall
}
}
break;
case RECT: // Прямоугольник
for (int16_t i = 0; i < nSamples; i++)
{
i < nDuty ? samples[i] = 255 : samples[i] = 0;
}
break;
case PAUSE:
memset(samples, 0, nSamples);
}
TIMSK2 = (1 << TOIE2);
/*for(uint16_t i = 0; i < nSamples; i++) {
sprintf(strbuf, "%d: %d", i, samples[i]);
Serial.println(strbuf);
}*/
}
// Возвращает true во время воспроизведения ноты
boolean isPlaying()
{
return (1 << TOIE2) & TIMSK2;
}
} // namespace Chimes
// Вызывается каждые 16 секунд, когда переполнен TIMER1
ISR(TIMER2_OVF_vect)
{
static uint32_t adsr_timer, adsr_time;
static uint16_t cnt; // Счетчик индекса
static uint8_t sustain_lvl, vol;
// Установка OCR2A на следующее значение в массиве выборок, это соответственно изменит рабочий цикл
OCR2A = vol * samples[cnt] / MAX_VOLUME;
if (cnt < nSamples - 1)
{
cnt++;
}
else
{
cnt = 0;
adsr_timer += tPeriod;
if (adsr_timer >= 10000)
{ //every 10 millisecond
adsr_timer = 0;
switch (adsrPhase)
{
case ATTACK:
if (ADSR_env[ATTACK])
{
vol = MAX_VOLUME * (float)adsr_time / ADSR_env[ATTACK];
if (vol == MAX_VOLUME)
{
adsrPhase = DECAY;
adsr_time = 0;
}
}
else
{
adsrPhase = DECAY;
vol = MAX_VOLUME;
adsr_time = 0;
}
break;
case DECAY:
if (ADSR_env[DECAY])
{
sustain_lvl = _sustain_lvl;
vol = MAX_VOLUME - (MAX_VOLUME - _sustain_lvl) * (float)adsr_time / ADSR_env[DECAY];
if (vol <= sustain_lvl)
{
adsr_time = 0;
adsrPhase = SUSTAIN;
}
}
else
{
adsrPhase = SUSTAIN;
sustain_lvl = MAX_VOLUME;
adsr_time = 0;
}
break;
case SUSTAIN:
if (adsr_time > ADSR_env[SUSTAIN])
{
adsrPhase = RELEASE;
adsr_time = 0;
}
break;
case RELEASE:
if (ADSR_env[RELEASE])
{
vol = sustain_lvl * (1 - (float)adsr_time / ADSR_env[RELEASE]);
if (vol == 0)
{
adsr_time = 0;
TIMSK2 = (0 << TOIE2);
adsrPhase = ATTACK;
}
}
else
{
adsrPhase = ATTACK;
vol = 0;
adsr_time = 0;
TIMSK2 = (0 << TOIE2);
}
break;
}
adsr_time += 10;
}
}
}
© digitrode.ru