Для выполнения автомасштабирования нам надо значение текущего (индексированного с помощью update__offset) элемента DATA разделить на текущее значение из массива NORM (которое говорит, сколько разверток включено в значение DATA), а затем еще раз разделить на ширину канала (которая говорит, сколько выборок было сделано в каждой развертке). Перед любым делением всегда проверяйте на нуль! Наконец, как при сдвиге, так и при автомасштабировании мы должны преобразовать полученное длинное данное со знаком в байт со знаком. В случае автомасштабирования результирующее длинное число всегда находится в диапазоне ±128. В случае фиксированного масштаба, если выбрать масштаб меньше отсчета в наиболее заполненном канале, произойдет переполнение. Лучше всего сделать так, чтобы при переполнении точки, выходящие за верхний край изображения, "прокручивались" в его низ и наоборот. Написав несколько чисел и проиграв с ними разные варианты, вы легко убедитесь, что правильный алгоритм заключается в усечении числа до 8 бит и инвертировании затем старшего бита. Мы реализовали этот алгоритм с помощью команды изменения бита BCNG, после которой выполняется байтовая пересылка (командой MOVE) в массив DISPLAY. Далее мы инкрементируем и сохраняем индекс и, наконец, выполняем команду 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 (в котором хранится адрес АЦП).