Пример законченной разработки: аналоговый усреднитель сигналов
В последующих разделах мы займемся проектированием законченного прибора на базе МП 68008 — аналогового «усреднителя сигналов» (вопросы усреднения будут обсуждаться в разд. 15.13). Прибор будет включать в себя ЦП с соответствующими электронными схемами для получения сигналов DTACK, BERR и других, память (ОЗУ и ПЗУ) и много разных интерфейсов: микропереключатели в DIP-корпусе, матрица электролюминисцентных диодов (ЭЛД), последовательный и параллельный порты, календарь-часы/таймер, аналого-цифровые и цифро-аналоговые преобразователи (АЦП и ЦАП), а также твердотельное реле для переключения цепей переменного тока. Как видите, мы задумали включить в наш прибор всего понемногу, чтобы получить микропроцессорный модуль общего назначения, перенастройка которого осуществляется сменой управляющей программы.
Мы пройдем с вами процедуру аппаратного проектирования, остановившись на критериях выбора элементов и разработки схемы. Вы научитесь выбирать и подключать память и периферийные устройства и разумным образом распределять адресное пространство. Закончив аппаратное проектирование, мы займемся программным обеспечением и напишем несколько программных «модулей» (блоков) для управления выбранными процессами. Мы, однако, не будем утомлять вас детальным рассмотрением всех программных строк, поскольку программные комплексы, подобные нашему, содержат много не очень интересных (хотя и необходимых) программ, вроде программы ввода с клавиатуры установочных команд. Наконец, мы проанализируем эффективность получившегося прибора — гибкость, обусловленную применением микропроцессора, и накладываемые им ограничения скоростных характеристик.
11.05. Разработка схемы
Структурная схема. На рис. 11.9 изображена структурная, на рис. 11.10 — электрическая схемы микропроцессорного прибора.
Рис. 11.9. Структурная схема прибора общего назначения на базе микропроцессора.
Рис. 11.10. Электрическая схема прибора общего назначения на базе микропроцессора.
Рассмотрим сначала структурную схему, на которой показаны устройства, подключенные к магистрали. Если анализировать эту схему с позиции микроЭВМ, то сразу бросается в глаза «перекошенность» памяти: объем ПЗУ в 4 раза превышает объем ОЗУ. Однако в микропроцессорном приборе конкретного назначения все программы и таблицы размещаются не в ОЗУ, а в ПЗУ, а ОЗУ используется только для буферизации данных и хранения временных результатов вычислений. К тому же производители программируемых ПЗУ с ультрафиолетовым стиранием (РПЗУ) по мере повышения качества технологии прекращают выпуск ПЗУ малого объема; сейчас трудно найти РПЗУ с емкостью, меньшей 8Кx8 бит. Так или иначе, на схеме показана память минимально возможного объема (по одной микросхеме ПЗУ и ОЗУ); при необходимости память можно расширить.
Следующее устройство на магистрали-календарь-часы. Календарь-часы??!! Что это, просто излишняя роскошь для тех, кому лень посмотреть на собственные часы при включении прибора? Напротив, календарь-часы — существенный элемент любого прибора, выполняющего периодические измерения, или фиксирующего моменты поступления данных, или управляющего любыми другими процессами, протекающими во времени. Календарь-часы можно запрограммировать, чтобы они вызывали периодические прерывания с частотой от 100 прерываний в секунду до одного за день; можно использовать их и как будильник (конечно, без звонка; вместо звонка возникает прерывание), устанавливаемый на любой момент хотя и в следующем веке. В нашем усреднителе сигналов мы воспользуемся таймерами параллельного порта 8536, но и календарь иметь под рукой полезно.
Микросхема 8530 последовательного порта представляет собой высокопроизводительный двухканальный УСАПП (универсальный синхронно-асинхронный приемно-передатчик, см. разд. 10.19), в комплекте с парой задающих генераторов. Это очень приличная микросхема, не только знакомая со всеми хитростями обычного асинхронного интерфейса RS-232, но и обеспечивающая полный синхронный протокол "SDLC/HDLC", включающий контроль ошибок, восстановление после тайм-аута, синхронизацию кадров и т. д.; возможно слишком жирно для нашей задачи, но, в конце концов, какого черта! Микросхема 8536 является одновременно таймером и параллельным портом; ее сделали отличные ребята из фирмы Zilog, и она тоже не слабак: возможностей у нее тьма-тьмущая. Например, каждую из ее 20 линий можно запрограммировать на вход или на выход, как на прямой, так и на инверсный; каждый выход может быть с открытым стоком или с двумя состояниями, в то время как каждый вход может работать в нормальном режиме или с «запоминанием 1» (кратковременный положительный импульс устанавливает входной регистр).
Описание режимов, кажется, не имеет конца (оно занимает 26 страниц) и поражает неисчислимыми возможностями. Посмотрим теперь на верхний ряд устройств. Матрица ЭЛД представляет собой просто линейку из 8 диодов, предназначенных для индикации работы прибора; эти диоды могут оказать также существенную помощь при отладке, когда остальные средства оказываются бессильны. К одному из выходов, питающих ЭЛД, мы подключили твердотельное реле, с помощью которого можно управлять каким-то сильноточным устройством. Например, для стабилизации температуры в ванне можно подавать сигнал, характеризующий температуру, на вход АЦП, а с помощью реле переменного тока включать нагреватель. Мы еще предоставим вам возможность проявить свою смекалку в этой области при выполнении упражнений. Микропереключатель в DIP-корпусе является обычным 8-контактным переключателем, полезным для ввода в прибор настроечной информации; с его помощью можно, например, указать, какой последовательный порт (и на какой частоте) используется ЦП после включения питания. Наконец, мы подключили к магистрали по паре аналого-цифровых и цифро-аналоговых преобразователей, чтобы прибор мог функционировать в мире аналоговых сигналов.
Детали схемы. Теперь начнется самое интересное. Рассмотрим электрическую схему прибора (рис. 11.10).
ЦП CLK. для работы МП 68008 требуются тактовые сигналы (CLK) прямоугольной формы (перепады логических уровней) в диапазоне от 2 до 10 МГц. Верхний предел определяется скоростью срабатывания внутренних вентилей и регистров; в настоящее время можно встретить экземпляры МП 68008 с максимальной тактовой частотой 8, 10 или 12,5 МГц. Нижний предел определяется тем обстоятельством, что в ЦП используются динамические регистры, требующие периодической регенерации, поскольку данные в них сохраняются не в триггерах, а в заряженных конденсаторах. Скорость вычислений пропорциональна тактовой частоте, поэтому естественно желание всемерно повысить тактовую частоту. Это, однако, сопряжено с некоторыми недостатками: а) более жесткие требования к временной синхронизации памяти и периферийных устройств, б) большая стоимость и в) большая рассеиваемая мощность, особенно для маломощных КМОП-процессоров и периферийных устройств. Обычно потребляемая мощность не имеет большого значения, за исключением случая батарейного питания; см. гл. 14. Мы остановились на тактовой частоте 8 МГц, так как это дает возможность использовать ту же тактовую серию (деленную на два) для микросхемы последовательного порта; в противном случае для работы УСАПП потребовался бы отдельный генератор, или пришлось бы ограничиться низкими скоростями передачи.
RESET, прерывания, стробирующие сигналы. Для того чтобы выполнить начальную загрузку МП 68008, следует установить сигналы RESET' и HALT' (обе линии двунаправленные; надо использовать схему с открытым коллектором с принудительной установкой верхнего уровня). Мы применили простую схему автозагрузки, состоящую из RC-цепи, триггера Шмитта и кнопки. Обратите внимание на диод, служащий для быстрого разряда в случае коротких выбросов напряжения питания; более совершенная схема сброса при включении питания должна использовать цепь «микропроцессорного контроля» вроде МАХ692, дающую хорошо сформированный сигнал сброса. Линия, обозначенная нами MR', переводится в высокое состояние и при начальной загрузке, и (на время 128 тактов) при выполнении ЦП команды RESET; линия INIT' переводится в высокое состояние только при загрузке.
В этой простой системы мы остановились на автовекторизуемых прерываниях; логическое И сигналов FC0 и FC1 указывает на выполнение цикла подтверждения прерывания, в течение которого мы обязаны установить сигнал VPA' одновременно со стробом адреса AS'. Мы также используем наш сигнал INTA' для запрещения нормального декодирования ввода-вывода (см. ниже). В МП 68008 предусмотрены три уровня автовекторизуемых прерываний. К нижнему уровню (IPL1) мы подключили через проводное ИЛИ «медленные» прерывания от последовательного порта и календаря; прерывания от чувствительного к задержкам таймера (называемого "СIO"-микросхемой) реализуются на следующем уровне (IPL0/2). Самый верхний уровень «немаскируемых» прерываний (установлены обе линии IPL) зарезервирован для прерывания от кнопки (немаскируемое прерывание NMI), так что вы всегда можете вывести плату из состояния зависания в процессе отладки программы.
Для образования пары стробирующих сигналов (RD', WR') мы использовали несколько вентилей. Эти сигналы будут полезны для некоторых "Intel-совместимых" периферийных устройств, для которых требуются отдельные строб-сигналы.
DTACK', BERR' и медленные периферийные устройства. Наконец, мы использовали сдвиговый регистр с параллельным выводом (`164) в качестве машины состояния для генерации последовательности нескольких требуемых сигналов. Сдвиговый регистр удерживается в исходном состоянии до установки ЦП сигнала AS', который указывает на начало цикла шины (см. рис. 11.4). После этого единицы начинают продвигаться вниз по регистру, на один шаг на каждый нарастающий фронт тактовой серии. Выход Q0 позволяет генерировать задержанный сигнал RD' (DELRD'), который используется обоими неповоротливыми устройствами (SCC и СIO), как это будет объяснено позже. Некоторые устройства ввода-вывода работают медленно и требуют состояний ожидания; выход Q2 сдвигового регистра позволяет сформировать задержанный сигнал DTACK и реализовать два состояния ожидания для всех портов ввода-вывода (при нашей раскладке адресов весь ввода-вывод отображается на память выше адреса $80000, т. е. при установленном сигнале А19), и отсутствие состояний ожидания для памяти (сигнал А19 не установлен). Если, однако, 1 дойдет до конца сдвигового регистра, возникнут неприятности, поскольку любые циклы магистрали завершаются (со сбросом сигнала AS) задолго до этого. Поэтому последний выход (Q7) использован для установки сигнала BERR, который приводит к векторизованному переходу (через ячейку 08), что предотвращает зависание ЦП. Такой сигнал «тайм-аута» магистрали особенно важен в ЭВМ общего назначения, где в противном случае обращение ЦП к несуществующему периферийному устройству приведет к аварии машины.
Память. Получив сигнал начальной загрузки (установка RESET и HALT), МП 68008 обращается к началу памяти с целью извлечения двух важных адресов: 32-разрядного стартового адреса, хранящегося в байтах памяти $04-$07, и начального значения указателя стека, располагающегося в байтах $00-$03. Прочитав эти адреса, МП инициализирует указатель стека, после чего осуществляет переход по стартовому адресу.
Поскольку эти ячейки в начале памяти считываются ЦП еще до загрузки какой-либо программы, эта область памяти должна быть энергонезависимой, т. е. ее содержимое должно сохраняться при полном отключении питания. Естественно использовать здесь РПЗУ (репрограммируемое постоянное запоминающее устройство, см. разд. 11.12), недорогое ПЗУ с байтовой организацией, стираемое ультрафиолетовым облучением. Эти ПЗУ легко распознать по небольшому стеклянному (собственно, кварцевому) окошку, расположенному «на спине» каждой микросхемы. Стирание РПЗУ требует полчаса, а запись-около минуты. Такие ПЗУ могут иметь емкость до мегабита, и данные сохраняются в них дольше, чем будет жить конструируемый вами прибор. Единственный недостаток размещения ПЗУ в начале памяти заключается в том, что там же располагаются разнообразные векторы (прерываний, ошибок шины и других «исключений»), содержимое которых хотелось бы изменять программно.
Решение проблемы может заключаться в использовании варианта РПЗУ, называемого ЭРПЗУ (электрически стираемое репрограммируемое постоянное запоминающее устройство). Можно также организовать двухступенчатый процесс: в ПЗУ постоянно хранятся векторы, указывающие на таблицу переходов в обычной памяти (ОЗУ, см. ниже). Есть, однако, более изящный способ. Вы конструируете схему таким образом, что при загрузке в начале памяти оказывается ПЗУ, но позже оно заменяется (под управлением программы) на обычную память с возможностью записи в нее, т. е. ОЗУ. Посмотрим снова на рис. 11.10. Мы использовали РПЗУ 27256, микросхему с организацией 32Кx8, имеющую, по нынешним стандартам, умеренную емкость. У нее есть 15 адресных входов, 8 трехстабильных выходов данных, вход выбора микросхемы (CS') и вход разрешения выхода (OE'). Каждый адресуемый байт (записанный в микросхему заранее в помощью программатора и более неизменяемый) поступает на линии данных только если установлены оба входа разрешения. Обычно вход CS' устанавливается как можно раньше сигналом с дешифратора адреса, а вход ОЕ стробируется сигналом чтения. В нашем случае память (ПЗУ или ОЗУ) активизируется только если сброшен сигнал А19; другими словами, память располагается в младшей половине адресного пространства. Кроме того, чтение ПЗУ разрешается только если а) установлен сигнал А18 или б) установлен бит BOOT (устанавливается при включении, сбрасывается программой). ОЗУ также располагается в нижней половине адресного пространства, но активизируется только если чтение ПЗУ запрещено. Таким образом, когда выполняется начальная загрузка, триггер BOOT установлен, и в адресном пространстве $0000-$7FFF временно располагается ПЗУ; ОЗУ как бы не существует. Обращение к ПЗУ осуществляется также и в области его «постоянного проживания», по адресам $40000-$47FFF.
Первые 8 байт ПЗУ остроумно запрограммированы так, что осуществляется переход на продолжение программы начальной загрузки, но уже в старшем адресном пространстве, где (среди прочего) очищается порт ЭЛД (адрес $86000). Запись в порт ЭЛД имеет побочный эффект сброса триггера BOOT, в результате чего временный образ ПЗУ в начале адресного пространства замещается образом ОЗУ. Для пояснения сказанного, ниже приведено содержимое первых 24 байт ПЗУ, реализующих описанную процедуру:
Заметьте, что две последние команды выполняются по адресам $40008 и $40010 соответственно благодаря стартовому адресу, извлеченному из ячейки $0004. Подключение микросхемы ОЗУ 8Кx8 осуществляется очень просто. ОЗУ воспринимает младшие 13 бит адреса (8К) и активизируется, когда сигнал А19 снят и ПЗУ отключено. Стробирующие сигналы RD' и WR' подключены ко входам разрешения выхода (OE') и разрешения записи (WE') соответственно. Будем пока считать, что схема декодирования, обозначенная на рис. 11.10 пунктиром, отсутствует. Тогда ОЗУ располагается в самом низу адресного пространства, за исключением момента начальной загрузки, когда оно замещается временным образом ПЗУ.
Однако наша схема декодирования адресов работает странным образом. Взглянем внимательно на ОЗУ. Мы игнорировали биты адреса А13-П17! В результате байт памяти с адресом, например $0000, имеет много двойников — его можно найти по адресам $2000, $4000 и вообще по любому адресу, имеющему нули в разрядах А0-А12 и А18-А19. Байт имеет множественное представление в адресном пространстве. Чтобы устранить эту неоднозначность, можно было более узко квалифицировать сигнал CS', разрешающий работу ПЗУ, обусловив нулевое состояние бит А13-А17, но в этом нет особого смысла. Хотя наличие «призраков» памяти по всему адресному пространству может показаться свидетельством небрежности, но вреда в этом нет, и к тому же экономятся вентили. То же происходит с ПЗУ (а также и с вводом-выводом). На рис. 11.11 показана карта памяти[1] нашего прибора, где описанные повторения обозначены явным образом.
Рис. 11.11. Карта памяти.
Разумеется, если вы захотите установить в системе дополнительную память, вам придется привлечь дополнительные адресные линии. На рис. 11.10 показано, как это можно сделать — просто подключите дешифратор 1 из 4 (`139) к двум следующим адресным линиям, активизируя его нашим сигналом разрешения ПЗУ, и без всяких хлопот вы можете добавить три блока ОЗУ. Дальнейшее расширение памяти осуществляется аналогично.
Упражнение 11.5. С помощью дешифратора 1 из 8 (`138) подключите к системе 8 блоков памяти 8Кx8.
Упражнение 11.6. Модифицируйте схему с целью подключения ОЗУ емкостью 32Кx8.
Упражнение 11.7. Теперь измените схему так, чтобы в ней работали два блока ПЗУ емкостью 64Кx8 (27512).
Упражнение 11.8. Для каждого из предыдущих упражнений нарисуйте карту памяти.
Синхронизация памяти. Перед тем, как приступать к рассмотрению ввода-вывода, полезно обратиться к вопросу синхронизации памяти. Ранее отмечалось, что наша схема генерации сигнала DTACK не создавала состояний ожидания при обращении к памяти. Это очень хорошо, но лишь в том случае, когда память обладает достаточным быстродействием, чтобы удовлетворить временным ограничением циклов чтения и записи на рис. 11.4. Но так ли это в действительности? Чтобы получить ответ на этот вопрос, надо начать с временной диаграммы МП 68008, затем вычесть наихудшие значения задержек «склеивающих» схем и посмотреть, сколько времени остается на реакцию памяти. Давайте проделаем это.
На рис. 11.12 изображен цикл чтения, для которого синхронизация обычно имеет большее значение.
Рис. 11.12. Временные соотношения цикла чтения из памяти (статическое ОЗУ, 150 нc).
Мы начали с временных характеристик ЦП для микросхемы в тактовой частотой 8 МГц, поскольку для нашей схемы мы выбрали именно эту частоту. Наиболее важным является временной интервал между правильным адресом ЦП и правильными данными памяти, так как этот интервал определяет максимально допустимое значение «времени доступа к адресу» со стороны памяти. В этом случае ЦП устанавливает правильный адрес по меньшей мере за 290 нc перед установкой правильных данных; соответствующее значение для DS' составляет 237 нc. Наша схема образования сигнала CS' для ОЗУ включает два каскада вентилей. При использовании микросхем 74НСТ02 и 74НСТ00 максимальные задержки составят 28 нc и 25 нc, что дает примерное значение 53 нc. В этом случае для времени доступа со стороны памяти (относительно фронта CS') остается 290 нc — 53 нc = 237 нc. С помощью аналогичных рассуждений (предположив, что сигнал RD' генерируется одной микросхемой 74НСТ32) получаем, что память должна выставить данные не позже 203 нc после установки ОЕ'. На рис. 11.12 также показаны наихудшие временные соотношения для самой медленной (150 нc) статической памяти (ОЗУ) с организацией 8Кx8: время доступа от фронта адреса 150 нc, от фронта CS' 150 нc и от фронта ОЕ' 60 нc. Поскольку для нашей схемы допустимы значения 290 нc, 237 нc и 203 нc, соответственно, мы имеем для наиболее критичной ситуации (время доступа от CS') запас почти 100 нc.
Мы не будем останавливаться на аналогичных рассуждениях для цикла записи, который в этом отношении является еще менее критичным. Очевидно, что с ОЗУ не возникает трудностей синхронизации, даже при использовании самой медленной памяти и при отсутствии состояний ожидания ЦП.
К сожалению, того же нельзя сказать про ПЗУ, которые обычно оказываются медленнее ОЗУ. Например, выпускаемые РПЗУ 32Кx8 характеризуются стандартными значениями времени (от адреса до данных или от CS' до данных) 150 нc, 200 нс и 250 нc. Приведенные выше выкладки сохраняют свое значение, но надо увеличить задержку CS' на 6 нс, так как изменяется логика образования этого сигнала. В результате только два более быстрых варианта РПЗУ удовлетворяют условию максимальной задержки 231 не от CS' до данных и могут использоваться в нашей схеме при отсутствии состояний ожидания. Вместо того, чтобы отбирать быстрые ПЗУ, можно было поставить более быструю «склеивающую» логику, например, 74АСТ или 74F; это дало бы возможность использовать ПЗУ с временем 250 нс. В действительности эти ПЗУ скорее всего будут работать в нашей схеме при любой логике, поскольку вычисления, выполненные на основе предельных временных характеристик, обычно дают результаты с большим запасом. Предельные значения достигаются при наихудшем сочетании температуры, напряжения питания, емкостной нагрузки и качества конкретной микросхемы; наши наихудшие условия предполагали значение напряжения питания 4,5 В, температурный диапазон от —40 °C до +85 °C, и неестественно высокую емкостную нагрузку 50 пФ. Если, однако, вы хотите быть уверенным в надежной работе аппаратуры, особенно в случае выпуска крупных серий приборов, следует вести расчеты по предельным значениям.
Периферийные цепи. В нашем приборе предусмотрено 9 периферийных устройств, поэтому в качестве «адресного коммутатора» мы использовали дешифратор «1 из 8» (`138); один из портов ввода-вывода разделяется ЭЛД-индикатором и набором микропереключателей. Сигналом разрешения дешифратора служит установка А19, что переводит нас в пространство ввода-вывода (верхняя половина адресного пространства); работа дешифратора запрещается на время цикла подтверждения прерывания, как это было объяснено ранее. К дешифратору подводятся линии А12-А14, в результате чего периферийные устройства имеют адреса $80000, $81000, $82000 и т. д.; оставшиеся старшие линии адреса мы игнорировали, как и при подключении памяти, в результате чего адреса периферийных устройств многократно появляются в адресном пространстве. В сущности, каждый адрес, превышающий 80000, до самого последнего адреса $FFFFF (а это полмиллиона адресов), отвечает какому-то периферийному устройству!
Упражнение 11.9. Расшифруйте это последнее утверждение, определив, сколько раз в точности каждое периферийное устройство появляется в адресном пространстве. После этого напишите общее выражение для адресов ЭЛД-индикатора, используя крестик (х) для тех бит, значение которых не влияет на результат дешифрации.
Упражнение 11.10. Единственным реальным недостатком нашей схемы неполной дешифрации адресов является использование понапрасну полмегабайта адресного пространства для обращения к десятку пустяковых периферийных устройств, в то время как большую часть этого пространства можно было бы отвести под память. Покажите, как следует дешифровать адреса ввода-вывода, если большую часть адресного пространства 1 Мбайт предполагается отвести под память. Наши 8 портов должны отображаться на адреса $FF000, SFF100… SFF700 и не отзываться при обращении по меньшим адресам. Теперь можно установить ОЗУ объемом 1 Мбайт, однако при обращении по адресам портов ввода-вывода будут активизироваться и ввод-вывод, и память. Найдите способ разрешить эту проблему.
Заметьте, что поскольку микросхема `138 игнорирует и младшие адресные сигналы, каждому периферийному устройству назначается целый набор смежных адресов. Некоторые устройства содержат несколько внутренних регистров, и для обращения к ним мы используем несколько младших линий адреса. Вы можете считать, что дешифратор отзывается на базовый адрес периферийного устройства. Рассмотрим теперь конкретные устройства ввода-вывода в нашем приборе.
ЭЛД и микропереключатели. Это простейшие из портов. При выводе матрица ЭЛД управляется 8-разрядным регистром из D-триггеров, для которого тактовым является сигнал декодирования адреса LEDSW', объединенный с WR'. Обратите внимание на то, что стробирование выполняется срезом сигнала; это уменьшает проблемы синхронизации, связанные со временем упреждения. Мы использовали восьмиразрядный регистр `273 с бистабильными выходами (вместо более распространенной тристабильной микросхемы `574) ради входа RESET', который мы устанавливаем на время сброса процессора или начальной загрузки; в результате при запуске световая индикация отключается. Логические микросхемы семейства НСТ имеют хорошие характеристики по скорости насыщения и выходному току (8 мА при выходном напряжении 4,5 В), что дает возможность использовать заземленную матрицу ЭЛД (с микросхемами семейства LS начальный уровень ЭЛД должен быть +5 В); это очень удобно, так как диоды индицируют единицы, а не нули. Выбранная нами матрица ЭЛД имеет встроенные резисторы, органичивающие ток до 6 мА. Заметьте, что один из битов порта ЭЛД управляет твердотельным реле переменного тока. Эти реле легко запускаются логическими уровнями (гарантированное напряжение срабатывания 3 В, сопротивление нагрузки 1,5 кОм), и, кроме того, они переключаются при нулевом напряжении (см. разд. 9.08 и 9.10). Заметьте также, что строб-сигнал WRITE порта ЭЛД выполняет дополнительную функцию, сбрасывая триггер BOOT при своей первой установке; после сброса триггера порт ЭЛД можно использовать по своему усмотрению.
Организация порта микропереключателей также не сложна. На выходе использован трехстабильный 8-разрядный инвертирующий буфер `240, управляемый уровнями от микропереключателей с принудительной установкой верхнего уровня. Разрешающим сигналом буфера служит тот же сигнал декодирования адреса LEDSW', на этот раз объединенный с RD'. Другими словами, если вы записываете по адресу $86000, данные индицируются на ЭЛД; если вы читаете, то считывается байт, характеризующий установку микропереключателей. Поскольку мы использовали инвертирующий буфер, замкнутый переключатель считывается как 1, а не 0.
АЦП и ЦАПы. Эти порты устроены так же просто. Обе микросхемы конвертеров являются «комплексными», со встроенными таймерами и опорными источниками. АЦП AD670 удовлетворяет протоколу сигналов R/W' и DS', поскольку снабжен входами направления и разрешения кристалла. Запись (разрешение микросхемы осуществляется низким уровнем R/W') начинает преобразование, в то время как чтение позволяет получить результирующий байт. В цикле записи АЦП фиксирует два бита данных: BPO/UPO' управляет диапазоном входных сигналов (высокий уровень — биполярный сигнал, низкий — однополярный), a FMT определяет формат цифрового выхода (высокий — дополнение до двух, низкий — беззнаковое двоичное представление). Выходной сигнал DONE говорит об окончании преобразования; мы отказались от использования этого сигнала, потому что, как нам кажется, проще выполнить несколько команд NOP в течение времени преобразования (длительность которого не превышает 10 мкс), чем организовывать опрос флага.
Микросхема AD670, как большинство периферийных микросхем, не отличается быстротой реакции в своей интерфейсной части. Ей требуется строб СЕ' по меньшей мере длительностью 300 нc в цикле записи, в цикле же чтения время доступа с момента установки СЕ' составляет 250 нc. Обратившись к рис. 11.4, вы увидите, что эти величины не удовлетворяют требованиям временной синхронизации МП 68008 в случае нормального (без состояний ожидания) цикла магистрали. Однако при двух состояниях ожидания (которые наша схема генерирует для всех адресов от $80000 и выше) все согласуется: сигнал DS' в цикле записи получает длительность 390 нc, а в цикле чтения он должен поддерживаться в установленном состоянии в течение 487 нc.
ЦАП AD558 также является комплексным конвертером; ему требуется единственное напряжение питания +5В, а на выходе образуется сигнал напряжения. В микросхему можно только записывать, поэтому мы использовали строб WR' для разрешения микросхемы, а сигнал декодирования адреса — для выбора микросхемы. Здесь также временные соотношения не будут удовлетворяться при отсутствии состояния ожидания: AD558 требует наличия данных за 200 нc до среза сигнала СЕ', а минимальная длительность СЕ' составляет 150 нc. Без состояний ожидания вы получите только 180 нc и 140 нc, соответственно; два состояния ожидания увеличат эти интервалы до удовлетворительных значений 430 нc и 390 нc.
Последовательный и параллельный порты. Типичными представителями перифейрийных БИС являются микросхемы Zilog 8530 SCC (последовательный порт) и 8536 СIO (параллельный порт и таймер). Такого рода микросхемы отличаются необычной гибкостью и умопомрачительным количеством рабочих режимов, программируемых путем засылки управляющих байтов во внутренние регистры. Некоторые из этих микросхем по сложности приближаются к микропроцессорам (см. рис. 11.13), и чтобы научиться программировать их работу, вам придется затратить немало времени.
Рис. 11.13. Структурная схема последовательного порта Zilog 8530.
Примечание: BR — запрос шины; FIFO — «пеpвым вошел, первым вышел»; TxD — сигнал передачи; RxD — сигнал приема; NRZ1 — кодирование без возвращения к нулю с инверсией; CRC — контроль циклическим избыточным кодом; DPLL — фазовая автоподстройка частоты; SDLC — синхронное управление линиями передачи данных.
Хотя периферийные БИС обычно разрабатываются под конкретные микропроцессоры, общность их характеристик позволяет использовать микросхемы, предназначенные для поддержки определенного семейства микропроцессоров, с процессорами других фирм. Микросхемы Zilog 85хх претендуют на роль универсальных, «магистрально-независимых» периферийных устройств, хотя при использовании их с МП 68008 возникает некоторая несовместимость в отношении строба RD', которую мы снимем, образовав задержанный строб RD'.
Рассмотрим сначала параллельный порт/таймер 8536. В нем используется пара стробирующих сигналов RD' и WR', а также сигнал разрешения входа СЕ' (который, как и обычно, поступает с выхода дешифратора адреса). Кроме того, на соответствующий вход микросхемы подаются тактовые сигналы для синхронизации таймера и управления внутренней логикой. Микросхема 8536 включает цепи полностью векторизуемых прерываний с подтверждением, выставляющие вектор на линии данных в течение цикла подтверждения прерывания. Реализация всех этих излишних для нас возможностей требует использования приоритетной цепочки, связывающей устройства (с помощью входного сигнала IEi и выходного IEO), а также входа INTACK', управляющего установкой (программируемого) вектора. Мы же ограничимся выходным сигналом INT' для организации запроса прерывания. Из состава интерфейсной шины к параллельному порту подключаются линии данных D0-D7, а также адресные линии (А0, А1) для адресации внутренних регистров; использование двух младших адресных линий приводит к отображению внутренних регистров на адресное пространство, начинающееся с базового адреса. В нашем случае внутренние регистры располагаются по адресам $84000-$84003.
Число адресных выводов наводит на мысль, что в микросхеме имеются 4 внутренних регистра, что, однако, весьма далеко от истины: фактически порт содержит 41 регистр для записи и 48 регистров для чтения! (Мы же предупреждали, что программирование этих микросхем — кошмарное занятие!) Для доступа к регистрам вы сначала записываете в «управляющий» регистр по адресу база + 3 ($84003) байт, содержащий адрес требуемого регистра данных, а затем читаете из или записываете в выбранный регистр. В отличие от этого регистры данных параллельного порта допускают непосредственную адресацию, и в них записывают или из них читают прямо по адресам база, база + 1 и база + 2.
На рис. 11.14 показаны временные диаграммы циклов чтения и записи, позволяющие рассмотреть проблемы синхронизации строба RD'.
Рис. 11.14. Синхронизация параллельного порта Zilog 8536.
Спецификации микросхемы 8536 дают минимальное значение интервала между установкой адресных сигналов А0-А1 и фронтом строба RD' (время упреждения) 80 нc. В спецификациях также определяется время отклика, как обычно, довольно большое — бедняге 8536 требуется 255 нc для выдачи данных; длительность же сигнала RD' должна составлять 390 нc (минимум). С большим временем отклика мы уже умеем бороться с помощью состояний ожидания. Однако состояния ожидания не решат проблему с временем упреждения адреса по отношению к RD' (из рис. 11.4 видно, что сигнал DS' может появиться всего лишь через 30 нc после установки правильного адреса). Чтобы все работало правильно, мы должны задержать RD' на один такт ЦП: это легко сделать с помощью того же сдвигового регистра, который генерирует сигнал DTACK'. Мы просто образуем логическое И «быстрого» строба RD' и (инвертированного) выходного сигнала Q0 сдвигового регистра, который не устанавливается до перепада тактового сигнала ЦП между состояниями S3 и S4. В результате образуется задержанный строб RD' (который мы назвали DELRD'), начинающийся на один такт позже (в тот же момент, что и нормальный DS' цикла записи). Описанная процедура предоставляет порту дополнительные 125 нc для упреждения адреса (в сумме 155 нc). Генератор состояний ожидания по-прежнему вводит два состояния ожидания, что делает полную длину цикла достаточной для медленных периферийных устройств.
К счастью, для сигнала WR' не требуется аналогичная схема, потому что МП 68008 предусмотрительно увеличивает время упреждения на один такт для циклов записи (обратите внимание на задержку сигнала DS' в цикле записи на рис. 11.4), а для микросхемы 8536 требуется то же значение времени упреждения (80 нc, см. рис. 11.14).
Интерфейс последовательного порта 8530 выглядит почти так же. Отличие заключается лишь в том, что адресные входы, выбирающие внутренние регистры, называются по-другому. Сигнал А0 подается на вход А/В' (выбирающий канал А или В сдвоенного порта), а сигнал А1 — на вход D/C' (который выбирает регистры данных или управления). Эта микросхема тоже не обижена регистрами: в ней имеются в каждом канале 16 регистров для записи и 9 регистров для чтения; доступ к ним осуществляется так же, как и в микросхеме 8536, в два этапа.
Тактовая частота 8530 может достигать 6 МГц; мы выбрали частоту 4 МГц, которая позволяет установить скорость передачи до 9600 бод. Асинхронные линии данных TxD и RxD работают с уровнями ТТЛ-логики, а не с биполярными сигналами стыка RS-232 (см. разд. 9.14 и 10.19). Большинство драйверов стыка RS-232 (например, классическая микросхема 1488) требуют двух источников питания, что для нашей системы, которая целиком питается от единственного источника +5В, будет как бельмо на глазу. К счастью, сейчас доступны искусные микросхемы, содержащие емкостные преобразователи напряжения. Эта методика была предложена фирмой Maxim в серии микросхем МАХ232; она используется также в микросхемах LT1080 фирмы LTC. Заметьте, что выход запроса прерывания с открытым стоком объединен по схеме проводного ИЛИ с соответствующим выходом микросхемы календаря-часов, так что возбуждение любого выхода приводит к автовекторизуемому прерыванию уровня IPL1. Обработчик прерывания этого уровня должен определить источник прерывания с помощью процедуры опроса, выполняя программное чтение регистра состояния каждого устройства. Соответствующая программа будет описана ниже.
Календарь-часы. Это последняя из использованных в нашем приборе периферийных БИС, и с ней тоже не все просто. Интерфейс этой микросхемы с магистралью практически такой же, как у микросхем Zilog: пара стробов «типа Intel» (RD', WR') и 4 бит адресации внутренних регистров. Здесь могут возникнуть и те же проблемы с синхронизацией сигнала RD'. Мы говорим «могут», потому что спецификация микросхемы недостаточно однозначна: в ней указывается «типичное» время упреждения от сигналов адреса до RD' (100 нс), но не дается минимального значения. Что имели в виду авторы, мы не знаем, но лучше не рисковать. Поскольку задержанный строб DELRD' у нас уже есть, давайте используем его.
Микросхема ICM7170 представляет собой современную БИС календаря-часов с внутренней схемой переключения питания; вы просто привешиваете к ней трехвольтовую литиевую батарейку, как показано на рис. 11.10. Прежние варианты календаря-часов требовали от вас обеспечения заданного порядка сброса управляющих сигналов при выключении, но микросхема 7170 берет на себя заботу и об этом. Конечно, бесполезно обращаться к микросхеме, если питание +5 В выключено; батарейка лишь поддерживает безостановочный ход часов в периоды спячки, так что схема просыпается с ясной головой и острым чувством времени.
Цепи питания. Закончив разработку схемы прибора, уже не хочется обременять себя такими деталями, как питание и заземление. Не поддавайтесь этому искушению. Наша схема использует «5-вольтовую логику», что на практике часто означает 5 В ± 5 % (в нашем примере ЦП и некоторые периферийные микросхемы требуют напряжения питания от +4,75 В до +5,25 В). Далее, в цепях питания не должно быть больших импульсных выбросов, избавиться от которых можно с помощью щедрого использования керамических конденсаторов 0,1 мкФ с некоторой добавкой танталовых электролитических конденсаторов большей емкости. Крайним случаем «большого выброса» является угрожающее перенапряжение, могущее возникнуть из-за отказа последовательных стабилизаторов напряжения в цепи питания +5 В. На этот случай стоит предусмотреть схему автоматического шунтирования источника питания при перенапряжении — либо на основной плате, либо в самом источнике питания. Выбирая источник питания, учтите, что микропроцессорная плата может легко потреблять 1 А и более, а по печатным проводникам, питающим вставные платы, может протекать и много ампер. Поэтому заранее запланируйте токонесущие печатные проводники достаточно большого сечения, а также сильноточные соединительные разъемы.
Как было показано в разд. 9.11, особую важность представляют заземляющие проводники как на самих печатных платах, так и между ними. Эти проводники должны иметь минимальную индуктивность. Лучше всего отвести под них один слой многослойной печатной платы, хотя «решетчатое» заземление на двухслойной плате часто дает удовлетворительные результаты (подробнее об этом в следующей главе). Наконец, последний совет касается схемы сброса при включении питания. RC-цепочка (с диодом), изображенная на рис. 11.10, заманчиво проста, однако она не будет отзываться на короткие броски напряжения, достаточные для нарушения работы выполняемой программы. Если микропроцессор встроен в прибор, то результатом будут сбои в работе прибора, причем для восстановления его работоспособности вам придется каждый раз выключать и снова включать питание! Мы сталкивались с таким явлением и в промышленном оборудовании, и в собственных разработках. Самое надежное — использовать хорошую современную схему сброса, например серии МАХ690 фирмы Maxim.
Разделавшись с аппаратным конструированием, которое оказалось совсем несложным, перейдем к действительно твердому орешку — программированию.
Разминка: сварим яйцо. Наш усреднитель сигналов потребует весьма сложного комплекса программ, как это обычно и бывает в мире задач реального времени. Внимательное чтение последующих разделов вознаградит читателей, собирающихся разрабатывать собственные микропроцессорные системы, так как они смогут детально познакомиться с большинством особенностей программного обеспечения аппаратуры, управляемой микропроцессором.
Перед тем, однако, как нырнуть в море, полное акул, давайте намочим ноги в мелкой луже простого (и несколько легкомысленного) примера программы для нашей микропроцессорной системы общего назначения. Компьютеры, вообще говоря, предназначены для избавления нас от рутинной работы. Пусть наш прибор каждое утро в 8 часов в течение 5 минут варит нам яйцо!
Представим, что твердотельное реле (рис. 11.10) подключено к небольшому кипятильнику, опущенному в чашку с сырым яйцом. Рассмотрим программу 11.2.
Для упрощения программы будем считать, что компьютер запущен и календарь-часы установлены. (В дальнейшем мы рассмотрим, как выполнить эти малоинтересные, но немаловажные действия!) Программа начинается с засылки нулевого байта в порт ЭЛД, чтобы выключить кипятильник; далее календарь-часы устанавливаются в 24-часовой режим при выключенных прерываниях. После этого программа входит в цикл ("WAKE", пробуждение) непрерывного опроса цифры часов в микросхеме календаря-часов, пока не будет прочитана цифра «8»; в этот момент в порт ЭЛД посылается байт, заполненный единицами, в результате чего включается кипятильник и загораются все ЭЛД.
Далее программа входит во второй цикл ("COOK", кипячение) непрерывного опроса цифры минут в микросхеме календаря-часов, пока не будет прочитана цифра «5»; в этот момент в порт ЭЛД посылается нулевой байт, выключающий кипятильник и гасящий ЭЛД. Наконец, программа переходит к третьему циклу ("WAIT", ожидание) опроса, как и в первом цикле, цифры часов, пока она не перестанет быть «8». В этот момент осуществляется безусловный переход в первый цикл ожидания 8 часов (уже завтрашних).
Мы привели эту скороспелую программу только для того, чтобы показать, как просто она может выглядеть. Не перенимайте наш стиль-многое мы сделали грубо, экономя место и не желая вдаваться в пояснения. В более совершенной программе мы могли повысить «интеллектуальный уровень» таймера, например, заставив один из портов АЦП фиксировать момент закипания воды; именно в этот момент следовало начать отсчет времени кипячения яйца, а можно было ради экономики электроэнергии еще и выключить кипятильник! Естественно также предусмотреть ввод, с помощью кнопки NMI, времени пробуждения, длительности кипячения и т. д. Порт ЦАП можно использовать для индикации времени, возможно, в виде «живых цифр», в то время как другой порт ЦАП (подключенный к динамику в подушке) мягко будит вас, тихо рассказывая что-то задушевное под аккомпанемент записанной в память мелодии…, но мы отклонились от темы. Ну что же, пора нырять!
11.06. Программирование: определение задачи
Самый верный способ напрасно потерять время и запутаться раньше, чем вы сделаете что-нибудь полезное — это начать программировать, не определив заранее, что именно вам надо. Это особенно справедливо, если вы программируете прикладной контроллер на языке ассемблера, потому что программа на языке ассемблера сама по себе не отличается ясностью построения, которую ей дает структурированный язык высокого уровня; более того, желая оптимизировать действия программы в реальном времени, вы вынуждены прибегать к разного рода хитроумным приемам и относительно туманным алгоритмам. В результате ваша программа — с переходами и ветвлениями, с разделением функций между главной программой и обработчиками прерываний, с программными флагами, модифицируемыми в самых неожиданных местах, с управляющими байтами для периферийных устройств и многим другим - ваша программа быстро превращается в ужасающую мешанину, особенно если на полдороги вы все еще не представляете целиком свою задачу. Как и при покраске дома, приготовления могут занять больше времени, чем сама работа, но в итоге оказываются оправданными.
Разрабатываемый нами усреднитель сигналов является удачным примером. Он не относится к числу очень сложных приборов, однако беглый взгляд на структурную схему обработчика прерываний, приведенную на рис. 11.21, должен убедить вас, что не так уж просто разобраться в установке и чтении флагов и сигналов, изменении векторов прерываний в реальном времени и вообще в порядке выполнения программных строк. Так что стоит потратить время и разобраться, как же должен функционировать наш прибор.
Что такое усреднитель сигналов? Усреднитель сигналов, иногда называемый многоканальным накопителем, предназначен для повышения качества (т. е. увеличения отношения сигнал/шум) периодического аналогового сигнала, неизбежно смешанного с непериодическим шумом (или сигналами помех). Повышение качества осуществляется путем измерения значений сигнала много раз в течение каждого периода, занесения этих выборочных значений в набор последовательных «ящиков» или каналов и затем сложения в каждом канале соответствующих выборок от многих периодов входного сигнала. Другими словами, сигнал складывается сам с собой по модулю его периода. Как будет показано в разд. 15.13, такая процедура повышает отношение сигнал/шум для сигнала, накапливающегося в каналах, потому что суммарное значение (периодического) сигнала растет линейно со временем, а флуктуации (случайного) шума растут только как квадратный корень из времени. Будем называть каждый последовательный период накопления значений в ячейках «разверткой»; типичный сеанс накопления данных может состоять из нескольких тысяч разверток.
Хороший усреднитель сигналов постоянно показывает на экране дисплея накопленную форму сигнала (хранящуюся в 1000 или около того каналов) и предоставляет вам широкий выбор ширины каналов («задержки на ячейку»), режимов запуска, масштаба изображения и т. д. Многие из этих возможностей мы реализуем в нашей разработке, однако далеко не все, иначе описание прибора выйдет за рамки главы. Из всего многообразия возможностей мы отобрали набор функций, который позволит продемонстрировать весь диапазон программных приемов и компромиссных решений, но и не даст нам унестись на крыльях мечты в царство идеальных усреднителей сигналов.
Характеристики. По причинам, которые будут описаны позже, мы решили снабдить прибор обычной управляющей (передней) панелью с надписанными переключателями вместо того, чтобы использовать современную методику с клавиатурой и экранными меню. В результате наш усреднитель выглядит, как обычный прибор такого рода, а его органы управления имеют предопределенные функции и диапазоны. Продумывая план этой главы, мы в действительности начали с того же, с чего начинаем сейчас - с выбора реализуемых функций и диапазонов настройки.
Мы решили ограничиться фиксированным числом каналов (256) с широким диапазоном их ширин. Поскольку усреднитель сигналов используется для исследования периодических явлений двух видов - с собственным внутренним периодом (например, океанские приливы) и запускаемых периодически нами (например, нервные импульсы или резонансные явления), мы предусмотрели два режима развертки: с внешним запуском, когда прибор ждет внешнего сигнала, чтобы начать цикл развертки, и с автозапуском, когда циклы развертки следуют друг за другом непрерывно. Далее, мы предусмотрели два способа завершения процедуры усреднения сигнала: по определенному заранее числу разверток и по нажатию на кнопку «стоп». В последнем случае усреднение завершается после окончания очередной развертки. Для наблюдения усредненного сигнала на экране ЭЛТ (с непрерывным обновлением изображения) мы формируем аналоговые X и Y сигналы (вместе с сигналом подсветки Z). При этом масштаб изображения можно изменять в широких пределах умножением на 2n; имеется также режим «автомасштабирования», в котором данные непрерывно пересчитываются (нормализуются) в соответствии с числом выполненных разверток. Наконец, на управляющую панель выведены ЭЛД, индицирующие состояние (ожидание, развертка) и логические выходные сигналы, характеризующие наличие развертки и конец развертки. Ниже приведены характеристики нашего усреднителя сигналов.
Диапазон аналогового входного сигнала: ±5В
Число каналов: 256
Внутреннее представление: 32-разрядные целые со знаком
Ширина канала: от 100 мкс до 1 с, с интервалами 1-2-5
Накопление: конечное суммирование (сумма 100 мкс-выборок)
Определяемое заранее число разверток: от 1 до 20000, с интервалами 1-2-5
Режимы развертки: внешний запуск; периодический (автозапуск)
Режимы наблюдения: с выбором масштаба (изображение, выходящее за верхнюю границу экрана, переносится вниз); с автомасштабированием
Масштаб изображения: от 1 до 16К входного диапазона, ступенями с умножением на 2
Входы: аналоговый сигнал, внешний запуск развертки
Выходы: X, Y, Z (на ЭЛТ), РАЗВЕРТКА, КОНЕЦ
Дополнительное управление: ПУСК, СТОП, СБРОС (перезагрузка)
На рис. 11.15 показаны входные и выходные сигналы микропроцессорной платы.
Рис. 11.15. Входные, выходные и управляющие сигналы усреднителя сигналов. «ЭЛД» обозначает порт индикации (см. рис. 11.10); «А», «В» и «С» относятся к битам параллельного порта 8536.
Для всех цифровых сигналов мы использовали параллельный порт 8536, причем направление и полярность сигналов соответствующим образом программируются; все цифровые входы, на которые поступают сигналы от органов управления, должны быть подключены через резисторы к уровню +5 В и заземляться при замыкании ключа. В схемах устранения дребезга нет необходимости, поскольку дребезг мы устраним программно.
Аналоговый фильтр имеет особое значение и заслуживает некоторых пояснений. Если вы получаете короткие периодические выборки значений непрерывного аналогового сигнала с частотным спектром конечной ширины (при максимальной частоте, присутствующей в сигнале, f, вы сохраняете входную информацию лишь если выборки следуют с частотой 2fмакс и более. Если же этот критерий Найквиста не удовлетворяется, происходят странные вещи; конкретно, на рис. 11.16 показано явление возникновения побочной низкочастотной составляющей в результате субдискретизации, когда частоты, близкие к частоте дискретизации fд, отображаются на низкочастотную область. Для устранения этого явления входной сигнал следует пропускать через низкочастотный фильтр с граничной частотой fд/2 или меньше.
Рис. 11.16. Возникновение побочной низкочастотной составляющей в результате субдискретизации.
Это вроде бы просто, но как быть с тем обстоятельством, что ширина канала и, следовательно, частота дискретизации в нашем усреднителе перестраивается? Можно установить на входе регулируемый фильтр низких частот (например, фильтр с коммутируемыми конденсаторами и программно управляемой тактовой частотой) и настраивать его согласно ширине канала; это допустимо, так как если вы выбираете большую ширину канала, высокие частоты вас все равно не интересуют. Однако можно поступить проще. Заметьте, что при интегрировании (усреднении) сигнала в течение ширины канала вы получаете низкочастотный фильтр с автоматической регулировкой. По этой причине на входе усреднителя сигналов иногда устанавливают преобразователь напряжения в частоту (микросхема высокой степени интеграции). Мы используем, в сущности, тот же подход: дискретизация аналогового входного сигнала всегда осуществляется на частоте 10 кГц (при этом для устранения наложения спектров входной сигнал фильтруется низкочастотным фильтром с граничной частотой 5 кГц, соответствующей частоте дискретизации); при больших периодах дискретизации осуществляется эффективное интегрирование сигнала за счет сложения соответствующего числа последовательных отсчетов. На рис. 11.17 показано, как могла бы выглядеть передняя панель прибора.
Рис. 11.17. Передняя панель усреднителя сигналов.
Отсутствующие возможности. Полезно пояснить, что именно мы не включили в нашу разработку, и почему. Мы не поддались повальному увлечению экранными меню по следующим причинам. Во-первых, приборы с управлением от клавиатуры нас часто раздражают, так как все они работают по-разному, и вам приходится постоянно переучиваться. Во-вторых, они замедляют вашу работу — для того лишь, чтобы переключать шкалу, вам надо набирать команду на клавиатуре. Наконец, управление от клавиатуры с помощью экранных меню требует разработки программ анализа введенных строк и формирования на экране изображения меню — программ, не представляющих интереса с педагогической точки зрения. Поэтому мы решили считывать состояния органов управления на передней панели с помощью параллельного порта; это быстрая и простая операция, хотя она ограничивает возможности выбора режимов и уменьшает универсальность прибора.
Можно перечислить еще целый ряд дополнений, которые улучшили бы наш прибор, но мы от них отказались, чтобы не усложнять описываемую здесь программу. Можно было предусмотреть второй аналоговый вход (микросхема 670 содержит два АЦП), переменное число ячеек, несколько «банков» памяти для хранения данных, цифровые выходы для индикации номера текущей ячейки, а также аналоговые сигналы, пропорциональные номеру ячейки (для контроля аналоговых величин). Это все довольно очевидные, но не принципиальные усовершенствования; более важной является возможность пересылать накопленные данные в микроЭВМ, для чего лучше всего использовать один из последовательных портов.
Даже в рамках выбранной нами конструкции можно было ввести дополнительные режимы. В частности, микропроцессорная система с аналого-цифровыми и цифро-аналоговыми преобразователями значительно выиграет при включении в нее аналоговых мультиплексоров, позволяющих связать выходы ЦАП со входами АЦП. В этом случае вы сможете выполнять программное тестирование всех преобразователей в качестве элемента общей процедуры тестирования при включении питания (куда также входит проверка памяти, портов и т. д.). Можно даже проверять, с помощью АЦП, напряжение (я) источника питания.
Упражнение 11.11. Покажите, как это сделать, если ко входам АЦП подключены аналоговые мультиплексоры. Вам потребуется предусмотреть адрес порта, по которому из ЦП будут направляться команды выбора мультиплексора.
11.07. Программирование: детали
Обзор. Программы обычно сложны. Программы реального времени на языке ассемблера сложны всегда. Однако вместо того, чтобы рассматривать упрощенную программу, которая в действительности работать не сможет, мы собираемся привести реальную законченную программу, годную для использования в нашем приборе, во всем ее великолепии (и безобразии). Чтобы помочь вам в ней разобраться, мы будем рассматривать ее постепенно, слой за слоем. Лучше всего воспользоваться методом «нисходящего проектирования», когда сначала определяются основные функции программы, порядок их выполнения и протокол обмена информацией между основными модулями программы. После этого можно перейти к структурным схемам конкретных программных модулей. Наконец, пишутся реальные программные строки на языке ассемблера.
Замечание: дальнейшее описание насыщено деталями. Читатели, желающие получить лишь общее представление, могут опустить обсуждение вопросов программирования, перейдя сразу к разд. 11.08 или 11.09.
На рис. 11.18 изображена общая структура, сильно упрощенная.
Рис. 11.18. Программирование: общая структура.
В ОЗУ мы организовали три массива: массив DATA из 256 32-разрядных («длинных») целых чисел для хранения текущих данных для каждого канала; массив NORM из 256 16-разрядных («слова») целых чисел для хранения количества полных разверток для каждого канала, что нужно для нормализации данных в режиме автомасштабирования; и, наконец, массив DISPLAY из 256 байт для хранения данных, непрерывно поступающих на дисплей. Основные функции программы заключаются в следующем: добавление в массив DATA новых данных из АЦП при одновременной модификации массива NORM; масштабирование этих длинных чисел с преобразованием их в байты массива (в режиме автомасштабирования — с использованием массива NORM, а при ручном задании масштаба - путем сдвига); непрерывный вывод этих байтов на экран.
Общая последовательность работы программы выглядит следующим образом. При включении питания ЦП активизирует процедуру начальной загрузки и начинает выполнять программу, записанную в ПЗУ (причем временный образ ПЗУ в самом начале адресного пространства содержит вектор перехода на истинное расположение ПЗУ, как это было объяснено в разд. 11.05). Инициализируются, при необходимости, периферийные микросхемы (посылкой соответствующих байтов в их управляющие регистры); кроме того, программа должна инициализировать массивы, указатели, начальные значения переменных и проч. Приборы вроде нашего характеризуются несколькими возможными состояниями (ожидания сигнала ПУСК, накопления данных и т. д.); мы начинаем с состояния ожидания нажатия кем-нибудь кнопки ПУСК. Однако прибор и в этом состоянии живет, выводя на экран данные, которые мы инициализировали нулями.
При нажатии кнопки ПУСК программа начинает с того, что считывает состояние органов управления на управляющей панели, чтобы получить параметры для своей работы. Затем программа переходит в режим приема данных (в соответствии с параметрами, введенными с управляющей панели), выдавая одновременно на экран текущие значения усредненного сигнала. После завершения заданного числа разверток или обнаружив нажатие кнопки СТОП, программа опять входит в режим ожидания.
Синхронизация; программное управление и управление от прерываний.
Наиболее важная задача — обеспечить выполнение АЦП преобразований каждые 100 мкс и накопление получаемых данных в массиве DATA. Следующая по важности задача - регенерация дисплея с частотой по меньшей мере 40 Гц, чтобы избежать мерцания. Менее важной является задача обновления по мере изменения содержимого массива DATA также и массива DISPLAY.
Вы можете подумать, что наиболее важная задача должна решаться средствами основной (зацикленной) программы, задачи же менее важные могут время от времени отнимать время процессора с помощью прерываний. Однако это не так. Прерывания имеют приоритет по отношению к текущей программе, поэтому срочные задачи должны выполняться обработчиками прерываний, задачи же менее важные — наоборот, «главной» программой, которая получит лишь то время ЦП, которое останется от обработки прерываний. (При этом, конечно, предполагается, что процессор обрабатывает прерывания достаточно быстро. Как мы увидим, большое число регистров МП 68008 позволяет ему исключительно быстро обрабатывать прерывания; МП 68008 прямо создан для этой задачи.)
Как было показано на рис. 11.18, данные из АЦП поступают в массив DATA с помощью прерываний, создаваемых каждые 100 мкс программируемым таймером 8536. Главная программа занимается непрерывным обновлением массива DISPLAY, используя для этого подпрограмму update (обновление) (поскольку эта подпрограмма совсем не думает о том, что у нее под носом протекает интереснейший процесс сбора данных, она вынуждена проверять флаг, устанавливаемый программой обработки прерываний и свидетельствующий об окончании измерений).
Мы сначала хотели включить в главную программу также и регенерацию дисплея, но этому помешало любопытное обстоятельство. С каждой парой сигналов X, Y, посылаемых на ЦАП для вывода на экран точки, надо формировать также и сигнал «оси Z» (подсветки). В разд. 10.20 было показано, как можно сформировать «программный сигнал», посылая в бит порта сначала 1, а затем 0. Сигналы оси Z должны иметь одинаковую длительность, иначе одни точки на экране будут ярче других. Если, однако, программа время от времени прерывается, невозможно гарантировать равную длительность программных сигналов.
Упражнение 11.12. Но почему?
Можно, конечно, выключать прерывания, генерировать сигнал, а затем снова включать прерывания. Это безобразное решение, поскольку в самый важный процесс регулярных выборок вносятся нежелательные задержки. Потом мы нашли лучший способ: вывод на экран одной точки выполнять, как вспомогательную задачу обработчиком прерываний. Обработчик срабатывает каждые 100 мкс, так что полное 256-точечное изображение будет выводиться 40 раз в секунду. При этом, поскольку прерывания возникают и в том случае, когда главная программа находится в состоянии ожидания (сигнала ПУСК), изображение на экране не будет гаснуть. Наконец, такой способ содержит в себе чудесную глюковину: ведь запустив АЦП, приходится выжидать 10 мкс перед тем, как снимать с него результат преобразования; этого времени как раз хватит, чтобы послать в ЦАП пару X, Y. Другими словами, регенерация дисплея в обработчике прерываний абсолютно не требует процессорного времени!
Главная программа: инициализация. Хватит нам ходить вокруг до около. Давайте рассмотрим подетальнее задачи, выполняемые программой. Сначала взгляните на главную программу, изображенную на рис. 11.19 в виде несколько необычной структурной схемы.
Рис. 11.19. Структурная схема главной программы.
Приведенная диаграмма весьма близко соответствует собственно программе на языке ассемблера (программа 11.3).
Текст программы начинается с определений адресов ОЗУ (включая вектор прерывания, область переменных и массивы), а также адресов (и бит) портов. В дальнейшем эти определения будут использоваться в качестве операндов команд обращения к памяти и портам, причем ассемблер подставит на их место фактические адреса. Хотя результат не зависит от того, пользуетесь ли вы определениями или непосредственно адресами, всегда следует использовать определения, так как в этом случае программа становится более наглядной и, кроме того, облегчается изменение назначения портов и битов в последующих модификациях. Адреса портов соответствуют нашей схеме и включают внутренние регистры периферийных устройств, адресуемые с помощью младших бит адреса или путем двухбайтовых пересылок.
Из текста программы также видно, как мы будем использовать регистры МП 68008. При каждом прерывании мы извлекаем данные из АЦП, добавляем их к текущему содержимому канала и проверяем, не дошли ли мы до конца канала или развертки. Можно было хранить содержимое указателей и счетчиков в памяти (так и пришлось бы поступать при использовании менее совершенного процессора типа 8086), но зарезервировав достаточное число регистров для нужд обработчика прерываний, мы существенно повышаем эффективность режима прерываний. Поэтому мы выделили регистры данных для текущего содержимого канала (D7), обратного счетчика периодов дескретизации (внутри канала) (D6) и обратного счетчика каналов внутри развертки (D5), смещения в массиве DISPLAY (D4), а также регистр для временных данных (D3). Далее, мы зарезервировали адресные регистры для трех массивов (NORM, А6; DATA, А5; DISPLAY, А4) и для наиболее используемых портов (ADC0, A3; СIO [параллельный порт], А2). Главная программа берет на себя обязательство не использовать эти регистры при включенных прерываниях.
Вам может показаться странным, что мы резервируем адресные регистры (со всеми их автоинкрементными возможностями, ориентированными на работу с массивами) для адресации отдельных фиксированных портов, когда вполне можно было обойтись абсолютной адресацией. Причина заключается в быстродействии. Команда с абсолютной адресацией
MOVE.B ADC0, D0
где ADC0 представляет длинный абсолютный адрес (в нашем случае $80000), требует 28 тактов (3,5 мкс в нашем процессоре), в то время как команда
MOVE.B (A3), D0
использующая косвенную адресацию через A3, выполняется всего за 12 тактов. Эта разница обусловлена исключительно процессами на магистрали, где для пересылки каждого байта требуются (в МП 68008) 4 такта. В процессе выполнения первой команды ЦП извлекает из памяти двухбайтовый код операции, четырехбайтовое расширение (длинного) адреса и, наконец, запрошенный байт данных, т. е. всего 7 байт, на что расходуется 28 тактов. Вторая команда требует извлечения двухбайтового кода операции и запрошенного байта данных, т. е. всего 3 байт (12 тактов). Вообще системы с узкими шинами (вроде нашего МП 68008, у которого внутренняя 32-разрядная архитектура должна себя чувствовать как в смирительной рубашке, общаясь с внешним миром через 8-разрядную шину) особенно неэффективны в условиях интенсивных передач данных.
Наконец, началась программа! Первые 8 байт ПЗУ хранят важнейший стартовый вектор: указатель стека и входную точку программы. Входная точка находится в «истинном» ПЗУ (по адресу $40008), поэтому мы можем немедленно очистить бит BOOT, что приводит к замещению временного образа ПЗУ, используемого при начальной загрузке, оперативной памятью. Теперь мы можем загружать векторы прерываний в начало ОЗУ, в конкретные ячейки, определяемые архитектурой МП 68008 (вся область векторов приведена в табл. 11.5): $68 (INT2), $74 (INT5) и $7С (NMI = INT7). Мы использовали только INT5 (от 100 мкс — таймера в микросхеме параллельного порта); в этот вектор мы загружаем адрес нашего обработчика прерываний. В зависимости от конкретного состояния прибора (ожидание пуска или внешнего сигнала запуска, начало новой развертки, процесс развертки) обработчик прерываний должен выполнять различные функции; поэтому мы написали один грандиозный обработчик со многими точками входа, соответствующими его функциям. На данном этапе мы еще не готовы принимать данные, поэтому в вектор INT5 мы загружаем входную точку idle__int (прерывание простоя). Очень полезно загрузить на всякий случай все неиспользуемые векторы прерываний адресом bad__int (ложное прерывание) (вдруг произойдет деление на нуль, ложное прерывание и т. д.); мы загружаем в них адрес программы, которая зажигает ЭЛД определенным образом (далее будет видно, каким именно).
Теперь наступает утомительный, но существенный этап инициализации портов. БИС периферийных устройств, как, например, 8536, обладают изумительной гибкостью, но за нее приходится платить тщательным планированием. Вы должны продумать, какие управляющие байты следует послать, в какие регистры и в каком порядке, чтобы получить требуемый результат. Для простых параллельных портов в процессе планирования следует выбрать направление, полярность, режим и прерывания, а для таймеров — основание счета, каскадирование, режим запуска, прерывания и проч. В программе 11.3 приведен полный текст инициализации параллельного порта/таймера. Разрешаются параллельные порты А, В и С, причем биты 4–6 порта В назначаются выходными, а остальные - входными (см. рис. 11.15). Таймер-0 настраивается на деление его тактовой частоты 4 МГц на 400 и на непрерывный перезапуск с генерацией прерывания (по INT5) каждые 100 мкс. Заметьте, что все установочные входы мы сделали инверсными, поэтому при замыкании контакта (на который изначально подано +5 В) на землю с него считывается 1, а не 0. На входе, к которому подключена кнопка СТОП, мы использовали опцию «запоминания 1», так что мгновенное нажатие фиксируется, а отрабатывается оно только в конце развертки.
Наконец, мы очищаем массивы в ОЗУ (отметьте использование подпрограммы), инициализируем регистры, разрешаем прерывания и переходим на выполнение «главного» цикла.
Главная программа: главный цикл. Завершив инициализацию, мы входим в бесконечный главный цикл main__loop. Фактически он состоит из двух циклов: цикла ожидания нажатия кнопки ПУСК и цикла непрерывного обновления памяти изображения, на фоне которого осуществляется сбор данных в режиме прерываний. Программа обработки прерываний, завершив последнюю развертку, устанавливает программный «флаг останова» stop__flag, который непрерывно проверяется вторым главным циклом. Обнаружив установленный флаг, главная программа возвращается в первый цикл ожидания нового пуска. Давайте сопоставим структурную схему и программные строки.
Главный цикл (рис. 11.19) начинается с установки на ЭЛД состояния «ожидание». Затем программа ждет нажатия кнопки ПУСК, т. е. ее перехода из разомкнутого в замкнутое состояние. Это сложнее, чем кажется, потому что кнопка не содержит цепей подавления дребезга, в результате чего вы имеете несколько десятков близко расположенных перепадов между уровнями «замкнуто» и «разомкнуто», возникающих на протяжении, возможно, 25 мс. Этого времени может хватить на завершение самого короткого цикла измерений (если вы выбрали 1 развертку и интервал дискретизации 100 мкс), после чего измерения будут ошибочно продолжены, поскольку контакт кнопки все еще колеблется между состояниями «разомкнуто» и «замкнуто». Поэтому мы написали простенькую программу подавления дребезга, которая фиксирует, что кнопка была непрерывно разомкнута в течение приблизительно 50 мс (тем временем многократно выполняется подпрограмма обновления update), а затем переходит в состояние «замкнуто». Наконец мы получили приказ на выступление!
Программа сбрасывает выходной сигнал КОНЕЦ, считывает состояние управляющей панели и использует соответствующим образом полученные значения (устанавливая программные флаги типа auto__loop и параметры вроде dwell__per__bin и num__sweeps). Обратите внимание на использование таблицы decode__tbl (и косвенной адресации с индексацией) для получения значений, соответствующих положениям переключателей.
Далее программа очищает массивы DATA и NORM, инициализирует некоторые регистры (адресов и данных) и сбрасывает флаг останова. Последний шаг заключается в изменении содержимого вектора INT5 (который пока указывает на метку idle__int в обработчике прерываний) на адрес wait__trig или sweep__start в зависимости от того, какой режим установлен на управляющей панели: внешнего запуска или автозапуска.
Наконец, главная программа входит в «рабочий» цикл, в котором многократно выполняются два действия: вызов подпрограммы update (обновления массива DISPLAY в соответствии с содержимым массива DATA) и проверка флага останова stop__flag. На фоне этого унылого цикла прерывания тайком выполняют все то, ради чего был сделан наш прибор.
Главная программа: подпрограммы. Перед тем как взяться за наиболее сложную программу нашего комплекса - обработчик прерываний, рассмотрим две подпрограммы, вызываемые главной программой (рис. 11.20).
Рис. 11.20. Структурные схемы подпрограмм.
Подпрограмма clear__arrays заполняет нулями оба массива DATA и NORM; массив DISPLAY очищать нет необходимости, потому что программа update сразу же скопирует нули из DATA в DISPLAY. Эта программа обновляет за раз одно значение из массива DISPLAY, используя для этого текущие параметры изображения с управляющей панели и входные данные из массивов DATA и NORM; она также обновляет состояние порта ЭЛД, копируя байт памяти led__store.
Рассмотрим сначала простую подпрограмму clear__arrays из программы 11.3. Регистры А0 и А1 используются, как указатели двух массивов, и все 32 разряда D0 заполняются нулями. Счетчик D1 инициализируется величиной, равной размеру массива минус один; сейчас станет понятно, зачем это нужно. В цикле слово или длинное слово нулей пересылается в массивы с помощью косвенной адресации (с постинкрементом); вспомните, что постинкрементная адресация — штука интеллигентная, она инкрементирует адресный регистр правильным образом, прибавляя в нашем случае 2 в операции со словом и 4 в операции с длинным словом. Команда DBF заслуживает особого объяснения. Она представляет собой один из вариантов команды DBcc, для которого код условия ее = «ложь» (False). Любая команда (в общем виде) DBcc Dn, метка фактически проверяет два условия.
Сначала она анализирует выполнение условия ее (т. е. состояние флагов, установленное предыдущей командой), при этом, если ее = «истина», ничего не делается (т. е. команда как бы пропускается и ЦП переходит к выполнению следующей команды). Если, однако, ее = «ложь», происходит декремент указанного регистра (как слова) и переход на метку метка, с предварительным анализом содержимого регистра. Если в регистре обнаруживается — 1, переход на метку не осуществляется, и выполняется следующая команда. В нашем случае команда DBcc действует просто как оператор цикла, так как ее = F («всегда ложь», см. табл. 11.1), поэтому декремент D1 осуществляется безусловно, до тех пор, пока не обнаружится D1 = — 1. Несмотря на эти сложности (а также и необходимость использовать счетчик длиной в слово), команда DBcc весьма удобна, так как заменяет две команды (SUBQ, Вcс), и выполняется очень быстро. Поскольку она проверяет счетчик на —1, последний следует инициализировать числом, на 1 меньшим требуемого числа шагов, чем и объясняется инициализация, использованная в программе. Подпрограмма заканчивается обычной командой RTS (возврат из подпрограммы), восстанавливающей исходное содержимое PC (программного счетчика) и осуществляющей таким образом возврат в вызывающую программу.
Заметьте, что в начале подпрограммы не понадобилось сохранять содержимое каких-либо регистров, потому что вызывающая программа не оставила ничего ценного в регистрах D0-D1 и А0-А1. Обратите также внимание на использование MOVE, а не CLR для обнуления массивов; оказывается, команда MOVE работает быстрее CLR из-за особенностей архитектуры МП 68000 — при выполнении CLR МП 68000 сначала инициализирует цикл чтения, а затем — цикл записи. Разработчики приняли такое, на первый взгляд, странное решение для упрощения логики ЦП.
Упражнение 11.13. Напишите вариант подпрограммы clear arrays с использованием команд SUBQ и Всс вместо DBF. Напишите еще один вариант, в котором вместо MOVE используется CLR.
Подпрограмма update более содержательна. Ее задача — обновлять массив DISPLAY в памяти (а также и состояние ЭЛД); она многократно вызывается в обоих циклах главной программы. Поскольку прерывания имеют приоритет, они выполняют свою работу в точном соответствии с расписанием (каждые 100 мкc), все же оставшееся время отдается подпрограмме update. Ее действия начинаются с пересылки образа ЭЛД в памяти в физический порт ЭЛД. Даже эта относительно простая операция требует некоторых пояснений. Естественный вопрос, который должен прийти вам в голову, — это почему бы, желая установить или сбросить бит ЭЛД, не обновлять ЭЛД непосредственно? В ответ надо указать на два обстоятельства. Во-первых, просто записать новый байт в порт ЭЛД нельзя, так как при этом потеряются значения остальных битов; либо мы должны иметь порт ЭЛД с возможностью как записи, так и чтения, либо надо хранить в памяти образ ЭЛД. Поскольку из нашего порта ЭЛД читать нельзя, в памяти предусмотрена ячейка led__store, хранящая копию последнего байта, посланного в порт ЭЛД. Во-вторых, раз уж такая ячейка все равно есть, мы можем сэкономить время в критических циклах обработчика прерываний, обновляя в них только ячейку led__store. Передачу же сообщения на ЭЛД-индикатор передней панели будет осуществлять подпрограмма update в ходе своего выполнения. Все это станет более понятным, когда мы приступим к рассмотрению обработчика прерываний.
Упражнение 11.14. Какие дополнительные (очень несложные) аппаратные средства требуются для того, чтобы можно было читать из порта ЭЛД? Проявите сообразительность, чтобы дополнительная дешифрация адреса получилась простой.
Оставшаяся часть подпрограммы update обновляет массив DISPLAY. Прежде всего из памяти извлекается смещение (число элементов от начала массива) очередного обновляемого элемента. (Для этого было бы неплохо использовать выделенный адресный регистр, но при распределении регистров приоритет был, конечно, отдан обработчику прерываний.) Смещение умножается на 4 (сдвигом влево на 2 бит), чтобы его можно было использовать для индексной адресации в массиве DATA длинных чисел. Переслав в D1 очередной элемент из DATA, мы считываем с управляющей панели текущее значение масштаба изображения и маскируем его, чтобы получить число от 0 до 15. Число 15 ($0F) обозначает автомасштабирование, в то время как меньшие числа определяют фиксированный масштаб в виде степени 2. Мы либо соответствующим образом сдвигаем значение элемента, либо переходим на программный блок автомасштабирования.
Для выполнения автомасштабирования нам надо значение текущего (индексированного с помощью update__offset) элемента DATA разделить на текущее значение из массива NORM (которое говорит, сколько разверток включено в значение DATA), а затем еще раз разделить на ширину канала (которая говорит, сколько выборок было сделано в каждой развертке). Перед любым делением всегда проверяйте на нуль! Наконец, как при сдвиге, так и при автомасштабировании мы должны преобразовать полученное длинное данное со знаком в байт со знаком. В случае автомасштабирования результирующее длинное число всегда находится в диапазоне ±128. В случае фиксированного масштаба, если выбрать масштаб меньше отсчета в наиболее заполненном канале, произойдет переполнение. Лучше всего сделать так, чтобы при переполнении точки, выходящие за верхний край изображения, «прокручивались» в его низ и наоборот. Написав несколько чисел и проиграв с ними разные варианты, вы легко убедитесь, что правильный алгоритм заключается в усечении числа до 8 бит и инвертировании затем старшего бита. Мы реализовали этот алгоритм с помощью команды изменения бита BCNG, после которой выполняется байтовая пересылка (командой MOVE) в массив DISPLAY. Далее мы инкрементируем и сохраняем индекс update__offset и, наконец, выполняем команду RTS.
Обработчик прерываний. Наконец мы добрались до обработчика прерываний — центральной фигуры всей программы. Перед нами четыре точки входа в обработчик, инициируемый прерываниями от таймера; перед нами также простенький обработчик bad__int ложных прерываний, а также и всех остальных векторизованных ошибок и ловушек (табл. 11.5). Займемся ради разминки программой bad__int, а когда не останется отговорок, примемся за обработчик прерываний от таймера.
МП 68008, как уже описывалось выше, распознает прерывания, а также разнообразные «исключения», перечисленные в таблице, и сохранив в стеке текущие PC и SR, осуществляет переход на команду, адрес которой извлекается из вектора, соответствующего данному исключению. Так, если вы попытаетесь разделить на нуль, ЦП сохранит в стеке содержимое счетчика команд и регистра состояния, а затем перейдет на команду, 32-разрядный адрес которой хранится в байтах памяти с абсолютными адресами $014—$017. Точно так же обслуживаются и прерывания, причем для векторов прерываний с полным подтверждением отведены ячейки с адресами $100-$3FF, а для векторов автовекторизуемых прерываний — ячейки $064-$07F. Вы можете выполнять в обработчике прерываний любые действия; завершить их следует командой RTE (возврат из исключения). Чтобы избежать путаницы, ЦП запрещает прерывания после передачи управления обработчику и разрешает их снова при выполнении команды RTE. Если у вас уж слишком закрученный обработчик, вам может понадобиться разрешить прерывания (только более приоритетных уровней) внутри обработчика, что можно сделать, послав соответствующий байт в регистр состояния.
Программа bad__int. Из рис. 11.20 и текста программы 11.3 легко представить ход выполнения программы bad__int, в задачу которой входит упорядоченный сброс выходных сигналов и вывод на ЭЛД какой-то бросающейся в глаза информации. Стартовый адрес этой программы, определяемый компоновщиком после сборки всех настраиваемых строк, загружается (главной программой в процессе начальной загрузки) во все зарезервированные для векторов ячейки (в начале памяти), перечисленные в таблице. Любое исключение или ложное прерывание (т. е. что угодно, кроме прерывания уровня 5) заставляет ЦП выполнить описанную выше процедуру с передачей управления на программу bad__int. Сначала выключается сигнал Z-оси, чтобы исключение, случайно возникшее в середине программного импульса Z-оси, не оставило луч дисплея включенным на полную яркость (к тому же в одной точке). Далее стоит сбросить сигнал на выходе РАЗВЕРТКА и установить сигнал на выходе КОНЕЦ, поскольку в предшествующих измерениях все равно нет смысла.
Теперь проявим остроумие. Пошлем в порт ЭЛД 01Н и войдем в бесконечный цикл, в котором это число циклически сдвигается влево и после биологически заметной задержки снова посылается на ЭЛД. Результатом такой операции будет «шагающий бит» на ЭЛД-индикаторе, картина, которая заставит встрепенуться самого измученного оператора. Поскольку в цикле нет команды RTE, процесс этот будет идти бесконечно. Чтобы снова начать измерения, оператор должен нажать кнопку СБРОС.
Упражнение 11.15. Придумайте более совершенный алгоритм, позволяющий оператору определить, какое исключение привело к сбою. Подсказка: всего имеется немного менее 256 исключений; ЭЛД-индикатор содержит 8 бит. Можете ли вы написать программу, реализующую ваше решение?
Прерывания от таймера: четыре точки входа. Теперь у нас не осталось никаких отговорок. Нырнем. Текст обработчика прерываний входит в программу 11.3; его структурная схема изображена на рис. 11.21.
Рис. 11.21. Структурная схема обработчика прерываний.
Обработчик имеет четыре точки входа, соответствующие различным состояниям прибора. Они обозначены idle, wait__trig, sweep__start и get__data. Программа, в зависимости от общего состояния прибора, автоматически изменяет содержимое вектора прерываний (ячейка $074), связывая прерывание с той или иной точкой входа. Если вы не желаете накапливать данные, вы входите в обработчик в точке idle; на экран выводится одна точка и осуществляется возврат. Если войти в обработчик в точке get__data, программа считывает АЦП, проверяет, не возникли ли состояния «конец ячейки» или «конец развертки» (обрабатывая их соответствующим образом) и обновляет дисплей. При входе в точке sweep__start устанавливается требуемое состояние ЭЛД и выходных сигналов и осуществляется переход в точку get__data.
Наконец, вход wait__trig служит для проверки наличия сигнала внешнего запуска и перехода либо на sweep__strat, либо на idle. В обработчике прерываний имеются и другие метки (например z__pulse), но они не являются входными точками, а служат для переходов внутри программы.
Прерывания от таймера: idle. Учитывая важность обработчика, рассмотрим его во всех деталях. Ранее в главной программе вектор прерываний был настроен на вход idle, чтобы в ожидании запуска образовать изображение на экране. Таким образом, выполнение начинается с метки idle__int. Если вспомнить назначение зарезервированных регистров, понять ход программы не сложно. В D4 хранится индекс очередной точки экрана, требующей регенерации, который мы посылаем в преобразователь Х-координаты ЦАПО (используя косвенную адресацию со смещением, которая быстрее абсолютной). В преобразователь Y-координаты ЦАП1 мы посылаем данное (используя D4 в качестве индекса массива DISPLAY, указатель базы которого находится в А4). D4 инкрементируется (но не проверяется на конец массива) и управление передается генератору импульса Z-оси.
Упражнение 11.16. Объясните, почему можно обойтись без проверки индексного регистра D4 массива DISPLAY после его инкрементирования?
К этому времени Х- и Y-ЦАП уже установились (время установки 1 мкс), поэтому генератор Z-импульса с помощью команды BSET устанавливает бит Z__BLANK (бит 4, см. определения) параллельного порта В, адрес которого, ввиду его частого использования, мы храним в регистре А2. Сбросить бит можно следующей командой, но в этом случае образовался бы слишком короткий (3 мкс) импульс, и изображение было бы бледным (подсветка на 3 мкс каждые 100 мкс). Поскольку, однако, все прерывания завершаются через этот программный блок, мы можем воспользоваться возможностью и сделать полезное дело, одновременно убив время, именно, сообщить таймеру, что он может снять свой запрос на прерывание. Запись в регистр команд и состояния таймера-1 осуществляется с помощью двухэтапного процесса (как это было и в блоке инициализации главной программы): сначала мы посылаем в управляющий регистр микросхемы (адрес $84003) внутренний адрес регистра ($0А), а затем посылаем сам управляющий байт ($20), который интерпретируется микросхемой 8536, как команда на снятие запроса прерывания от таймера-1. Больше до выхода из прерывания ничего делать не нужно, поэтому мы завершаем импульс Z-оси (командой BCLR) и выполняем команду RTE (возврат из исключения). Поместив строки подтверждения прерывания в генератор Z-импульса, мы удлинили импульс подсветки до 10 мкс, с повторением его каждые 100 мкс. Прерывание все равно надо было подтвердить, и мы нашли для этого самое подходящее место. Такая же глюковина использована нами и в другом месте, когда мы в течение аналого-цифрового преобразования посылаем в ЦАП X и Y-координаты точки. Об этом ниже.
Прерывания от таймера: get_data. Эта точка входа используется чаще других, именно, когда усреднитель сигнала выполняет развертку. Мы запускаем АЦП, посылая в его порт байт режима ($03); это число определяет биполярное преобразование в дополнительном коде. Как и раньше, для повышения скорости мы используем косвенную адресацию через регистр A3 (в котором хранится адрес АЦП).
Теперь надо подождать 10 мкс окончания преобразования — прекрасная возможность послать на ЦАП дисплея новую пару X и Y-координат точно так же, как это делается в блоке idle. Эти программные действия заканчиваются на 1 мкс раньше, чем нужно, поэтому мы тянем время с помощью команды NOP (холостая команда), а затем считываем АЦП. Заметьте, насколько это удобнее, чем вводить бит состояния, сигнализирующий о завершении преобразования в АЦП (эта возможность обсуждалась в разд. 11.05); не забудьте, однако, добавить еще несколько команд NOP, если вам захочется увеличить тактовую частоту ЦП.
Мы прочитали из АЦП байт в дополнительном (до 2) коде, но наш массив DATA и накопитель ячейки (D7) используют длинные дополнительные числа. Для получения длинного целого числа дважды выполняется команда ЕХТ (расширение знака). Расширение знака представляет собой просто копирование самого старшего бита числа влево, пока не заполнится большее по длине целое слово; эта операция сохраняет значение целого со знаком (простое заполнение нулями не сохраняет значения числа). Расширенное целое добавляется к накапливаемому содержимому ячейки в D7, а счетчик ширины канала dwell__per__bin (D6) декрементируется. Если в нем еще не нуль, возврат осуществляется через z__pulse, как описано выше. Полное время выполнения программы обработчика в этом случае составляет 32,3 мкс плюс 9 мкс на процедуру прерывания ЦП и еще 5 мкс на команду RTE, всего 46,3 мкс. Таким образом, главная программа имеет более половины процессорного времени на выполнение простой задачи обновления массива DISPLAY.
Если накопление в канале завершилось, обработчик устанавливает счетчик ширины канала, добавляет накопленное значение в D7 к соответствующему элементу массива DATA (на который указывает А5), инкрементирует соответствующий элемент массива NORM (через А6), очищает регистр-аккумулятор (D7), декрементирует счетчик каналов и (если в счетчике каналов не нуль, т. е. развертка не завершилась) переходит на z__pulse. Обратите внимание на использование автоинкрементного режима адресации. Дополнительное время, расходуемое обработчиком на выполнение этих операций, составляет 14,8 мкс.
Если завершилась и развертка, о чем говорит нуль в счетчике каналов D5, обработчик устанавливает указатели, ЭЛД-индикатор и выходные сигналы. Затем проверяется, не была ли нажата кнопка СТОП; такую проверку следует обязательно выполнять в конце (или начале) развертки, чтобы данные всегда усреднились по целому числу разверток. Если кнопка СТОП была нажата, программа переходит на метку stop sweep, в результате чего устанавливаются выход КОНЕЦ и стоп-флаг, а в вектор INT5 загружается адрес входной точки idle.
Если кнопка СТОП не нажималась, программа проверяет, не следует ли завершить измерения ввиду отработки заданного на передней панели числа разверток (число оставшихся разверток хранится в памяти в переменной num__sweeps), поскольку значение 0 обозначает «безостановочная работа», мы сначала проверяем на нуль; если num__sweeps = 0, это значение сохраняется и осуществляется переход на re__trigger, в противном случае значение num__sweeps декрементируется и снова проверяется на нуль. Если теперь оно равно нулю, это значит, что закончилась последняя запланированная развертка; в этом случае осуществляется переход на stop__sweep. Если развертки не исчерпались, выполняется программный блок re__trigger.
Блок re__trigger определяет режим запуска следующей развертки. Если переменная autoloop, установленная программой main после считывания состояния управляющей панели, имеет значение «истина», в вектор INT5 загружается адрес точки входа sweep__start, в противном случае загружается адрес wait__trig.
Заметьте, что в процессе смены вектора нет опасности прерывания, потому что пока ЦП выполняет обработку прерывания, прерывания запрещены; поскольку мы не включаем их в обработчике прерывания, они остаются запрещенными.
Прерывания от таймера: sweep__start и wait__trig. Эти входные точки используются, если следующее прерывание должно начать развертку, либо мы ожидаем импульса внешнего запуска (длительностью на менее 100 мкс!). Соответствующий адрес загружается в вектор INT5 либо в главной программе при нажатии кнопки ПУСК, либо в обработчике прерываний при завершении обработки не последней развертки (в точке re__trigger); по структурной схеме можно проследить, где это делается. Программный блок sweep__start сразу начинает развертку, и его структура проста: зажигается ЭЛД РАЗВЕРТКА, устанавливается выходной сигнал РАЗВЕРТКА, сбрасывается фиксатор бита кнопки СТОП («запоминание 1»), загружается вектор get__data, а затем происходит естественный переход на метку get__data. При последующих прерываниях вход в обработчик прерываний осуществляется через входную точку get__data.
Вход в обработчик через входную точку wait__trig осуществляется, если следующая развертка не должна начаться до получения внешнего сигнала запуска (параллельный порт А, бит 7). Поскольку нажатие на кнопку СТОП должно «пересиливать» запуск, программа сначала анализирует состояние входа СТОП (и переходит при наличии этого сигнала на метку stop__sweep), а затем входа внешнего запуска; если сигнал запуска отсутствует, происходит переход на метку idle, если присутствует - на метку sweep__start.
11.08. Характеристики
Измерительный прибор на основе микропроцессора можно сконструировать таким образом, чтобы весь сбор данных осуществлялся быстрой аппаратурой, а микропроцессор выполнял лишь функции начального запуска и вывода информации. Такой прибор будет работать с максимальной скоростью, определяемой быстродействием аппаратуры, а микропроцессор придаст ему гибкость и облегчит работу с ним. Разумеется, вы платите сложностью и стоимостью аппаратуры; кроме того, гибкость прибора может оказаться невысокой из-за фиксированной аппаратной организации. Если в противоположность такому подходу вы упрощаете аппаратуру и используете процессор для обработки данных в реальном времени, как это сделано в нашем примере, вы можете удешевить аппаратуру и повысить гибкость прибора за счет, возможно, его быстродействия. Во многих случаях, однако, быстродействие не имеет решающего значения, и выбор оказывается однозначным.
В нашем случае базовая частота выборок и, следовательно, ширина канала ограничиваются скоростью работы процессора. Программа обработки любого прерывания должна завершиться до поступления следующего. При проектировании нашего прибора мы оценили необходимый объем обработки и решили, в значительной степени интуитивно, что 100 мкс хватит для ее выполнения. Естественно, уверенности у нас не было, но в случае необходимости мы были готовы пойти на уменьшение частоты выборок.
Посмотрим теперь на числа. Технические характеристики МП 68008, которые представляют собой том объемом 100 страниц, включают таблицы времен выполнения команд (в числе тактов). С помощью этих таблиц мы рассчитали длительности выполнения программных блоков, упоминавшиеся в тексте. Ниже приведены расчетные данные (с учетом операций векторизации и возврата) для обработчика прерываний;
Входная точка · Время выполнения (мкс)
idle 37
det__data 46,3 (внутри канала)
61 (конец канала)
92 (конец развертки, ручной СТОП)
105 (конец развертки, программный останов)
113 (конец развертки, ожидание внешнего запуска)
114 (конец развертки, автозапуск)
sweep__start 61
wait__trig 46 (нет запуска)
69 (запуск)
Большая часть приведенных длительностей не превышает «длительности пульса» усреднителя сигналов (100 мкс), что, конечно, очень хорошо. В трех случаях, однако, время обработки прерывания оказывается больше 100 мкс. Первая ситуация (конец развертки, программный останов) не страшна, поскольку затрата нескольких липших микросекунд после того, как все данные собраны, не имеет значения. Точно так же вряд ли вас обеспокоит вторая критическая ситуация (ожидание внешнего запуска), так как между сигналом внешнего запуска и началом очередной развертки всегда можно допустить некоторое время ожидания. Однако от последней ситуации (конец развертки, автозапуск) можно ожидать неприятностей, так как в режиме автозапуска мы ожидаем значение полного периода, точно равное 256 х ширина канала. В действительности, однако, здесь все в порядке по следующей причине. При использовании усреднителя сигналов в режиме автозапуска внешнее оборудование всегда запускается от усреднителя (для чего и предусмотрен выходной сигнал РАЗВЕРТКА), и если период будет отличаться на долю процента от ожидаемого, никакой беды не будет. Если, однако, вам необходимо завершать обработку за время, меньшее 100 мкс, используйте МП 68008 с тактовой частотой 10 МГц, отчего все длительности окажутся меньше на 20 %; это даст для наихудшего случая значение, меньшее 100 мкс (фактически 91 мкс). Однако при использовании более быстрого процессора не забудьте подправить программу обработчика, чтобы предоставить АЦП достаточное время для преобразования.
В целом, наше предположение, что МП 68008 позволит осуществлять выборку с частотой 10 кГц, оправдалось. Авторам это особенно приятно, так как мы написали все до последнего параграфа, и лишь тогда сами узнали, что были правы. Очевидно также, что безнадежно добиться от нашего прибора частоты 20 кГц без перехода на аппаратную обработку данных.
11.09. Некоторые дополнительные соображения
В процессе разработки прибора мы, сталкиваясь с различными возможными вариантами элементов аппаратного или программного обеспечения, должны были принять какое-то решение. Во многих случаях выбор варианта не был однозначным. Чаще всего, правда, «наилучшее решение» представлялось очевидным, но иногда альтернативный вариант был ничем не хуже; в таких случаях, как правило, мы старались выбрать решение, отличающееся максимальной простотой или иллюстрирующее наиболее употребительную методику (избегая хитроумных приемов, основанных на тонких особенностях аппаратуры), а также приводящее к упрощению программы. В реальной жизни (в противоположность книгам) вполне естественно использовать особенности аппаратуры; естественно также писать сложные программы. Рассмотрим некоторые элементы нашего проекта, допускающие альтернативные решения.
Чтение состояния органов управления с помощью таблицы. В нашей программе предусмотрен программный блок, выполняющий чтение и анализ различных битов с управляющей панели, а также соответствующую установку программных параметров. Это распространенный и удобный способ настройки программы. Имеется, однако, и другое, не менее удобное решение, и при этом допускающее простую модификацию. Организуется короткий цикл опроса битов управляющей панели, при этом адреса портов, расположение битов и соответствующие им настраиваемые переменные программы описываются с помощью таблиц. Поскольку такая методика требует особых разъяснений и в нашем случае, возможно, привела бы к усложнению программы, мы выбрали более простое решение: включение в программу однозначных строк чтения органов управления. Однако в приложениях с большим числом параметров, особенно, если вам может понадобиться изменять назначение или значения входных битов, удобнее использовать табличную методику.
Одновибратор подсветки. Мы использовали для подсветки луча дисплея «программный импульс» параллельного порта, потому что считали необходимым продемонстрировать эту полезную методику. При этом мы особо подчеркнули, что при включенных прерываниях нельзя получить надежный программный импульс. Другая возможность заключается в использовании (вместо бита параллельного порта) аппаратного импульсного генератора, например, микросхемы одновибратора. Такого рода микросхемы, вообще говоря, применять рискованно, однако для нашего случая прекрасно подходит микросхема 8536 СIO фирмы Zilog, содержащая встроенный одновибратор, с которого можно снять выходной сигнал. Этот одновибратор фактически образуется с помощью одного из трех встроенных таймеров, что позволяет программно управлять длиной его импульса (вы даже можете соединить два таймера последовательно и получить более длинный импульс). В нашем приборе используются не все таймеры; и описываемая методика оказывается весьма удобной. С ее помощью сокращается программа обработчика прерываний и возникает возможность оптимальной настройки длительности Z-импульса подсветки.
«Запоминание 1» для кнопки СТОП. При чтении состояния кнопки СТОП мы воспользовались полезным качеством микросхемы 8536, именно, наличием встроенного триггера «запоминания 1». При инициализации микросхемы 8536 можно придать свойство запоминания 1 любому биту входного порта; этот бит затем устанавливается при кратковременном нажатии на кнопку и удерживает это состояние до программного сброса при выполнении цикла записи в этот бит порта. Для нашего случая это очень удобно, потому что нам надо фиксировать нажатие кнопки СТОП только в конце развертки. Длительность развертки может составлять много секунд, и встроенная память позволяет избежать периодического считывания состояния кнопки СТОП; поэтому в нашей программе состояние бита СТОП анализируется только в конце развертки (см. рис. 11.21).
Большинство микросхем параллельных портов не содержит входной памяти, и вам может понадобиться запланировать в программе действия, которых нам удалось избежать. Сделать надо следующее. Прежде всего определите внутренний программный флаг, который можно назвать stop__at__end (останов в конце); в программе это определение следует поставить после stop__flag. Не забудьте сбросить этот флаг перед входом в цикл приема данных; удобно это сделать после считывания состояния управляющей панели. Далее добавьте в цикл update__loop несколько команд, чтобы периодически проверять вход stop__bit и, если кнопка СТОП нажата, установить флаг stop__at__end. Наконец, измените строки обработчика прерывания так, чтобы в конце каждой развертки проверялась не кнопка СТОП, а этот программный флаг.
Упражнение 11.17. Впишите карандашом предлагаемые изменения в листинг программы.
Обработчик прерывания: несколько точек входа или флаги? В программе обработчика прерываний мы предусмотрели несколько входных точек, по одной на каждое возможное состояние прибора (бездействие, ожидание сигнала запуска, начало развертки, накопление данных). Поскольку обработчик прерываний не является вызываемой подпрограммой, и вход в него осуществляется по вектору, программа при каждом изменении состояния изменяет и точку входа, загружая ее адрес в ячейки вектора (в начале памяти). Очевидно, что вместо этого можно иметь в обработчике одну точку входа, а для передачи управления на требуемый программный блок предусмотреть строки анализа флага. В этом случае программа передает обработчику информацию о требуемых действиях путем изменения состояния программного флага (вместо того, чтобы настраивать вектор прерываний). Такой метод отличается простотой, однако программа выполняется медленнее, поскольку при каждом входе в обработчик осуществляются проверки и переходы. Разница, впрочем, не так уж велика, и вы вполне можете изменять функцию драйвера с помощью флагов, если этот способ вам нравится больше.
Последовательный порт: дамп данных и управление ведомым. Как уже отмечалось в разд. 11.06, наш усреднитель сигналов не обладает важным свойством пересылки усредненных данных на другой компьютер. Программа выполнения этой операции не сложна, однако громоздка, так как должна включать процедуры инициализации (как для микросхемы 8536), упаковки данных, а также квитирования, чтобы приемник данных мог инициировать передачу данных и подтверждать их прием.
Если предположить, что связь с другим компьютером осуществляется через последовательный порт, имеет смысл использовать этот порт и в качестве альтернативной управляющей панели, чтобы внешний компьютер мог настраивать параметры и запускать накопление данных. Для этого специальная программа анализа должна «отлавливать» определенные байты, которые компьютер посылает в усреднитель, чтобы получить управление. С помощью дополнительных байтов определяются сами параметры (ширина канала, число разверток и др.), причем диапазоны изменения параметров не ограничиваются количеством фиксированных положений переключателей, как это имеет место в нашем приборе с управляющей панелью. Разумеется, надо предусмотреть программное обеспечение, переключающее прибор на управление от управляющей панели, если из компьютера не поступает запрос на управление. Это позволит нам и на земле погулять, и в рай попасть: простота настройки с помощью ручек на передней панели будет сочетаться с гибкостью компьютерного управления.
Чтение органов управления с плавной регулировкой. В описываемом микропроцессорном приборе нам удалось избежать сложностей, присущих органам управления с плавной регулировкой, так как мы использовали более простые устройства - переключатели, каждый из которых связан с одним из битов параллельного порта. Нежелание разработчиков усложнять себе жизнь привело к появлению неоправданной тенденции полного отказа от органов плавной регулировки, которые заменяются (например, в генераторе с микропроцессорным управлением) парами кнопок «вверх» и «вниз». Возможно, вы, как и мы, испытываете ностальгическое желание плавно покрутить ручки. Наш усреднитель сильно выиграл бы при наличии ручки, позволяющей выбрать определенный канал и вывести на экран его адрес и число отсчетов в нем.
Простейший способ организации плавного управления в микропроцессорном приборе заключается в использовании АЦП, преобразующего напряжение от переменного резистора, укрепленного на передней панели и подключенного между напряжением +5 вольт (или другим, более удобным) и землей. В продаже имеются небольшие дешевые микросхемы 8-битных АЦП, скомпонованных с 8-битными мультиплексорами и дискретизаторами с памятью; обычно у вас остается несколько свободных входов, которые можно использовать для чтения нескольких органов управления на передней панели. Можно даже с помощью АЦП прочитать состояние «-позиционного поворотного переключателя — достаточно подключить его выводы к цепочке из n — 1 резистора равной величины и подать на АЦП выходное напряжение!
Если вам нужно иметь лучшее разрешение, чем обеспечивает простой 8-битный АЦП, подумайте о многооборотном кодировщике. Он укрепляется на передней панели и имеет размер не больше обычного переменного резистора. Кодировщик содержит пару оптических прерывателей, формирующих, по мере вращения ручки, импульсы, сдвинутые по фазе на 90°. Сдвиг импульсов по фазе дает возможность определить, в каком направлении поворачивается ручка (см. рис. 8.97). В отличие от обычного переменного резистора, многооборотный кодировщик не имеет фиксатора, что и позволяет поворачивать его ось на много оборотов. Типичный узел такого рода серии Bourns EN формирует 256 импульсов на один оборот.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК