[Из песочницы] Hands Free, но не телефон. Послушный дом, когда рук не хватает
Здравствуй, Сообщество!
Появилось у меня желание снова поковыряться с микроконтроллером и сделать что-нибудь полезное. Цель сформировалась почти сразу, так как в квартире меня кое-что напрягало.
Как известно, компьютерный стол — он же обеденный, чтобы смотреть Дробышевского или читать Гиктаймс / Зелёного Кота / etc. одновременно с ужином. Но есть проблема — из кухни я иду обычно с обеими занятыми руками, обратно тоже, ведь чашки копятся по 3 шт. Включать и выключать свет на кухне (выключатель тройной — кухня/ванна/туалет) приходится плечом, носом, мизинцем. То есть неудобно никак, а переставить ниже нельзя. Возникла задача управлять как-то дистанционно.
Всякие датчики присутствия и прохода отмёл сразу — не та точность, нет управления по воле хозяина. Решение найдено в звуковом управлении, голосом. Скажу сразу, я не планировал делать рапознавалку речи, она не нужна здесь. Свет, включающийся по хлопку, описан ещё в Радио-80х, но я так не хотел делать. Получилось своеобразное handsfree, когда руки заняты. Подробности — дальше.
Аппаратная часть.
Была в наличии плата с Atmega32 с кварцем и периферией SEM0007M-32A и россыпь электроники.
Нашёлся микрофончик и операционный усилитель. Для выхода — транзистор в sot32 корпусе на плате, там же реле на 7 Ампер. Всё собрано упихано в коробочку для визиток, реле запараллелено с выключателем, микрофон спрятан под розеткой. Схема банальна, я её даже не рисовал. Используется только один аналоговый вход и один дискретный выход МК. Плата SEM избыточна, но пока пусть так будет.
Плата и неприбранные провода. Потом переделал аккуратнее.
Сам выключатель, микрофон не виден под демонтированной розеткой.
Поиски алгоритма.
Цель: датчик должен реагировать на слово, например »свет! » при минимуме кода.
Задача: выявить командное слово на фоне возможного шума, стуков, щелчков этим же выключателем. То есть просто амплитудный анализ не подходит, а спектральный анализ показал, что гармоник в слове слишком много и они конечно изменяются. Поэтому надо было искать простое решение, но с приемлемой помехозащитой. Можно сделать несколько временно-частотных фильтров и сравнение с образцом слова, но заниматься распознаванием ни к чему. Решено анализировать наличие только гласного звука, например звука «Е».
Звук «Е». Видно много гармоник, из-за этого анализ затруднён.
Звук «А». Спектр выглядит чище, есть главная частота.
Программная часть
Для того, чтобы узнать спектральные составляющие сигнала, можно использовать цифровые фильтры. В Интернете есть неплохая программа для построения цифровых КИХ и БИХ фильтров и вычисления их коэффициентов — там наглядно и код на Си генерируется автоматически.
float ACoef[NCoef+1] = {
0.00000347268864059354,
0.00000000000000000000,
-0.00001389075456237415,
0.00000000000000000000,
0.00002083613184356122,
0.00000000000000000000,
-0.00001389075456237415,
0.00000000000000000000,
0.00000347268864059354
};
float BCoef[NCoef+1] = {
1.00000000000000000000,
-7.09708794063733790000,
22.77454294680684300000,
-43.03321836036351300000,
52.29813665034108500000,
-41.84199842886619100000,
21.53121088556695300000,
-6.52398963450972500000,
0.89383378261285684000
};
Микроконтроллер, возможно и справился бы, но с отладкой были бы проблемы — пододвинуть границы фильтра нелегко — это новые коэффициенты рассчитывать.
После некоторых поисков — остановился на одночастотном Фурье-преобразовании онлайн. То есть классическое дискретное Фурье-преобразование, выполняемое по приходу каждого отсчёта сигнала с частотой дискретизации (1600 Гц), прохода по частотам нет, частота одна, поэтому её легко настроить по RS-232 при наладке. В итоге анализ был сделан для частоты 128 Гц.
Вследствие коротких сэмплов (блоков) и прямоугольного окна — разрешение по частоте получается низким, что даёт избирательную чувствительность в диапазоне 114…140 Гц, а это тот П-фильтр, который и хотелось получить.
Сначала надо понять, где кричат начинается сигнал голосовой команды. Для этого сначала вычисляется нулевой уровень сигнала, через экспоненциальное сглаживание со сглаживающей константой 1/64. Код приведён ниже.
Сигнал нормализуется к среднему. Для определения уровня интенсивности звука абсолютные значения сигнала также усредняются с константой 1/16, для ВЧ-фильтрации от отдельных полуволн сигнала (это аналог RMS, но проще в вычислениях). Превышение этого уровня над порогом является началом голосовой команды, и начинается последовательный анализ 5-ти блоков по 135 отсчётов (84,3 мс).
// Timer 0 output compare interrupt service routine
interrupt [TIM0_COMP] void timer0_comp_isr(void)
{
a = adc_data[0] << 2 ; // считывание с АЦП и умножение на 4 для улучшения точности.
a0 = (a0*63 + a+ 63) >> 6; // экспон. сглаживание для "0". сход до 10% за 150 отсчётов
ae = (int)(a - a0); //
a = ae; // теперь это нормализованное значение к среднему.
ae = abs(ae);
if (ae < 32) { // обнуление ошибки при целочисленном усреднении
ae = 0;
};
d = (int)((15 * (long int) d + ae + 15) >> 4); // средний эксп. уровень // сход до 10% за 35 отсчётов
if (d > 100) { //превышение порога уровнем сигнала
if (snd == 0) { Yz=0; snd++; } // начинается первый блок
PORTB.1 = 1; // зажигается светодиод индикатор превышения порога
};
.....
На рисунке ниже показан сигнал, уровень сигнала, порог срабатывания и 5 блоков.
Защита от помех
На блоки сигнал разбивается для защиты от импульса — щелчка или стука. У импульса, как известно, равномерная АЧХ, то есть в любой частотной полосе будет ненулевой результат и вероятно, выше порога. Но волос длин, да ум импульс короток. То есть в следующем блоке импульса уже не будет, значит и уровень в частотной полосе будет ниже порога. Одновременно с этим преимуществом, короткие блоки дают низкое разрешение по частоте. Поэтому некоторые частотные отличия в сигнале всё равно попадают в выбранную частотную линию.
Частотное преобразование
В каждом блоке выполняется одночастотное Фурье — преобразование для одной частоты f.
Традиционно, для ускорения вычислений функции sin и cos сделаны табличными и масштабированы до -127…+127. График ниже отображает байтовый массив из 48 значений для sin (x).
Индекс ps
массива si(ps)
вычисляется из аргумента sin (2 * π * f * t / T), конечно с закольцовкой внутри одного периода. Индекс pc
для cos (2 * π * f * t / T) просто сдвинут на 12 позиций вперёд в этом же массиве si
.
Результат Y — уровень спектральной линии получается как сумма абсолютных значений реальной и мнимой части за время одного блока. По-правильному надо делать сумму квадратов, и корень, но это жуть для 8-разрядного МК.
В том же таймере:
a_si = (long int) (a * si[ps]) >> 4; // a*sin
a_co = (long int) (a * si[pc]) >> 4; // a*cos
Ysi = Ysi + a_si ; //сумма
Yco = Yco + a_co;
Y = (labs(Ysi) + labs(Yco)) >> 7; // деление на 128 для помещения в int и передачи по rs-232.
В конце каждого блока Y сравнивается с порогом, подсчитывается количество блоков с превышением порога — сработавших блоков. После экспериментов выяснилось, что минимальное количество сработавших блоков это 3 из 5-ти.
Пример спектральной интенсивности в блоках при голосовой команде. Команда прошла.
Три и более сработавших блока интерпретируется как верно принятая команда. Сигнал на дискретном выходе МК инвертируется, включая или выключая свет. Так как весь анализ происходит внутри блоков, задержки срабатывания после последнего блока нет.
Время выполнения вычислений примерно 1600 тактов, таймер вызывается каждые 9000 тактов, так что загруженность МК невысока — есть место для дальнейших экспериментов с распознаванием. Или можно делать законченное решение меньших размеров и на слабом МК.
Контроль правильности алгоритмов вёлся посредством обмена по RS-232 необходимыми переменными (лог) с программкой на VBasic. Частота f и пороги хранятся в eeprom.
В итоге: датчик оказался очень удобен, реагирует на слова с »А», например »Вааау»,»Тааам»,»Лаайт»,»Мяаау»,»Yao-Yao». Слово »Свеет» упорно отказывается слушать. Щелчки, стуки дверьми, шаги, льющуюся воду игнорирует. Теперь можно ходить с полными руками чашек и тарелок)).