В первой части мы познакомились с аппаратной частью устройства. Рассмотрели используемые интерфейсы и протоколы и общие принципы работы системы.
При первом включении, система инициализирует карту памяти, многократно посылая сигнал «Reset», пока не получит ответ. Это происходит в фоновом режиме, поэтому все остальные функции устройства работают в обычном режиме.
После получения сообщения о готовности карты, микроконтроллер считывает дескриптор статуса карты и идентификационное поле для определения емкости карты и серийного номера. Затем считывается первый сектор и проверяется наличие «своей» индексной таблицы, и создается новая, если таковая отсутствует (т.е. это означает, что существующая файловая система будет удалена, но карта потом может быть в любой момент отформатирована).
Устройство отслеживает состояние телефонной линии и отображает статус на дисплее: “DISCONNECTED” – линия не подключена (отсутствует напряжение на линии), “IDLE” – в ожидании(on-hook), “IN USE” – линия занята, проводится запись (off-hook).
При помощи клавиатуры пользователь может выбрать запись для прослушивания (клавиши «влево», «впрво»). Центральная клавиша «Enter» может использоваться для прерывания воспроизведения, а также для выбора режима отслеживания: всегда, только когда линия в состоянии «трубка снята» и выключен. Клавиши «вверх» и «вниз» пока не задействованы.
При обнаружении входящего звонка или DTMF посылки АТС (АОН) устройство переходит в режим записи, на второй строке индикатора отображается определенный номер. На первой строке отображается счетчик количества звонков и таймер. Запись продолжается до момента освобождения линии, если звонок принят. Если звонок не принят, запись прекращается через несколько секунд в отсутствии активности.
При исходящем звонке запись начинается с момента поднятия трубки и прекращается при освобождении линии. В этом случае на первой строке индикатора отображаются цифры набранного номера, на второй – таймер и статус.
DTMF команды
Устройство распознает несколько команд, которые можно подать с помощью DTMF сигналов. Команды начинаются с префикса «**».
- 01HHMMSS: установка времени в формате HH (часы), MM (минуты), SS (секунды);
- 02YYMMDD: установка даты в формате YY (год), MM (месяц), DD (день);
- ##: прекращение записи текущего звонка.
Принятые команды подтверждаются пятью короткими звуковыми сигналами в линию.
Описание параметров работы с приложением на компьютере приводится в приложении по окончании статьи.
Основной временной параметр в программном обеспечении для микроконтроллера нашего проекта - это частота дискретизации, генерируемая при помощи Таймера/счетчика 0 в режиме CTC (Clear Timer on Compare Match). Код инициализации Таймера0 устанавливает делитель тактовой частоты CLK/8, при этом получаем частоту 1.3842 МГц. Прерывание Timer0 Compare Match устанавливает в регистре Output Compare 0 значение 171 каждые 5 раз или 172 в остальных случаях. В среднем, это эффективно, если разделить на (4×171+172)/5=172.8 – мы получаем точно 8000 Гц с максимальным джиттером 0.46%, что допустимо для наших целей.
АЦП и сэмплирование аудио
АЦП микроконтроллера настроен на автоматический запуск по прерыванию Timer0 Output Compare Match и вырабатывает прерывание по готовности результата (по окончанию преобразования). Это происходит регулярно каждые 823 машинных цикла, после старта преобразования (аналого-цифровое преобразование занимает 13 циклов АЦП и мы используем предделитель тактовой частоты на 64 – CLK/64).
Подпрограмма обслуживания прерывания от АЦП представляет результат в двух видах:
- как беззнаковая байтовая величина в диапазоне 0-255, где 0 В = 128 (из-за смещения задаваемого входным каскадом). Это значение присваивается переменной sample8 (см. исходный код);
- как 16-битное значение со знаком, где 0 В это фактически 0 – то есть мы вычитаем смещение по постоянному току. Переменная хранящее это значение называется sample16. Она представлена как 16-битная переменная, хотя фактический результат АЦП – 10-битный.
Беззнаковые значения используются для передачи по последовательному протоколу, для записи на карту памяти и воспроизведения. Старшие 8 бит значения со знаком используются в алгоритме определения DTMF сигналов. Полное 16-битное значение со знаком используется в экспериментальном компрессоре ADPCM.
DTMF детектор
Современные телефонные аппараты используют тональный набор номера DTMF (Dual-Tone Multi-Frequency – двухтональный многочастотный аналоговый сигнал), при котором каждая нажатая на клавиатуре телефона кнопка вызывает генерацию тонального сигнала, представленного сложением двух синусоидальных сигналов определенной частоты.
1209 Гц |
1336 Гц |
1477 Гц |
1633 Гц |
|
697 Гц |
1 |
2 |
3 |
A |
770 Гц |
4 |
5 |
6 |
B |
852 Гц |
7 |
8 |
9 |
C |
941 Гц |
* |
0 |
# |
D |
Таким образом, осуществление детектирования DTMF сигналов сводится к измерению интенсивности сигнала на этих частотах. Если мы определили интенсивный сигнал в определенный промежуток времени одновременно с определенной частотой из таблицы выше, мы можем утверждать что соответствующая цифра была набрана.
При аппаратной реализации это типично решается с помощью полосовых фильтров. При программной же реализации нам необходимо решить эту задачу с помощью цифровых фильтров. Для измерения интенсивности сигнала на определенных частотах используется процедура, известная как алгоритм Герцеля, который проделывает это с хорошей точностью.
Здесь мы не будем рассматривать общую теорию данного алгоритма, которую пользователь сможет найти в Интернете или специальных учебных пособиях.
Для данного алгоритма требуется некоторые, предварительно вычисленные константы, и состоит он из двух частей: внутренняя часть, запускаемая для каждого отсчета и внешняя (финализация), запускаемая по окончанию каждого блока.
Основной параметр – размер блока. Чем больше блок, тем чувствительнее фильтр и тем меньше полоса пропускания. Мы используем блок размером N=206 выборок (назовем его - аудио блок или звуковой блок). Получаем минимальную длительность обнаружения сигнала:
206/8 кГц = 25.75 мс
и полосу пропускания приблизительно 40 Гц.
Исходя из этого, для каждой частоты мы можем вычислить коэффициенты:
k = int(0.5+f×N/S) и c = 2×cos(2 ×k × pi / N);
где, N- размер блока, f – частота, для которой будет проводится измерение интенсивности, S – частота дискретизации. Параметр с называется коэффициентом косинуса и он будет использоваться в остальной части алгоритма Герцеля.
В нашей реализации используется арифметика с фиксированной точкой с 9-битными коэффициентами, интерпретируемыми как 1 бинарная цифра до десятичной точки и 8 цифр после. Так, для частот, которые нам нужны, мы получили следующие коэффициенты:
Коэффициент c в формате |
||||
Частота |
k |
c |
decimal |
hex |
440 |
11 |
1.8885 |
483 |
1E3 |
697 |
18 |
1.7061 |
437 |
1B4 |
770 |
20 |
1.6393 |
420 |
1A3 |
852 |
22 |
1.5664 |
401 |
190 |
941 |
24 |
1.4876 |
381 |
17C |
1209 |
31 |
1.1706 |
300 |
12B |
1336 |
34 |
1.0176 |
260 |
104 |
1477 |
38 |
0.8004 |
205 |
0CC |
1633 |
42 |
0.5714 |
146 |
092 |
Заметьте, что мы добавили еще одну частоту 440 Гц. Эта частота сигналов вызова номера, занятого номера.
Для внутренней части алгоритма Герцеля, для каждой из этих частот и для каждой выборки, мы должны вычислить:
y = d1 × c - d2 + s
d2=d1
d1=y,
где d1 и d2 – промежуточные результаты, с – коэффициент косинуса (см. таблицу выше), s – значение текущей выборки. Первое вычисление по данному выражению называется «умножение и накопление». Конвертирование данного выражения в единицы с фиксированной точкой дает:
y=((d1×c)>>8)-d2+ss8,
где ss8 – значение текущей выборки в 8-битном формате со знаком (это 8 старших значащих бита переменной sample16) и «>>» – оператор сдвига вправо. Переменные d1, d2, c должны быть 16-битными значениями со знаком и фактически будут массивами, потому что они нужны будут для каждой частоты. И в результате мы должны будем проделать следующее:
for (n=0;n<9;n++) {
y=((d1[n]*c[n])>>8)-d2[n]+ss8;
d2[n]=d1[n]; d1[n]=y;
}
В конце звукового блока мы должны будем произвести завершающие вычисления:
m = d1×d1 + d2×d2 - d1×d2×s
Результатом m является величина или интенсивность сигнала для данной частоты. Конвертируя в формат с фиксированной точкой, получаем:
d1=d1>>8;
d2=d2>>8;
mag=d1*d1+d2*d2-d1*((d2*c[n])>>8);
d1=d2=0;
В итоге, алгоритм Герцеля позволяет нам определить интенсивность сигнала на заданной частоте, произведя одно умножение 16-битных значений, одно сложение, одно вычитание и одну 8-битную операцию сдвига вправо за одну выборку для обнаруженной частоты. В конце блока из 206 выборок проводятся дополнительные вычисления: три 8-битных сдвига вправо, три перемножения 16-битных значений, одно сложение и одно вычитание. И благодаря встроенному в микроконтроллер модулю аппаратного умножения, это происходит очень быстро (перемножение двух 8-битных операндов за два машинных цикла).
Однако, при попытке организации данных вычислений с использованием языка С, мы видим что это приводит к умножению 16-битного числа (d1) на 16-битное число (d2) и результат 32-битное число. Но не только в этом расточительство, т.к. требуется четыре операции 8-битного умножения, ведь мы еще потеряем младшие значащие 8 бит.
В итоге в ходе написания кода на Си мы увидели, что лишь один круг вычислений, которые мы рассмотрели выше, занял 100 машинных циклов. Вспомните, ведь мы должны проделать это для 9 частот, а это около 900 машинных циклов или 65% от нашего «бюджета» в 1376 машинных циклов. А мы еще учли часть финализации, где в три раза больше операций. По грубой оценке в общем потребовалось бы около 2700 машинных циклов.
Но финализацию мы можем провести просто. Ключ к этому – наблюдение, что мы не нуждаемся во всех результатах по окончанию звукового блока. Мы можем иметь два набора значений d1 и d2, один используется во внутренней части алгоритма, другой используется в финализирующей части с результатами предшествующего блока. Кроме того, мы можем распределить вычисления на весь блок из 206 выборок, делая только одну или две операции за отсчет. Тогда завершающая часть примет вид:
if (nn switch (cc) {
case 0: d1[n]>>=8; break;
case 1: d2[n]>>=8; break;
case 2: mag =d1[n]*d1[n]; break;
case 3: mag+=d2[n]*d2[n]; break;
case 4: coef=c[n]; break;
case 5: coef*=d2[n]; coef>>=8; break;
case 6: coef*=d1[n]; break;
case 7: mag-=coef; break;
case 8: // results interpretation,
// omitted for brevity
case 9: d1[n]=d2[n]=0; break;
}
cc++; if (cc==10) cc=0,n++;
В итоге завершающая часть алгоритма отнимет менее 60 машинных циклов и мы получили равномерное распределение рабочей нагрузки, но используем при этом вдвое больше памяти (72 Байта).
Следующий шаг – оптимизация внутренней части алгоритма. С этой целью мы объединили два массива d1 и d2 в один массив d, упростив т.о. индексацию, и смогли перемещаться по массиву с помощью одной 8-битной операции инкремента (это также означает, что массив не может пересечь 256-байтную границу). Операции умножения в этой части алгоритма были реализованы с помощью ассемблера. Таким образом внутренняя часть алгоритма Герцеля заняла 42 машинных цикла, вместо 100.
В итоге весь алгоритм Герцеля, примененный в исходном коде проекта, забирает себе менее 500 циклов за выборку (менее 37% из нашего «бюджета» машинного времени).
Последовательный протокол
Последовательный протокол мультиплексирует аудио данные и команды управления на одном канале. Используется метод, при котором все передаваемые байты, за исключением определенного, называемого префиксом команды (в нашем случаем 0xFE), являются звуковыми данными. При обнаружении префикса команды следующие байты являются параметрами команды.
Определены следующие исходящие команды (от регистратора к компьютеру)
Имя команды |
Формат |
Описание |
CMD_NOTIFY_ON_HOOK |
FE 01 |
Линия занята |
CMD_NOTIFY_OFF_HOOK |
FE 02 |
Линия свободна |
CMD_NOTIFY_DISCONNECTED |
FE 03 |
Отсутствует напряжение на линии |
CMD_DTMF_DETECTED |
FE 10 XX |
DTMF цифра XX (в ASCII) определена |
CMD_LOCAL_RING_DETECTED |
FE 11 |
Входящий звонок определен |
CMD_ADPCM_STATE |
FE 00 XX YY YY |
Старт ADPCM сжатого звукового блока |
CMD_RAW_BLOCK |
FE 0F |
Старт несжатого («сырого») звукового блока |
CMD_LITERAL |
FE FE |
Литеральное значение 0xFE в аудио потоке |
Определены следующие входящие команды (от компьютера к регистратору)
Имя команды |
Формат |
Описание |
CMD_LCD_BACKLIGHT_ON |
FE 80 |
Включить подсветку дисплея |
CMD_LCD_BACKLIGHT_OFF |
FE 81 |
Выключить подсветку дисплея |
CMD_GO_OFF_HOOK |
FE 82 |
Снять трубку (off-hook) |
CMD_GO_ON_HOOK |
FE 83 |
Положить трубку (on-hook) |
CMD_SET_TIME |
FE 90 HH MM SS |
Установить время to HH:MM:SS (формат BCD) |
CMD_SET_DATE |
FE 91 YY MM DD |
Установить дату MM/DD/YY (формат BCD) |
CMD_LITERAL |
FE FE |
Литеральное значение 0xFE в аудио поток |
Скорость передачи – 115200 бит/с.
Перед передачей звукового блока из 206 выборок передается стартовый блок. Он информирует компьютер о том, какие данные будут передаваться (сжатые или нет) и если сжатые данные передаются, то будет отправлена информация о сжатии.
Разработка кода для MMC/SD интерфейса
Существует много доступных библиотек для работы с MMC/SD картами памяти, но они не подходят для нашего проекта, т.к. у нас накладываются жесткие ограничения по времени. Поэтому был разработан собственный код для работы с картами памяти с применением техники распределения нагрузки (в ходе циклов оцифровки аудио сигнала) с применением множества статических конечных автоматов, контролируемых глобальными переменными. Данный код работает, но он слишком длинный, с каскадной структурой и сложен для исследования.
При программной реализации интерфейса MMC/SD используется около 80% доступной памяти SRAM. Размер блока MMC/SD составляет 512 Байт и мы используем три таких блока памяти: два для двойного буфера записи/воспроизведения и один для таблицы указателей на начало кластеров для каждой записи.
Это ограничение памяти исключило возможность использования файловых систем, таких как FAT, поэтому мы должны были обратится к простой схеме, где карта памяти рассматривается как огромный кольцевой буфер. При заполнении его старые записи будут заменяться новыми (перезаписываются).
Чтобы разместить 230 указателей в единственном индексном секторе размером 512 Байт, была разработана система адресации: мы разделили карту на кластеры с 16 секторами (8 Кбайт или приблизительно 1 с записи) и каждый номер кластера имеет 16-битную ширину. Таким образом обеспечивается поддержка карт объемом 512 МБайт.
Преимущество двойного буфера в том, что мы можем одновременно производить запись и воспроизведение, и это возможно, потому что скорости записи и воспроизведения идентичны.
Программирование микроконтроллера
Программное обеспечение для микроконтроллера ATmega32 скомпилировано с использованием AVR-GCC 3.4.3 и libc-1.2.3 (WinAVR-20050214). При установке Fuse-битов необходимо учесть, что используется внешний кварцевый резонатор и отключен JTAG-порт (он используется для подключения LCD). Исходный код снабжен комментариями и пользователь сможет экспериментировать с ним (включив отладку перед компиляцией, см. исходный код).
Приложение: описание параметров программы для компьютера, схема, исходные коды и .hex-файлы для микроконтроллеров, исходный код и программа для компьютера, описание алгоритма Герцеля и алгоритма зарядки аккумуляторов в системе резервного питания.