Главная программа: инициализация. Хватит нам ходить вокруг до около. Давайте рассмотрим подетальнее задачи, выполняемые программой. Сначала взгляните на главную программу, изображенную на рис. 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 (ложное прерывание) (вдруг произойдет деление на нуль, ложное прерывание и т. д.); мы загружаем в них адрес программы, которая зажигает ЭЛД определенным образом (далее будет видно, каким именно).