;вместо ADrWord можно подставить начальный адрес
;во flash-памяти, он может быть отличен от 0:0
sei ;разрешаем прерывания
loop_voice: ;читаем байт для последующего вывода
rcall read_i2c ;чтение памяти, YL,YH — адрес, в DATA — полученных байтов
sleep ;Idle-mode, проснется по прерыванию Timer0
sbrs FLAGS,bSample ;бит встает в 1 в обработчике прер. TIM0
rjmp loop_voice ;если еще не установлен, то на начало цикла
adiw YL,1 ;иначе увеличиваем адрес на 1
cbr FLAGS,1<
sleep ;Idle-mode, проснется по прерыванию Timer0
in temp,TCCR0 ;проверяем, не остановлен ли таймер
tst temp
brne loop_voice ;если не остановлен, то следующий цикл
;иначе вывод звука закончен — делаем что-то еще
…
;===== обработчик прерывания от Timer 0 =====
TIM0:
out TCNT0,T_Sample ;перезаряжаем Timer0
clr temp
out OCR1AH,temp
out ОСR1AL,DATA ;занесение байта в PWM
sbr FLAGS,1<
sbiw XL,1 ; уменьшаем счетчик прочитанных байтов
brne rt_pwm_ ;если он равен 0
out TCCR0,XL ;то выключаем таймер 0
out TCCR1B,XL ;и Timer 1 также
rt_pwm_:
reti ;возврат из обработчика Timer0
В начале программы мы устанавливаем Timer 1 в PWM-режим и задаем ему переключающий режим по выходу ОС1А такой, чтобы там устанавливался низкий уровень, а также запускаем его с входной частотой, равной тактовой. Прерываний от Timer 1 никаких не требуется, он будет работать непрерывно, пока мы его не выключим. Управление битрейтом мы будем осуществлять по прерыванию переполнения Timer0. Если каждый раз в него записывать некоторое число, то можно регулировать частоту таких прерываний. Включим его с частотой на входе, равной 1/64 тактовой (последняя должна быть равна 16 МГц), тогда минимальная частота на выходе будет равна 976 Гц (976,5625 Гц = 16 МГц/64/256). Мы же здесь хотим частоту как можно ближе к 4 кГц (не следует окончательно портить звук еще и изменением битрейта), поэтому мы будем записывать каждый раз в таймер число 193, и он будет считать от 193 до 255, т. е. отсчитывать 62 такта, тогда прерывание будет происходить с частотой почти ровно 4 кГц. Меняя эти параметры (указанное число и частоту на входе Timer 0), можно устанавливать другой битрейт, ограниченный в данном случае скоростью чтения из памяти по интерфейсу I2С (при максимальной частоте шины 400 кГц эта скорость составит около 9 кбайт/с). При более скоростной памяти битрейт будет ограничен в принципе лишь скоростью работы таймера в PWM-режиме (32 кГц), но при воспроизведении такого звука могут возникнуть сложности из-за искажений. Более глубоко в этот вопрос влезать здесь нет смысла.
В процедуре прерывания мы загружаем очередной байт в Timer 1 и будем устанавливать некий флаг (bsampie в регистре флагов), а в основной программе заведем непрерывный цикл, в котором, если этот флаг установлен, производится чтение из памяти следующего байта.
В этой программе число воспроизводимых байтов ограничено 65 536 (64 кбайт), т. к. для упрощения мы считаем их в 16-разрядном регистре х, но при необходимости несложно добавить еще один регистр счетчика адреса и задействовать большую емкость памяти (правда, для длинных клипов придется переходить на другие типы интерфейса, см. главу 16). В листинге 19.1 указаны теоретические начальный адрес (ADrWord) и объем записи (Nbytes), которые нужно для вашей задачи заменить на конкретные числа. Кроме того, по окончании звукового фрагмента программа просто остановится. Несложно сделать так, например, чтобы она «закольцевалась»: для этого вместо выключения таймера просто заново занесите значение Nbytes в регистры XH и XL. В общем, приспосабливайте программу для ваших нужд, как можете.
И еще несколько слов о том, откуда берутся исходные звуковые сэмплы. Для этого нужно записать в компьютере звук (моно!) в формате WAV, и обработать его в любом звуковом редакторе, который позволяет регулировать битрейт и глубину оцифровки (например, Sound Forge). Исходным материалом может быть как ваш собственный голос, записанный через микрофон, так и готовый звуковой клип. Формат WAV — чистый оцифрованный звук, и его можно напрямую перекачивать в нашу память. Проще всего для этого воспользоваться каким-нибудь универсальным программатором, но несложно модифицировать данную программу так, чтобы МК сам мог записывать клипы из компьютера через UART. Все необходимые сведения для создания такой программы в этой книге есть.
Аналоговая индикация может быть во многих случаях более естественным методом для создания человеко-машинных интерфейсов, чем цифровая. Как я уже указывал в главе 10, большинство показывающих приборов на пультах управления сложными системами имеют стрелочные или шкальные индикаторы, т. к. точное значение некоего параметра человека интересует не так уж часто. Это касается даже часов: модели со стрелками не есть просто дань стилю «ретро», в некоторых ситуациях (например, когда вы кого-то ждете), они удобнее, чем с цифровым индикатором. Все определяется задачей: от медицинского термометра мы ждем точного значения температуры, от датчика температуры двигателя — лишь оценки относительно некоего порога. Вот когда нужна такая оценка, аналоговый индикатор окажется лучше цифрового.
Существуют готовые микросхемы для управления шкальными индикаторами. Они представляют собой специализированные дешифраторы. Например, К155ИД11[18] при подключении 8 светодиодов, расположенных в ряд, формирует светящийся столбик, высота которого соответствует трехразрядному входному двоичному коду. Для каскадного включения таких микросхем предусмотрены дополнительные входы и выходы (разрешения и переноса), потому с их помощью можно создавать и более длинные шкалы. На практике для большинства применений достаточно 16 градаций.
Более современный аналог К1003ПП1 (UAA180) менее удобен, т. к. управляет 12 светодиодами — ни то ни се (24 при каскадном включении двух микросхем — много, 12 при одной микросхеме— мало). Впрочем, никто не мешает вам задействовать только часть разрядов, да и индикация с 24 разрядами, а то и более, тоже иногда требуется. Отметим, что К1003ПП2, которая вроде бы управляет 16 светодиодами, для практических целей не годится, т. к. высвечивает не столбик, а только один из светодиодов в линейке, что и некрасиво, и малоинформативно. Разумеется, есть и другие подобные микросхемы, но мы не будем на этом задерживаться.
Заметки на полях
Учтите, что приобрести готовую прилично выглядящую шкалу, представляющую собой линейку LED, крайне непросто. Основная проблема состоит в том, что близкорасположенные светодиоды засвечивают друг друга, и в стандартном технологическом процессе их приходится разделять промежутком, чтобы компаунд затекал в зазоры достаточно надежно. Выглядит такая шкала безобразно: лучше уж взять матричный индикатор и сформировать шкалу на его основе, хотя это усложняет управление и тоже не совсем то, что требуется. Наилучший способ — сформировать линейку из плоских светодиодов, вручную окрасить их боковые грани и затем установить на плату. Светодиоды обязательно должны быть с диффузным (матовым) рассеивателем, иначе равномерной засветки вы не добьетесь. В идеале их следует также отбирать по яркости свечения, в противном случае шкала будет неравномерной, хотя на практике это довольно сложно осуществить. Для окраски граней обойтись краской из баллончика не удастся, т. к. она будет просвечивать, особенно на ребрах, даже при покрытии в несколько слоев. Потому необходимо взять обычную темную и достаточно густую нитрокраску (можно даже дать постоять ей на воздухе, чтобы дополнительно загустела) и окрасить весь светодиод методом окунания. Когда краска полностью засохнет (высохнув, она значительно уменьшится в объеме), аккуратно сошлифуйте мелкой шкуркой ее с торца светодиода.
Большинство проблем, связанных со шкальной индикацией, с помощью К155ИД11 или ее многочисленных аналогов решить можно. Причем следует учесть, что на самом деле 8-разрядная линейка индикаторов имеет не 8, а 9 состояний (восемь зажженных LED плюс состояние, когда все погашены). На выходе К155ИД11, в частности, число зажженных LED на единицу больше входного кода: когда на входе 000, горит один, самый первый светодиод шкалы. Чтобы его тоже погасить, надо подать отдельно сигнал логического нуля на вход переноса (тогда микросхема перестает реагировать на входной код вообще). Поэтому иногда проще уменьшить число ступенек шкалы до 7 (а для 16-разрядного индикатора — до 15). Аналогичная проблема с числом состояний касается всех дешифраторов подобного рода.
При этом придется заняться компрессией, т. к. обычные АЦП все же имеют разрядность много большую, чем требуется в этом случае. Компрессию совершить элементарно просто: достаточно сдвинуть исходное число на столько разрядов вправо, сколько нужно, чтобы результат содержал четыре (для шкалы с 16 состояниями) или три (для 8 состояний) разряда. Предварительно надо позаботиться, чтобы исходное число занимало всю нужную шкалу, т. с. подогнать масштаб.
Приведем пример того, как можно подключить шкальный индикатор к нашему измерителю с помощью микросхемы К155ИД11. Нашей целью будет индикация атмосферного давления в расчете на шкалу с 15 градациями (16 состояниями) в диапазоне от 710 до 785 мм рт. ст. (т. е. по 5 мм рт. ст. на одну градацию шкалы). При этом состоянию 710 и менее должны соответствовать все погашенные LED, от 711 до 715 — один горящий, от 716 до 720 — два горящих и т. д.