Глава 19 Практические схемы на AVR

… и телефонный аппарат (клубок катодов, спаек, клемм, сопротивлений) безмолвствует.

И. Бродский «Посвящается Ялте»

Возможности МК серии AVR весьма велики, вплоть до того, что на старших моделях можно выстраивать полноценные системы управления, например, жидкокристаллическими дисплеями. Среди продукции Atmel есть контроллер USB на AVR-ядре, и с его помощью можно делать настоящие USB-устройства, не прибегая к эмуляции СОМ-порта. Но и младшие модели AVR вполне пригодны для серьезных вещей, достаточно упомянуть, что такое устройство, как компьютерная клавиатура, изначально была спроектирована на куда менее мощном, чем почти любой современный AVR, контроллере 8048.

Здесь мы рассмотрим способы построения некоторых типовых узлов с помощью AVR, не задаваясь вопросом конструирования законченных приборов. Довести до ума и встроить в необходимые вам устройства эти узлы вы сможете сами, я лишь покажу, как это делается в принципе. И начнем мы с самого, пожалуй, интересного — попробуем заставить МК воспроизводить цифровой звук.

Заставить камни заговорить

На самом деле способность воспроизводить звук встроена во все МК AVR изначально. Для этого надо лишь иметь «исходник»— ранее записанный звуковой файл с определенными параметрами. Такое устройство можно использовать, как узел голосовой сигнализации — например, если укомплектовать им наш измеритель с часами, и он сможет вслух сообщать время и температуру. Для этого придется сделать немного— разделить в памяти звуковые сэмплы, произносящие различные слова, и комбинировать их по ходу дела в нужном порядке. Именно так работают системы автоматического оповещения, например, о задолженности по телефонным счетам. Здесь мы в подробности влезать не будем, а покажем, как вообще организовать на AVR режим воспроизведения цифрового звука.

Что такое цифровой звук, вы уже знаете из главы 10— это последовательность отсчетов сигнала с определенной частотой (называемой частотой дискретизации или частотой оцифровки, битрейтом) и с определенным разрешением по напряжению (квантованием). Сначала давайте поймем, как в принципе можно воспроизводить такой звук.

Предположим, что мы имеем в качестве источника набор байтов, напрямую представляющий исходную оцифрованную последовательность (сжатые форматы мы не рассматриваем, поскольку это увело бы нас далеко за рамки темы книги). «Лобовой» метод понятен из той же главы 10: взять ЦАП, подать ему на вход оцифрованный звук с той же частотой, с которой его оцифровывали, а к его выходу подключить фильтр, усилитель и динамик. Но вот, например, производители сотовых телефонов просто замучили своей «полифонией»: сидя в маршрутке, невольно вздрагиваешь, когда у соседки в сумочке вдруг то ребенок заплачет, то котенок замяукает. Не многие, однако, задавались вопросом — а как это делается? Неужели в мобильник встраивают настоящий цифровой тракт, со всеми этими ЦАПами, фильтрами и усилителями? Вовсе нет. Главная идея, которая заложена в простой реализации воспроизведения цифрового звука, называется «усилитель в режиме D», от слова digital — цифровой.

В усилителях класса D цифровой звук вообще не переводится в аналоговую форму какими-то специальными устройствами. Наоборот, там обычный звук представляется в виде последовательности прямоугольных импульсов, пропорциональных по длительности интенсивности сигнала. Для усиления таких импульсов не нужно никаких ухищрений — чаще всего используют комплементарную пару транзисторов, подобно тому, как усиливается сигнал на выходе логических КМОП-микросхем. Выгода заключается в том, что теоретически КПД такого импульсного усилителя может быть равен 100 %, ведь какой-то из транзисторов пары всегда заперт, а второй транзистор в это время полностью открыт и мощности на них не выделяется. Это, конечно, в теории, потому что из главы 3 вы знаете, что падение напряжения на открытом транзисторе все же имеет место, да и переключение не происходит мгновенно. Но вопросы КПД нас тут не интересуют, т. к. мы не собираемся конструировать 100-ваттные усилители, и указанная схема нас привлекает не столько КПД, сколько простотой и компактностью.

Представление синусоидального сигнала в виде последовательности импульсов различной длительности называется ШИМ-модуляцией (по-английски, PWM — Pulse-Wide Modulation). Фокус заключается в том, что для извлечения исходного синусоидального сигнала из ШИМ не требуется никаких специальных сложных приборов — достаточно обычного резисторно-конденсаторного ФНЧ с подходящей частотой среза (о ФНЧ см. главу 2). В результате весь звуковой тракт упрощается до предела (рис. 19.1).

Рис. 19.1. Принцип работы выходной части усилителя в режиме D

Заметки на полях

А как сформировать входной сигнал для такого усилителя, если у нас в наличии имеется лишь аналоговая звуковая волна? Нужно ли ее оцифровывать? Совсем нет: исходный аналоговый сигнал поступает на один вход компаратора, а на второй его вход подается напряжение треугольной формы и подходящей амплитуды. Тогда на выходе компаратора мы получим ШИМ-сигнал. Работа счетчика-таймера, показанная на рис. 19.2 далее, делает в точности то же самое, но в цифровой форме.

Для того чтобы получить ШИМ-сигнал из уже оцифрованного звука, у нас есть такая «штука», как микроконтроллер, причем уже специально приспособленный для подобных целей. Если вы уже имеете книгу [1] или [2], или скачивали фирменный PDF-документ с описанием какого-то из контроллеров AVR, и при этом ваше любопытство зашло столь далеко, что вы эти источники даже немного пролистали, то, несомненно, заметили, что в описаниях таймеров PWM-режиму уделяется довольно много места — больше, чем всем остальным режимам вместе взятым. Это потому, что PWM-режим сложнее простого счета. Но на самом деле идея, которая в него заложена, очень проста: мы загружаем в регистр сравнения очередное число, взятое из звуковой последовательности, и запускаем таймер на счет с нуля, а когда он дойдет до верхнего предела, то сразу реверсируется и начинает считать обратно до нуля. В контроллерах Tuny вместо реверсирования счетчик сбрасывают и начинают отсчет заново. В семействе Mega для формирования сигнала PWM есть и тот, и другой и еще некоторые режимы работы таймеров (например, с переменным битрейтом).

В момент, когда числа в счетчике таймера и в регистре сравнения равны между собой, в режиме PWM автоматически переключается знакомый нам выход, связанный с выбранным таймером (в главе 14 это был выход ОС1, который управлял миганием двоеточия). Только в данном случае он не переключается туда-сюда с каждым прерыванием от таймера, а находится в состоянии логического нуля, когда число в таймере больше, чем в регистре сравнения, и в состоянии логической единицы — когда меньше. В результате на один цикл счета «туда-обратно» мы получаем один период ШИМ-сигнала, в котором длительность состояния логической единицы строго пропорциональна числу в регистре сравнения. Меняя к следующему циклу это число на очередную выборку из звуковой последовательности, мы в результате получаем то, что требовалось: входной импульсный сигнал для усилителя в режиме D. Общая схема процесса показана на рис. 19.2 (на примере с использованием Timer 1). Кстати, отметим, что этот режим может применяться также, например, просто для формирования сигнала с определенной скважностью, не равной двум.

Рис. 19.2. Принцип работы счетчика-таймера в режиме PWM

Теперь надо понять, какие характеристики исходного оцифрованного сигнала нам нужны и какие параметры таймера необходимо устанавливать. Хотя мы будем использовать Timer 1, но задействовать все 16 разрядов в таком режиме он не может (счет в реверсивном режиме возможен максимум с 10 разрядами, а использовать режимы с переменной разрядностью мы не будем). Нам же будет достаточно и 8 — это означает, что глубина квантования исходного звука должна быть также 8 разрядов. Баха не очень сыграешь, но для передачи разборчивой речи достаточно.

Теперь разберемся с частотой оцифровки. Тактовую частоту МК для такой схемы лучше выбирать максимально возможной, для большинства AVR это 16 МГц (чтобы еще повысить качество звука, можно специально взять модель 2313, у которой максимальная частота 20 МГц, но мы будем ориентироваться на 16 МГц). Легко подсчитать, что реверсивный 8-разрядный счетчик будет считать туда и обратно с частотой fтакт/510, т. е. при такой тактовой частоте получится около 32 кГц. Это и будет несущая частота fоп на выходе ШИМ, что удовлетворительно, т. к. она выходит за пределы слышимого диапазона. Однако требуемая частота оцифровки исходного звука может быть все же заметно ниже (что удобно в целях экономии памяти). Пусть она составляет 4 кГц (это может возмутить аудиофилов, но для передачи речи это нормальный показатель).

Тогда можно сразу выбрать характеристики RC-фильтра: чтобы отфильтровать 32 кГц простой RC-цепочкой, нам желательно, чтобы частота среза не превышала частоту оцифровки, т. е. 4 кГц. Тогда 32 кГц затухнут в 8 раз по сравнению с верхней частотой диапазона оцифровки, и мы их влияние не почувствуем. Параметры фильтра рассчитываются по формуле fcp = 1/2π, и нашим требованиям удовлетворяют параметры R = 10 кОм и С = 3,9 нФ.

Для хранения звука используем память с I2С-интерфейсом АТ24С512. Одному отсчету тут будет соответствовать ровно один байт, одной секунде звучания — 4 кбайт. Итого 65 536 байт дадут нам около 16 с звучания. Этого достаточно, чтобы произнести стандартную предвыборную речь кандидата в президенты, если предварительно ее отредактировать и выбросить все фразы, не несущие смысловой нагрузки.

Все параметры схемы мы рассчитали, можно приступать к проектированию. В качестве звукового усилителя возьмем описанный в главе 6 микроусилитель МС34119. Выбор усилителя не имеет большого значения, но данная микросхема «умеет» работать с однополярным напряжением 5 В и это удобно. Общая схема соединений показана на рис. 19.3. Полную схему включения МС34119 см. на рис. 6.15.

Рис. 19.3. Принципиальная схема для использования AVR в режиме голосовой сигнализации

Программа для вывода звука

Здесь мы для простоты выберем АТ9 °C8515 семейства Classic (а точнее, ATmega8515 в режиме совместимости с АТ9 °C8515, потому что оригинал может работать максимум на 8 МГц тактовой частоты, а мы рассчитывали на 16 МГц). Это проще для нашего рассмотрения, поскольку в семействе Classic имеется лишь один режим PWM для таймеров, а в Mega их много и это лишь путает. В крайнем случае, разобраться в том, как дополнить программу выбором нужного нам в данном случае режима Phase Correct PWM вы сможете самостоятельно. Для работы с интерфейсом I2С в программе используется тот же самый, что и в главе 16, включаемый файл i2c.prg (см. Приложение 5, листинг П5.3).

В целях компактности из него можно для данного случая удалить процедуры, относящиеся к RTC (write_i2c и read_i2c), но будьте осторожны, чтобы не удалить что-то нужное. Кроме того, следует обратить внимание на величину задержки в процедуре delay — там у нас установлена величина 5 мкс в расчете на 4 МГц. При 16 МГц задержка укоротится вчетверо, и память будет работать на пределе (400 кГц), что не очень хорошо. Хотя обычно (если линия передачи не слишком длинная) память справляется, но помнить об этом параметре в случае возникновения каких-то сбоев следует. Но и снижать скорость шины до тех величин, что у нас были в измерителе не следует, т. к. чтение может не успеть за битрейтом (см. далее).

Программа для данного случая содержится в листинге 19.1.

Листинг 19.1

/==== программа вывода цифрового звука ====

;процессор mеgа8515 в режиме 8515, частота 16 МГц

.include "853 5def.inc"

.equ T_Sample = 193  ;предварительное значение для Timer 0 при 4 кГц

.equ bSample = 0  ;бит готовности к чтению

;регистры temp и DATA определены в "I2С.рrg" (Приложение 5)

.def FLAGS = r19

; прерывания

rjmp RESET  ;начальный загрузчик

reti

reti

reti

reti

reti

reti

rjmp TIMO  ;обработчик прерывания переполнения Timer 0

reti

reti

reti

reti

.include "I2C.prg"

RESET:

            ldi temp,0b00100000

            out DDRD,temp  ;ОС1А — на выход

            ldi temp,(1<<C0M1A1)|(1<<PWM10)  ;инициализация PWM

            out TCCR1A,temp

            ldi temp,1<<CS10  ;включаем Timer1, 1/1

            out TCCR1B,temp

            ldi temp,(1<<CS01)|(1<<CS11)  ;включаем Timer0, 1/64

            out TCCR0,temp

            out TCNT0,T_Sample  ;T_Sample=6, заряжаем таймер 0 на 4 кГц

            ldi temp,(1<<TOIE0)  ;разрешаем прерывание Timer0

            out TIMSK,temp

            clr temp  ;очищаем все регистры

            out OCR1AH,temp

            out OCR1AL,temp

            out OCR1BH,temp

            out OCR1BL,temp

            ldi XH,high(Nbytes)

            ldi XL,low(Nbytes)  ;зарядка счетчика выводимых байт

            ;вместо Nbytes подставить объем записи в байтах, не более 64К

            ldi YH,high(ADrWord)

            ldi YL,low(ADrWord)

            ;вместо 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<<bSample  ;сбрасываем бит готовности к чтению

            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<<bSample  ;бит готовности к чтению — в 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 — два горящих и т. д.

Схема на рис. 19.4 составлена в предположении, что от цифровой индикации мы отказались, и порт С, занятый ранее сегментами, у нас освободился. Младшие разряды этого порта мы и задействуем для управления линейкой светодиодов. Остальные соединения на схеме не показаны (см. рис. 15.2). Ради простоты индикацию температуры опустим. Вывод Е (разрешения) микросхем управляется старшим разрядом четырехбитового числа с вывода РСЗ. Логика К155ИД11 такова, что если на Е уровень логического нуля, то работа микросхемы DD2 запрещена, если «1»— запрещается работа микросхемы DD3. Так как во втором случае на выходе Р верхней микросхемы уровень логического нуля, то нижняя микросхема зажжет все светодиоды. Ограничительных резисторов для светодиодов не требуется, они встроены в микросхему, хотя яркость в этом случае может быть непредсказуемой (а вот К1003ПП1 «умеет», в том числе, управлять яркостью).

Рис. 19.4. Схема шкальной индикации для измерителя давления и температуры

Программу придется переделать таким образом. Расчеты физических величин нам уже не требуются, но вот преобразование масштабов провести придется. Потому на подготовительном этапе нужно выяснить значение коэффициентов К и Z таких, чтобы по уравнению зависимости выходного кода от значений, прочитанных из АЦП, приведенному в главе 15, у нас значение выходного кода, равного нулю, соответствовало 710 мм рт. ст. Таким образом, коэффициент Z, который нужно вычесть из кода АЦП, будет равен значению кода при 710 мм рт. ст., или, как несложно рассчитать, примерно 840 (с учетом того, что датчик работает не с нуля давления, см. главу 15).

Значение же, соответствующее 785 мм рт. ст., должно соответствовать какому-нибудь «круглому» двоичному числу (не очень важно, какому, т. к. мы потом его все равно урежем до 4 бит). Из характеристик датчика мы знаем, что максимальная шкала АЦП в 10 бит соответствует давлению около 850 мм рт. ст. Нас же интересует шкала всего в 75 мм (от 710 до 785), что составит около 90 единиц кода. Потому мы смело можем выбрать, например, 128 для верхнего предела шкалы (что соответствует 7 битам). Тогда коэффициент К (который ранее составлял 0,895 мм рт. ст. на единицу кода), теперь будет примерно 128/90 = 1,422. Оба коэффициента, естественно, должны уточняться при калибровке.

Сама процедура расчета не нуждается в переделке (меняются только значения коэффициентов, остальное можно оставить, как есть, хотя если внимательно ее рассмотрите, то увидите, что есть резервы для сокращения необходимых ресурсов, например, задействованных регистров). Единственное, что следует учесть— вычисленные значения хранятся в регистрах AregH: AregL (см. Приложение 5), но 7-битовый результат, конечно окажется только в регистре AregL, а регистр АregH всегда будет равен нулю. После расчета, вместо преобразования В двоично-десятичный КОД (rcall bin2BCD16), мы должны записать:

lsr AregL

lsr AregL

lsr AregL  ;теперь результат усечен до 4 бит

in temp.PortC  ;значение разрядов PortC в temp

cbr temp,15  ;обнуляем младшие 4 бита

ori temp,AregL  ;устанавливаем младшие 4 бита

out PortC,temp  ;выводим

Здесь операции очистки младших битов и сохранения старших требуются для того, чтобы не вмешиваться в процессы, которыми (предположительно) могут управлять старшие биты порта С. В остальном программу читатель может доделать самостоятельно — она упростится, даже если ввести еще индикацию температуры. При полном отказе от цифровой индикации исчезнет необходимость в громоздкой процедуре управления разрядами (индикация становится статической), и также сократится число необходимых ячеек в SRAM — ясно, что хранить четырех- и даже восьмибитовые значения величин ни к чему. А в физические величины, если потребуется, исходные значения параметров можно пересчитать и в компьютере, сохраняя их в энергонезависимой памяти.

В заключение отмечу, что в сконструированном мной однажды приборе для некоей яхты была задействована более сложная и более красивая шкальная индикация: при значении параметра ниже некоторого порога индикаторы меняли цвет на красный, который сменялся зеленым при повышении параметра до нормы. Естественно, готовые микросхемы (типа К155ИД11 и подобные) такую задачу решить не позволяют, и пришлось городить всю процедуру внутри контроллера, управляя светодиодами напрямую. Задачу изменения цвета удалось решить, применив двухвыводные двухцветные LED (L117), которые обоими выводами коммутировались к выводам МК (для одного цвета действовало сочетание состояний 0 и 1, для другого — 1 и 0, а погашен LED оказывался при одинаковом состоянии выводов).

Подстройка внешних часов

Кварцевые резонаторы имеют весьма высокую точность: ±1 с в сутки для электронных часов равносильны, например, ошибке в 1 м на 86 км при измерении длины. Но, не говоря уж о специальных технических применениях, такой точности при измерении времени недостаточно даже в быту— часы с такой ошибкой будут уходить на полминуты в месяц и их придется подводить каждые два-три месяца, как минимум. Если для стрелочных часов такая процедура ничего сложного не представляет, то для электронных она выливается в довольно занудные манипуляции с кнопками или необходимость (как в нашем измерителе с часами) коррекции времени через подключение к компьютеру.

Манипуляций хочется избежать, а иногда и просто необходимо обеспечить ход часов более точно. Как это сделать? Как мы видели в главе 14, 16-разрядный таймер при отсчете секунд дает достаточно грубую подстройку того же порядка, что и ошибка, т. е. для начала надо придумать способ более тонкой подстройки. Кроме того, когда речь идет о внешних часах, то мы вообще не можем вмешаться в процесс счета времени. Значит, кроме самой поправки, необходимо придумать еще механизм того, как ее применять к внешним часам. Все эти проблемы можно решить, например, следующим способом.

Для начала о самой поправке: можно, конечно, попросту ввести задержку вроде той, что служит для формирования сигналов I2С (собственно, так и отсчитывали время в контроллерах, когда в них еще не было таймеров). Но это не очень хорошо по той же причине, по которой такие паузы лучше не использовать в Windows, где много процессов протекают параллельно. Либо это задержит весь контроллер, либо (если прерывания разрешены) задержка может быть непредсказуемой длительности. Потому лучше задействовать таймер (скажем, Timer 2, который имеется во всех Mega, a Timer 1 оставим для других надобностей), только запускать его так, чтобы он формировал прерывания достаточно короткой длительности. Например, ежесуточная коррекция ступеньками по 10 мс нас будет вполне устраивать (при точном подборе задержки ошибка составит не более 1 с в три месяца, если не учитывать «гуляние» частоты кварца самой по себе).

Самое сложное здесь — понять, как автоматически ввести коррекцию в обе стороны (и когда часы спешат, и когда отстают). Я придумал следующий механизм: мы задаем некий коэффициент коррекции в виде байта Кккор. Если этот байт равен $FF (расчет на то, что именно такое значение содержится в чистой EEPROM), то коррекцию вообще не проводим, или загружаем значение по умолчанию (как и с коэффициентами).

Если часы спешат, то значение Кккор должно находиться в пределах 1—127 (старший бит равен нулю), и оно определяет задержку, которую мы проводим с помощью таймера, настроенного на 10 мс, раз в сутки. Для этого надо остановить часы, пропустить столько циклов таймера, сколько задано в Кккор, и запустить их заново с того же значения. Максимальная задержка составит 1,27 с в сутки, что намного перекрывает возможную ошибку кварца.

Сложнее, если часы отстают. Чтобы отличить эту ситуацию от предыдущей, мы задаем старший бит Кккор равным единице, но в пределах 155–254 (т. е. коррекция возможна в пределах 1 с). Почему именно в этих пределах, мы сейчас поймем. Величина задержки программой будет вычисляться, как разность между 255 и Кккор (задержка, равная нулю, не имеет смысла, отсюда максимальное значение 254). В МК мы останавливаем часы, включаем таймер, делаем столько сравнений, сколько задано этой разностью, затем устанавливаем секунды = 1 и запускаем часы опять (теперь они идут немного вперед). Отсюда понятно, почему мы не можем задать Кккор меньше 155 — при таком алгоритме значение, к примеру, 128 привело бы к еще большему отставанию. Следовательно, чем больше задержка (разность между 255 и Кккор), тем меньше коррекция в данном случае: если нам нужно коррекцию свести к нулю, то следует задать значение Кккор = 155. Отметим, что остановка и пуск часов сами по себе займут время порядка миллисекунд, но ошибка будет невелика, и ее можно не учитывать.

Для коррекции задействуем Timer 2 с коэффициентом 1:256, получаем на выходе 15 625 Гц (при тактовой частоте 4 МГц). Если использовать прерывание по сравнению с величиной 156, получаем примерно 100 Гц (или 10 мс). Кккор мы будем хранить в EEPROM (по адресу KоrrEE), и переписывать его в RAM (по адресу KorrRAM) — эти адреса выбираются из любых свободных. Процедура для инициализации регистра сравнения таймера и коэффициента коррекции приведена в листинге 19.2 (в секторе начальной загрузки, о самой программе см. главу 16).

Листинг 19.2

;команду ldi temp,(1<<TOIEO) заменяем на

          ldi temp, (1«TOIE0)|(1<<ОСIЕ2)

;добавляем:

          ldi temp,156

            out OCR2,temp  ;получаем 100 Гц Timer2

;коэффициент коррекции ============

          clr ZH;addr eepr

          ldi ZL,KorrEE

          rcall ReadEEP

          cpi temp,$FF

          brne corr_K

          ldi temp,100  ;если = FF, то по умолчанию 100

corr_K:

            ldi ZH,1

            ldi ZL,KorrRAM

            st Z,temp

Величина задержки по умолчанию определяется на основе предварительных изысканий по уходу конкретного кварца (здесь часы спешили примерно на 1 с в сутки). Не забудем заменить в четвертой сверху строке таблицы прерываний reti на rjmp TIM2_comp. Отведем для счета прерываний регистр count_msek (пусть будет r12). Бит 4 регистра Flag будет сигнализировать о том, отстают часы или спешат (если отстают, то бит установлен). Значение этого бита и регистра count_msek будем устанавливать в момент, когда будет вызываться процедура коррекции (см. далее), там же часы останавливаются. Сам обработчик приведен в листинге 19.3.

Листинг 19.3

ТIМ2_СОМР:

            dec count_msek

            brne end_t  ;если еще не 0, то на выход

            sbrs Flag,4

            rjmp k_minus  ;если спешат, то пропустить если отстают

            cbr Flag,8  ;очищаем бит к следующему разу; устанавливаем часы на 01 сек. и заводим их

            ldi temp,1

            rcall IniSek

            ldi count_sek,1  ;меняем значение в регистре секунд

            ldi ZH,1

            ldi ZL,Sek

            st Z,count_sek

            rjmp end_korr

k_minus:

;если спешат, просто заводим часы обратно

            clr temp

            rcall IniSek

end_t:

reti  ;конец прерывания коррекции

Теперь собственно процедура вызова коррекции: будем выполнять ее в полночь. Она довольно громоздкая, потому что приходится определять момент, когда полночь настала. Мы можем вклинить ее туда, где часы проверяются на кратность трем (0 минут и 0 секунд нам обеспечены). Однако сразу после этого производится запись во flash (а иногда и не производится), и она нам будет непредсказуемо тормозить коррекцию. Потому будем ее проводить в одну минуту первого (00:01:00). Сразу после метки sek_0 вписываем фрагмент кода, содержащийся в листинге 19.4.

Листинг 19.4

sek_0:

          ldi ZL,1  ;загружаем минуты

          ld temp,Z+;

          cpi temp,1  ;сравниваем минуты, если 1 — коррекция

          breq min_1

          cpi temp,0  ;сравниваем минуты, если 0 — запись

          breq mm0  ;на проверку трехчасового цикла

          reti  ;иначе выходим

min_1:

          ld temp,Z  ;загружаем часы

          cpi temp,0  ;сравниваем часы = 0

          breq hour_0  ;если равны 0, то на коррекцию

          reti  ;иначе выходим

hour_0:

         ldi ZL,KorrRAM  ;коэффициент коррекции Id

         count_msek,Z  ;в счетчик

         ldi temp,128

         ср count_msek,temp  ;определяем его величину

         brlo k_plus

         com count_msek  ;если больше 127, то вычитаем из 255

         sbr Flag,8  ;значит отстают

k_plus:  ;если меньше — часы отстают

         ldi temp,$80

         rcall IniSek  ;останавливаем часы

         ldi temp,0Ь00000110  ;заводим таймер

         out TCCR2,temp  ;Timer2 1:256

reti

mm:  ;далее по тексту программы

Разумеется, если вы используете сторожевой таймер, то его следует останавливать на время проведения этой операции (и запускать в прерывании Timer 2 по окончании коррекции.) В правильно организованной программе, кроме того, необходимо также запрещать за некоторое время до наступления момента коррекции длинные процедуры с запрещением прерываний (вроде чтения данных из Flash), иначе коррекцию можно пропустить.

Измерение частоты

Частоту можно измерять, как известно, двумя способами: либо подсчетом числа импульсов измеряемой частоты за определенный промежуток времени, либо, наоборот, подсчетом числа импульсов известной частоты за период (или несколько периодов) измеряемого сигнала. В первом случае мы получаем именно значение частоты (если промежуток времени равен 1 с, то сразу в герцах), а во втором — обратную величину, значение периода. Первый способ удобнее для измерения высоких частот, второй — низких.

С помощью контроллеров МК частоту можно измерять несколькими путями. В том числе есть специальный режим работы таймеров с «захватом» (capture) внешнего перепада уровней и генерацией прерывания по этому поводу. Он удобен для измерения периода низких (с точки зрения МК) частот по второму способу. Здесь я покажу метод прямого измерения (по способу подсчета импульсов) достаточно высокой частоты, причем с подстройкой измерительного интервала для получения более точного результата прямо в физических величинах — герцах.

Предположим, измеряемая частота находится в диапазоне до 4 МГц, и нам желательно измерить ее с разрешением до 1 Гц. Прежде всего отметим, что тактовая частота контроллера должна превышать измеряемую не менее, чем в два раза — таково требование руководства. Обнаружение изменения внешнего сигнала производится по фронту тактового, и если период измеряемого сигнала слишком короткий, то в регистрации могут быть пропуски.

Замечание

Отметим, что перепад уровней внешнего сигнала регистрирует автономная асинхронная схема. Потому из изложенного в руководстве не следует, что тактовая частота должна быть выше измеряемой именно в два раза — достаточно простого превышения. И по моему опыту AVR прекрасно измеряют частоту всего в полтора раза ниже тактовой. Однако окончательное суждение по этому вопросу я оставляю на усмотрение читателей — изложение в руководстве не очень толковое (не до конца прояснен вопрос с задержками регистрации фронта сигнала), и, конечно, лучше «на всякий случай» следовать фирменным рекомендациям.

Так что нам потребуется МК с тактовой частотой не менее 8 МГц. Выберем АТ9 °C2313 (Classic— при необходимости легко модифицировать алгоритм к любому AVR) с частотой 8 МГц. Для измерения мы используем два таймера— один 16-разрядный (Timer 1) для отсчета собственно внешней частоты, и второй (Timer 0) для отсчета измерительного интервала.

Заметки на полях

Результат измерения частоты 4 МГц с точностью до герца в принципе займет не менее трех 8-битовых регистров. Но реальное их число, которое требуется задействовать, будет зависеть от нижнего предела измеряемой частоты. В самом деле, предположим, что частота может меняться не более чем на 256 Гц. Тогда старшие два регистра всегда будут показывать одно и то же число (и точно известно, какое), а все изменения будут регистрироваться только в самом младшем регистре счетчика. Если же частота 4 МГц не меняется более чем на 65 кГц, то можно оставить только два регистра (собственно таймера). Здесь важно только, чтобы в процессе изменений частота не «переваливала» за границу, когда старший регистр тоже меняется (что в нашем случае произойдет, например, если средняя частота колеблется около значения 222 = 4 194 304), иначе возникнет неоднозначность (которую, впрочем, также в некоторых случаях можно учесть). Но мы не будем в этот вопрос углубляться, а тупо предположим, что частота в пределах емкости трех регистров (т. е. с большим запасом — до 16,7 МГц) может быть любой.

Для измерения нам потребуется ввести прерывание Timer 1 по переполнению, в котором третий регистр (назовем его count3) будет всякий раз увеличиваться на единицу. Входной сигнал подадим на вход Т1 (вывод 9 для 2313), с которого внешние импульсы поступают прямо на счетчик таймера, если ему задать соответствующий режим.

Теперь разберемся с формированием измерительного интервала. При 8 МГц тактовой частоты и коэффициенте предделителя для Timer 0, равном 1/256, прерывания будут происходить с частотой 122,07 Гц. Нам же требуется 1 с (1 Гц), потому мы введем счетчик (count_sek) и будем его с каждым прерыванием увеличивать, пока он не отсчитает ровно 122 таких прерывания. После этого можно фиксировать число импульсов, сосчитанное к тому времени в регистрах Timer 1. Но если кварцевый резонатор идеально точный, то секунда получится чуть меньше настоящей (неучтенные 0,07 Гц дадут ошибку 576 мкс со знаком «минус»), и перед чтением значений мы введем задержку для компенсации этой недостачи, с помощью которой наш частотомер можно еще дополнительно калибровать (т. е. учесть исходную неточность кварца). В начале и в конце интервала будем переключать разряд 6 порта D (вывод 11), чтобы контролировать измерительный интервал в процессе калибровки. На рис. 19.5 представлен МК AT90S2313 с обозначением выводов для нашей цели.

Рис. 19.5. Подключение AT90S2313 для измерения частоты

Программа частотомера приведена в листинге 19.5 (определение регистров я опускаю, в данном случае их потребуется всего три — temp, count3 и count_sek). В секции прерываний введем прерывания Timer 0 и Timer 1 по переполнению (по меткам TIM0 и TIM1). Инициализация таймеров в секции начальной загрузки сводится к разрешению прерываний, но обязательно здесь же нужно очистить счетные регистры таймеров и все дополнительные

Листинг 19.5

ldi temp,(1<<TOIE0)|(1<<ТO1Е1)  ;разр. прер. Timer0 и Timerl

out TIMSK,temp

clr temp

out TCNT1H,temp

out TCNT1L,temp  ;очищаем Timer1

out TCNT0,temp  ;очищаем Timer0

clr count3

clr count_sek  ;очищаем счетчик прерываний

ldi temp,0b00000100;

out TCCR0,temp  ;запускаем Timer0 div 1:256

Прерывание Timer 1 будет очень простое (листинг 19.6).

Листинг 19.6

TIM1:

    inc count3

reti

Теперь рассмотрим самое главное прерывание, Timer 0 (листинг 19.7).

Листинг 19.7

TIM0:  ;таймер 122,07 Гц

          inc count_sek

          cpi count_sek,122  ;получаем 0.999424 с

          breq corr_1  ;если секунда прошла, то на коррекцию счета

          cpi count_sek,1

          brne corr_1  ;в самом первом цикле запускаем Timer 1:

          ldi temp,0b00000111  ;внешний сигнал Т1 (выв. 9) по фронту

          out TCCR1B,temp  ;запускаем Timer1

corr_1:  ;1 сек + коррекция

          clr temp

          out TCCR0,temp  ;останавливаем Timer0

          ;задержка на -600 мкс для коррекции интервала

          ldi ZH,high(1200)  ;8 МГц, цикл 4 такта

          ldi ZL,low(1200)

loop:

          sbiw ZL,1

             brne loop

;переключение контр. выв 11 PinD6 период 1 с

             sbis PinD,6

             rjmp set_1

             cbi PortD,6

             rjmp Set_0

Set_1:

             sbi PortD,6

Set_0:

             clr temp

             out TCCR1B,temp  ;останавливаем Timer1

;читаем данные

             in temp,TCNT1L

             <пишем младший байт в память>

             in temp,TCNT1H

             <пишем второй байт в память>

             <пишем старший байт count3 в память>

;очищаем все регистры

             clr temp out TCNT1H,temp

             out TCNT1L,temp  ;очищаем Timer1

             out TCNT0,temp  ;очищаем Timer0

             clr count3

             clr count_sek  ;очищаем счетчик прерываний

;запускаем таймер 0 опять

             ldi temp,0Ь00000100;

             out TCCR0,temp  ;запускаем Timer0 div 1:256

reti

Обратите внимание, что читать данные из регистров таймера нужно в указанном порядке: сначала старший, потом младший, а записывать в обратном порядке. Это сделано специально: при чтении из старшего регистра TCNT1H данные из младшего TCNT1L одновременно переписываются в специальный регистр временного хранения, и ситуации, когда в промежутке между командами чтения младший разряд может измениться (при запущенном таймере), не возникает. То же самое, только в обратном порядке, происходит и при записи. Запись данных я не расшифровывал, потому что это может быть и запись в SRAM с последующим выводом на индикацию, и запись во внешнюю энергонезависимую память для последующего чтения из компьютера (или одновременно и то и другое). Простейший частотомер можно сделать, если организовать автоматическую передачу данных через UART в компьютер, который и занимается отображением и записью информации.

Из-за инструкции sbiw, которая занимает два такта (а не один, как инструкция dec, которую мы использовали в процедуре delay для интерфейса I2С, см. главу 16), здесь один цикл задержки равен четырем тактам, или 0,5 мкс при тактовой частоте 8 МГц. Меняя число циклов задержки, можно подстроить длительность секундного интервала в интервале от 576 мкс в сторону уменьшения (задержка равна нулю) до целых 131 мс в сторону увеличения. 576 мкс может показаться слишком маленьким значением, но этого достаточно для подстройки стандартного кварца, в крайнем случае, можно отобрать экземпляр из нескольких. Калибровка осуществляется измерением длительности импульса на контрольном вводе 11 МК с помощью точного частотомера (профессионального лабораторного прибора, а не любительского, который имеет недостаточную точность).

У AT90S2313 недостаточно выводов, чтобы напрямую обеспечить динамическую индикацию для нашей цели (7 десятичных разрядов), поэтому можно либо только использовать данные непосредственно, либо управлять разрядами через внешние дешифраторы (скажем, 561ИД5 позволяет управлять семисегментным индикатором четырехразрядным двоичным кодом с выводов PD0—PD3, а управление переключением семи и даже восьми разрядов можно тогда осуществить через полностью свободный порт В). Можно, конечно, выбрать другой контроллер. Динамическая индикация практически никак не помешает счету, так как таймер считает абсолютно независимо от остальных схем.

Частотомер получится довольно низкочастотный (если только не использовать внешние счетчики-делители с ущербом для разрешающей способности измерений). Но самой сложной проблемой для построения настоящего частотомера будет формирование из исходного сигнала произвольной формы последовательности «чистых» импульсов нужной формы и амплитуды.

Без подробного обсуждения привожу одну из возможных схем такого формирователя импульсов (рис. 19.6), работающего на частотах до 4 МГц. 554САЗ заменяется на 521САЗ (импортный аналог — LM311). Конденсатор 100 пФ служит для фильтрации высокочастотных помех и его емкость подбирается при регулировке. Предпочтителен более современный (и несколько более быстродействующий) LM6511, совпадающий с указанными по выводам. Переключатель полярности сигнала в принципе не требуется (его можно реализовать программно простым переключением режимов Timer 1), но таким образом можно сэкономить вывод МК, который пришлось бы подсоединять к переключателю.

Рис. 19.6. Формирователь входных импульсов для частотомера 0–4 МГц

Объединение систем на МК

Как я уже упоминал в главе 16, последовательный порт USART может работать в режиме мультипроцессорного обмена. Однако реализован он довольно сложно и малопригоден для организации такого часто встречающегося варианта, когда есть главный компьютер и несколько равноправных систем на МК, с которым хочется организовать двустороннюю связь через единый COM-порт. Сейчас мы разберем одну из возможностей организации такого обмена.

Обмен такого рода не может обойтись без присвоения индивидуального адреса устройству, т. к. их надо как-то различать. Все подобные протоколы (I2С хотя бы) различаются лишь способом доставки и форматом этого адреса. В нашем же случае придется еще придумать, как обеспечить «прозрачное» переключение каналов обмена во избежание конфликтов (в USART это достигается односторонностью обмена— по линии от главного МК к ведомым передается только 9-битовый адрес, а обратно— только 8-битовые данные, нам же нужен двусторонний обмен).

Для реализации такого варианта мы сконструируем специальную плату-коммутатор на основе отдельного МК (возьмем тот же AT90S2313). Идея состоит в том, что мы выделяем специальные команды-адреса, которые воспринимает только этот МК, и в соответствии с ними переключает канал обмена на нужное устройство. Если переключать с помощью мультиплексоров/демультиплексоров 561КП2 (см. рис. 8.8), то можно адресовать до восьми устройств. В качестве команд адресации удобно выбрать числа от 0 до 7, тогда они прямо будут соответствовать коду, который требуется подать на мультиплексоры.

Естественно, среди команд управления устройствами и передаваемых к устройству данных байты с таким значением должны полностью отсутствовать (например, установку часов напрямую таким способом не передашь), и это накладывает ограничения, но не очень серьезные. В каждом конкретном случае можно что-нибудь придумать: например, дополнять данные со значением меньше 8 старшим битом, равным единице, или еще что-то в этом роде (с похожими проблемами сталкиваются при передаче произвольных данных — вложений — по электронной почте, и ничего, как видите, справляются). Разумеется, можно задействовать и дополнительные линии СОМ-порта для адресации коммутирующего МК отдельно от остальных, или использовать девятибитовые посылки адреса (так, как это делается в USART), чтобы отличить их от данных и т. п. Здесь я приведу только самый простой вариант.

Схема такого коммутатора показана на рис. 19.7.

Рис. 19.7. Коммутатор UART на 8 каналов

Выводы коммутатора, помеченные номером с буквой R, следует присоединить к выводам RxD устройств, а их выводы TxD (строго в том же порядке) следует соединить с выводами коммутатора, помеченными номером с буквой Т. Программа коммутатора (листинг 19.8) очень проста и даже не содержит таблицы векторов прерываний, поэтому я привожу ее целиком.

Листинг 19-8

;Тестовый коммутатор

;Кварц 4 МГц

.include <<2313def.inc»

;=======

.def    temp = r16

;======= программа

          ldi temp,low(RAMEND)  ;загрузка указателя стека

          out SPL,temp

          ldi temp,(1<<ACD)  ;выкл. аналог, компаратор

          out ACSR,temp

          ldi temp, (1<<RXEN|1<<TXEN|1<<RXB8|1<<TXB8)

          out UCR,temp  ;разрешение приема/передачи 8 бит

          ldi temp,25

          out UBRR,temp  ;скорость передачи 9600

          ldi temp,0b00000111  ;устанавливаем PB0-PB2 выходы

          out DDRB,temp

          clr temp

          out PortB,temp  ;по умолчанию адресуется устройство 0

G_cykle:

          rcall in_com

          cpi temp,9  ;если принятый байт больше или равен 9

          brsh G_cykle  ;то ничего не делаем

          out PortB,temp  ;иначе выводим его в порт В

rjmp G_cykle

in_com:  ;прием байта в temp с ожид. готовности

          sbis USR,RXC

          rjmp in_com

          in temp,UDR

ret

Больше ничего делать не требуется — «верхняя» программа всегда начинает с того, что посылает номер устройства n от 0 до 7, мультиплексор коммутирует выходы nR и nТ к выводам RxD и TxD устройства с номером и, и далее «общение» с ним происходит совершенно «прозрачно», как будто остальных устройств не существует.