Arduino + микрофон
В данной статье рассматривается частный случай подключения микрофона к Arduino. Всё написанное также верно и для клонов Arduino, например Freeduino.
В статье встречаются технические термины, для понимания которых могут быть не бесполезны статьи Использование прерываний Arduino, Таймеры-счётчики Arduino и Как подключить к Arduino...
Подключение микрофона к Arduino.
Надо заметить, что подключить к Arduino конденсаторный (электретный) микрофон - это задача, которая требует навыка работы с операционными усилителями, поскольку сам по себе выходной сигнал от микрофона довольно слабый, и поэтому прежде, чем подать сигнал с микрофона на АЦП (аналого-цифровой преобразователь), его желательно предварительно усилить, и заодно немножко почистить от шума в цепях питания.
Дело в том, что в схеме Arduino питание цифровой и аналоговой цепей микроконтроллера производится по упрощённой схеме, без LC-фильтра между ними. И соответственно, всё, что происходит в цифровой части схемы, попадает в аналоговый сигнал, оцифровываемый с помощью АЦП.
Поэтому, подключая микрофон, я включил в цепь питания микрофона и операционного усилителя дроссель L1, что позволило заметно снизить количество шумов в сигнале.
В качестве схемы для подключения микрофона я взял за основу схему подключения микрофона из Application Note AVR335: Digital Sound Recorder with AVR and DataFlash. Микрофон подключал так называемый компьютерный, то есть со стерео-джеком 3.5-мм.
Саму схему я путём экспериментов полностью изменил, поскольку микросхема усилителя TL071, приводимая в Application Note, у меня от +5 Вольт работать не захотела, а в даташите я так и не смог найти минимальное напряжение, которое ей требуется. Поэтому использовал в качестве усилителя тот низковольтовый операционник, который имелся в запасе, а им оказался KA2209, который меня в своё время привлёк своей ценой и тем, что он требует однополярного напряжения. Но вообще-то это усилитель мощности и даже не аудио. Хотя с питанием от дросселя он меня даже удивил тем, что не шумит (без дросселя шумел довольно заметно). Так что я счёл его использование в данной схеме вполне приемлемым.
Для того, чтобы откалибровать схему, я подал на вход вместо сигнала от микрофона напряжение +5 Вольт, используя для отображения простой скетч, отображающий состояние АЦП на текстовом терминале, совместимом с VT100:
void setup()
{
Serial.begin(230400);
Serial.print("\x1B[?25l"); // Выключение курсора
}
void loop()
{
unsigned char input = (analogRead(A0) >> 6); // Чтение состояния входа Analog In 0 со сдвигом значения в диапазон [0..15]
Serial.print("\x1B[0;0H|"); // Курсор в позицию 0, 0
for (unsigned char n = 0; n < 16; ++n) { // Изображение шкалы
Serial.print((n == input) ? '+' : '-');
}
Serial.print('|');
}
В linux после успешной компиляции и загрузки скетча в память микроконтроллера, можно вызвать эмулятор VT100-терминала командой
screen /dev/ttyUSB0 230400
где /dev/ttyUSB0 - это путь к USB-устройству Arduino. Выход из screen производится нажатием Ctrl+A, затем Ctrl+\ и ответом y на запрос подтверждения выхода. Либо отключением Arduino от USB.
Калибровку удобно производить, снимая сигнал с ползунка потенциометра. После этого можно подать на вход схемы сигнал от микрофона и посмотреть, насколько результат совпадает с ожидаемым.
В схеме присутствуют два потенциометра, подключенные к входам Analog In 1 и 2. В скетче они используются для регулировки фильтра и уровня реверберации соответственно.
Фильтрация сигнала требуется по той причине, что регулировка уровня реверберации приводит к тому, что при достаточно больших уровнях громкости усилителя и реверберации происходит самовозбуждение системы. Без регулировки (операции умножения 8-битного текущего значения в буфере на 7-битное значение уровня реверберации) этого не происходит. С фильтром же у меня микрофон не "заводится" даже, если поднести его вплотную к динамику.
Скетч захватывает сигнал с входа АЦП, затем сигнал обрабатывается обрезным фильтром ВЧ четвёртого порядка, на него накладывается эффект реверберации, затем сигнал выводится на вывод широтно-импульсного модулятора (ШИМ) Digital 11, к которому подключен 8-Омный динамик с усилителем класса D. Текст скетча с комментариями приводится ниже:
#define MICINPUT A0 // Микрофон подключен к выводу Analog In 0
#define FILTERINPUT A1 // Регулятор частоты среза фильтра - вывод Analog In 1
#define ECHOINPUT A2 // Регулятор уровня реверберации - вывод Analog In 2
#define PWMOUTPIN 11 // Вывод ШИМ - Digital 11
volatile bool waitForInterrupt = true; // Флаг ожидания прерывания
unsigned int out = 0; // Виртуальный звуковой выход
unsigned char filterInput = 0; // Состояние регулятора частоты среза фильтра
unsigned char echoInput = 0; // Состояние регулятора уровня реверберации
#define BUFSIZE 1096 // Размер буфера ревербератора
unsigned char buffer[BUFSIZE]; // Буфер ревербератора
int nbuf = 0; // Текущий индекс буфера ревербератора
void setup() // Инициализация микроконтроллера
{
noInterrupts(); // Выключить прерывания
clearBuffer(); // Инициализация буфера ревербератора
pinMode(PWMOUTPIN, OUTPUT); // Контакт вывода ШИМ в режим вывода
setupPWMOutput(); // Настройка ШИМ
interrupts(); // Включение прерываний
}
void loop() // Основной цикл программы
{
out = (analogRead(MICINPUT) << 6); // Чтение состояния микрофонного входа
// со сдвигом значения в диапазон [0..65536]
filter(); // Обработка сигнала фильтром
mixReverb(); // Наложение эффекта ревербератора
while(waitForInterrupt); // Ожидание прерывания
OCR2A = (out >> 8) & 0xFF; // Вывод сигнала на выход ШИМ
waitForInterrupt = true; // Установка флага ожидания прерывания
++nbuf; // Увеличение индекса буфера на 1
if (nbuf >= BUFSIZE) { // Если индекс вышел за пределы размеров буфера
nbuf = 0; // Обнуление индекса
filterInput = (analogRead(FILTERINPUT) >> 4); // Чтение состояния регулятора частоты среза фильтра
echoInput = (analogRead(ECHOINPUT) >> 3); // Чтение состояния регулятора уровня реверберации
}
}
void setupPWMOutput() // Настройка ШИМ
{
TCCR2A = 0;
TCCR2A |= _BV(WGM21) | _BV(WGM20); // Режим быстрого ШИМ (TOP = 0xFF)
TCCR2A |= _BV(COM2A1); // Сброс OC2A при совпадении, установка OC2A в нижней точке,
// (не-инверсный режим).
TCCR2B = 0;
TCCR2B |= _BV(CS20); // Тактовый сигнал таймера 2 - от предделителя /1
TIMSK2 |= _BV(OCIE2A); // Разрешить прерывание TIMER2_COMPA_vect
}
ISR(TIMER2_COMPA_vect) // Обработка прерывания TIMER2_COMPA_vect
{
waitForInterrupt = false; // Сброс флага ожидания прерывания
}
void clearBuffer() // Инициализация буфера
{
for (int n = 0; n < BUFSIZE; ++n) { // Заполнение буфера нулями
buffer[n] = 0;
}
}
void filter() // Фильтр ВЧ четвёртого порядка
{
static unsigned int prevx[4] = { 0, 0, 0, 0 };
unsigned int fout = out - (prevx[0] >> 7) * (filterInput >> 1)
- (prevx[1] >> 7) * (filterInput >> 2)
- (prevx[2] >> 7) * (filterInput >> 3);
- (prevx[3] >> 7) * (filterInput >> 4);
prevx[3] = prevx[2];
prevx[2] = prevx[1];
prevx[1] = prevx[0];
prevx[0] = out;
out = fout;
}
void mixReverb() // Наложение реверберации на сигнал
{
unsigned int rev = buffer[nbuf] * echoInput ;
out = (out >> 1) + rev;
buffer[nbuf] = ((out >> 9) & 0x7F) + ((rev >> 9) & 0x7F);
}
Прослушать демо можно, кликнув по ссылке.
Коррективы:
Rev. A. Функция clearBuffer(): исправлено значение верхней границы цикла (было BUFSIZE-1, стало BUFSIZE).
Автор: Андрей Шаройко <vanyamboe@gmail.com>