С. Ахманов, А. Нечаев, Н. Рой, А. Скурихин

Архитектура "Корвета"

"Корвет", как и любой другой компьютер, состоит из процессора, памяти и устройств ввода-вывода (УВВ), называемых еще периферией. С аппаратной точки зрения компьютеры отличаются друг от друга типом процессора, объемом и типом памяти, составом УВВ и способом доступа к ним, быстродействием и т. п. Дополнительные отличия, весьма заметные для пользователя, может вносить имеющееся программное обеспечение (тип операционной системы и др.).

Мы постараемся дать детальное представление о том, как устроен "Корвет" для программиста нижнего уровня, напрямую управляющего аппаратурой. Программного обеспечения будем касаться лишь вскользь, и то главным образом встроенного.

Основой "Корвета" является 8-разрядный микропроцессор КР580ВМ80А (подробнее о нем рассказывается в статье Архитекутра процессора КР580ВМ80А).

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

ПЗУ - важнейший компонент компьютера. Компьютер без программы - беспомощная железяка, но сразу после его включения, когда ОЗУ еще младенчески чисто, запускается программа, хранящаяся в ПЗУ. Как правило, она производит проверку основных узлов компьютера и загружает операционную систему.

Может ли существовать процессор столь "умный", чтобы самостоятельно загрузить какую-либо программу из внешней памяти? Да, такие процессоры существуют, но процедура загрузки в них все равно записана в ПЗУ, только внутреннее, являющееся частью процессора (так называемое ПЗУ микропрограмм).

Способы реализации ПЗУ весьма многочисленны. В доисторические времена, когда компьютеры изготовляли из транзисторов и даже радиоламп, вся внутренняя память делалась на ферритовых кольцах; часть "ферритового" ПЗУ изображена на рис. 1. Количество колец в одном ряду обычно равно разрядности ячейки памяти, в нашем случае 8. Каждое кольцо имеет обмотку считывания, кроме того, через кольца продеты провода, образующие собственно ячейки памяти - каждый провод свою ячейку. Если какой-то бит в данной ячейке должен быть равен 1, провод проходит сквозь кольцо, если 0 - мимо. Благодаря этому при подаче импульса тока на провод в обмотках считывания некоторых колец возбуждаются индуцированные токи, а в некоторых - нет; происходит считывание информации.

TODO: ЗДЕСЬ БУДЕТ РИСУНОК 1

Рисунок 1. ПЗУ на ферритах

Отсюда видно, что процесс записи в такое устройство заключается в продевании проводов через определенные кольца, весьма напоминающем процесс шитья; отсюда и происходит термин "прошивка ПЗУ", сохранившийся до сих нор, хотя сейчас программирование постоянной памяти делается совсем иначе.

Если ПЗУ нужно изготовить в большом числе экземпляров, используют так называемые масочные микросхемы. Их содержимое определяется в процессе конструирования и не может быть изменено, зато они наиболее дешевы.

В программируемые ПЗУ (ППЗУ) информация заносится после их изготовления. В ППЗУ с плавкими перемычками (это, например, микросхемы типа 556РТ) просто пережигают импульсами тока некоторые перемычки, записывая таким образом нули в некоторые биты. Перепрограммировать их после этого нельзя (разве что добавить нулей), они потребляют много энергии, однако имеют высокое быстродействие.

Существуют и такие ППЗУ, которые можно программировать неоднократно, стирая старую информацию - в некоторых ультрафиолетовым светом, направляемым через специальное окошко в корпусе (это микросхемы тина 573РФ), в некоторых - электрическим током (например, 558РР; в подобных микросхемах для считывания используются напряжения порядка 5В, для стирания - 24В, для программирования - 15-20В).

ПЗУ содержит данные, записываемые обычно на заводе при изготовлении компьютера. Программист не может изменить содержимое ПЗУ; более того, изменение содержимого ПЗУ в процессе эксплуатации является неисправностью компьютера и диагностируется тестом начального пуска - в этом случае выдастся сообщение "ПЗУ неисправно".

Объем (количество ячеек) ПЗУ равен 24 Кбайт. Первые 8 Кбайт содержат тест начального пуска, программу инициализации устройств и переменных, драйверы дисплея, клавиатуры, локальной сети и некоторых других устройств, загрузчики с диска и из внешнего ПЗУ. Оставшиеся 16 Кбайт содержат интерпретатор языка Бейсик, который включается, если "Корвет" не может загрузить операционную систему с диска. Подробное описание ПЗУ "Корвета" будет дано позже.

ОЗУ позволяет не только считывать данные из своих ячеек, но и записывать их туда. Именно в ОЗУ загружаются операционная система и программы, которые вы запускаете с диска, локальной сети или магнитофона. Здесь хранятся также все переменные и данные, которые используют программы.

Общий объем ОЗУ в "Корвете" - 64 Кбайт. Этот массив ячеек называют еще основной памятью.

Другой тип устройств, к которым может обращаться процессор - УВВ. Их набор в "Корвете" достаточно широк: алфавитно-цифровой и графический дисплеи, клавиатура, интерфейсы принтера, магнитофона, многофункциональный параллельный, а также контроллеры локальной сети, накопителя на гибких дисках, последовательного интерфейса.

Дисплеи и клавиатура имеют в своем составе довольно большие массивы ячеек. В состав алфавитно-цифрового дисплея входят 1024 9-битовые ячейки, графического - до 192 Кбайт (в школьном варианте обычно используется версия 48 Кбайт), клавиатура включает 512 ячеек (отметим, что ячейки клавиатуры можно только читать).

Остальные устройства управляются через отдельные ячейки, называемые управляющими и периферийными регистрами (не путать с регистрами процессора!). Отметим, что оба дисплея имеют помимо массивов ячеек управляющие регистры.

Регистры устройств в "Корвете" бывают двух типов: доступные и для чтения, и для записи (периферийные, принадлежат дисководу, параллельному и последовательному интерфейсами и т. д.); доступные только для записи (управляющие - к их числу относятся системные и некоторые регистры графического контроллера). Регистров каждого типа - 256, т. е. всего они занимают 512 ячеек.

Если просуммировать все перечисленные ячейки, то получается, что процессор должен различать (адресовать) до 282 Кбайт, а он умеет адресовать только 64К. Где же выход из этой ситуации?

Обычно для расширения адресного пространства процессора используется так называемая страничная организация памяти, например, с помощью введения сегментного регистра. 20-разрядный адрес (т. е. возможность адресовать 1 Мбайт) с его помощью получается так: к 16 разрядам адреса добавляются 4 разряда смещения из сегментного регистра (рис. 2). Другими словами, 1 Мбайт памяти составляется из 16 "страниц" по 64 Кбайт: номер страницы задается в сегментном регистре, а внутри страницы ячейки адресуются непосредственно микропроцессором. Такая схема используется, в частности, в компьютерах IBM PC.

TODO: ЗДЕСЬ БУДЕТ РИСУНОК 2

Рисунок 2. Адресация со смещением

В "Корвете" используется другой способ "ужимания" большого числа ячеек в небольшое адресное пространство процессора. Исходно все адресное пространство занимает ОЗУ (основная память). Все остальные ячейки можно включать в адресное пространство и отключать от него.

Если ячейки какого-то устройства (далее будем называть их просто устройством) включаются в адресное пространство, то соответствующая область ОЗУ отключается, т. е. устройство как бы заслоняет ОЗУ от процессора. Но устройства, в которые можно только писать или из которых можно только читать, лишь частично заслоняют ОЗУ ("под" ячейку "только для чтения" можно записать в ОЗУ данные, "из-под" ячейки "только для записи" данные можно считывать). В связи с этим уместно предостеречь программистов, любящих скидывать ненужные константы в нейтральную, как они считают, область, например в ПЗУ. В "Корвете" эта константа запишется в соответствующую ячейку ОЗУ, даже если ее сверху "накрывает" ПЗУ.

Произвольно подключать и отключать отдельные устройства нельзя - используются только некоторые наборы из всего возможного множества комбинаций. Каждый такой набор называется конфигурацией адресного пространства, а расположение устройств в адресном пространстве - картой памяти. На "Корвете" можно использовать 32 конфигурации адресного пространства.

Для задания той или иной конфигурации адресного пространства в состав системы управления памятью "Корвета" входит системный управляющий регистр. Он наиболее важен и требует очень осторожного обращения: при изменении его содержимого мгновенно меняется карта памяти (представьте, что почувствует турист, заметив, что кто-то подменил карту местности, по которой он путешествует!). Если это произошло в результате необдуманных действий, то машина зависает и приходится делать холодный старт. При этом возможна порча информации, записанной на диски.

В системном регистре для задания номера конфигурации используются биты со 2-го по 6-й (5 битов как раз позволяют задать 32 конфигурации); остальные должны быть установлены в 0.

Часть конфигурации отвечает картам памяти, используемым в известных операционных системах СР/М, TRS 80, МикроДос и др. Отметим, что не все конфигурации памяти используются, а некоторые вовсе не имеют смысла - в них можно войти, но из них нельзя выйти. Появление таких бессмысленных конфигураций объясняется тем, что биты, определяющие номер конфигурации, разбиты на отдельные группы, каждая из которых отвечает на свои системы, и в некоторых комбинациях они оказываются не согласованы.

Возможные карты памяти (области памяти, отводимые под различные устройства в зависимости от кода, записанного в системном регистре) представлены в таблице 1. Для алфавитно-цифрового дисплея (А/Ц), графического дисплея (ГЗУ), периферийных (для чтения-записи) регистров (Perl), управляющих (только для записи) регистров (Рег2) и клавиатуры приведены только начальные адреса их областей, поскольку длины областей фиксированы: для А/Ц - 3FF, ГЗУ - 3FFF, регистров - по FF, клавиатуры - 1FF.

Таблица 1. Карты памяти
Код ПЗУ А/Ц ОЗУ ГЗУ Рег.1 Рег.2 Клав.
00 0-37FF 3C00 4000-FFFF 3B00 3A00 3800
04 0-1FFF 2000-FFFF
08 0-3FFF 4000-FFFF
0000-FFFF
10 0-1FFF FC00 2000-F7FF FB00 FA00 F800
14 0-1FFF FC00 2000-F7FF FB00 FA00 F800
18 0-3FFF FC00 4000-F7FF FB00 FA00 F800
1C FC00 0000-F7FF FB00 FA00 F800
20 0-37FF 3C00 4000-BFFF C000 3B00 3A00 3800
24 0-1FFF 2000-BFFF C000
28 0-3FFF 4000-BFFF C000
2C 0000-BFFF C000
30 0-1FFF 2000-3FFF
8000-FDFF
4000 FE00 FF00
34 0-1FFF 2000-3FFF
8000-FDFF
4000 FE00 FF00
38 0-3FFF 8000-FDFF 4000 FE00 FF00
3C 0000-3FFF
8000-FDFF
4000 FE00 FF00
40 0-5FFF FC00 6000-F7FF FB00 FA00 F800
44 0-1FFF FC00 2000-F7FF FB00 FA00 F800
48 0-3FFF FC00 4000-F7FF FB00 FA00 F800
4C FC00 0000-F7FF FB00 FA00 F800
50 0-5FFF 6000-FDFF FE00 FF00
54 0-1FFF 2000-FDFF FE00 FF00
58 0-3FFF 4000-FDFF FE00 FF00
5C 0000-FDFF FE00 FF00
60 0-5FFF 6000-BEFF C000 BF00
64 0-1FFF 2000-BEFF C000 BF00
68 0-3FFF 4000-BEFF C000 BF00
6C 0000-BEFF C000 BF00
70 0-5FFF 6000-BEFF C000
74 0-1FFF 2000-BEFF C000
78 0-3FFF 4000-BEFF C000
7C 0000-BEFF C000

Конфигурация 0 соответствует карте памяти, принятой в машинах семейства TRS 80 фирмы Tandy. Конфигурация 1СН соответствует карте памяти, используемой в ОС СР/М. МикроДос, в зависимости от версии, использует конфигурацию 5СН или 1СН. Бейсик, зашитый в ПЗУ, использует конфигурацию 40Н.

При изменении карты памяти неприятные сюрпризы чаще всего преподносят прерывания. Они происходят "сами по себе", независимо от наших действий, после чего управление передается туда, где должна находиться программа обработки прерывания. А если мы перед этим изменили карту памяти, этой программы на привычном месте, скорее всего, не окажется. Результат - зависание. Следовательно, при переключении карты памяти надо запретить прерывания (с помощью команды DI).

Прочитать содержимое системного регистра нельзя, следовательно, все манипуляции с картой памяти должны задаваться программистом заранее и быть хорошо продуманными. Нужные части программы не должны оказаться в "отключенной" части памяти. Если программа начинает работать под управлением СР/М, в системном регистре обычно записано число 1СН; поэтому по окончании манипуляций с картой памяти следует (если нужно вернуть управление СР/М) загрузить в системный регистр 1СН и разрешить прерывания (с помощью команды EI).

Системный регистр имеет адрес 7FH на странице управляющих регистров. Это значит, что при использовании карты памяти TRS 80 его абсолютный адрес равен 3A7FH. Тот же адрес в конфигурации ОС СР/М равен FA7FH. Таким образом, если мы хотим изменить карту памяти из СР/М в TRS 80 и обратно, нам потребуется следующая программа:

    SREGC EQU 0FA7FH  ;Адрес системного регистра в СР/М
    SREGT EQU 3A7FH   ;Адрес системного регистра в TRS80
          ORG 4000H   ;Начало ОЗУ TRS80
          DI          ;Запрещение прерываний
          MVI A,0     ;Номер конфигурации TRS80
          STA SREGC   ;Запись нового значения системного регистра
          ...         ;Часть программы, работающая с картой памяти TRS80
          MVI A,1CH   ;Номер конфигурации СР/М
          STA SREGT   ;Запись нового значения системного регистра
          EI          ;Разрешение прерываний

Здесь специально подчеркнуто, что программа, переключающая карту памяти, должна начинаться с адреса, большего или равного 4000Н, поскольку в конфигурации TRS 80 именно с этого адреса начинается ОЗУ.

Переключив карту памяти, нельзя пользоваться никакими функциями BIOS и BDOS. По этой причине не пытайтесь переключать карту памяти из Бейсика - компьютер наверняка зависнет. Программа, занимающаяся этим опасным делом, должна создаваться при полном осознании происходящих в машине процессов. Если вы не знаете ассемблера, лучше не трогайте карту памяти, тем более что ее изменения нужны лишь в небольшом числе случаев - обычно при работе с графикой и "изъятии" данных из ПЗУ. В языках программирования есть специальные библиотеки для выполнения этих функций - пользуйтесь ими.

Как видно из таблицы, конфигураций памяти, ГЗУ может занимать окно размером 16 Кбайт. Позднее мы увидим, что это фундаментальная единица объема графической памяти, определяющая цветовую плоскость. Для использования ГЗУ необходимо переключить карту памяти таким образом, чтобы 16-Кбайтное окно оказалось подключенным к адресному пространству процессора. При этом желательно минимально повредить нашу программу, т. е. сохранить в адресном пространстве те области памяти, где записаны необходимые ее части. Например, если мы работаем под управлением ОС СР/М, т. е. в системном регистре хранится 1СН, то для подключения ГЗУ можно использовать конфигурацию 6СН: ГЗУ будет подключено к верхним 16 Кбайт памяти, нашей программе станут недоступными функции BDOS, BIOS и периферийные регистры. При работе с конфигурацией 40Н, соответствующей Бейсику без графики, удобно подключить графику, используя конфигурацию 60Н. Именно так поступает Бейсик, но не пытайтесь сделать это из Бейсика сами!

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

В качестве устройства отображения информации в "Корвете" используется электронно-лучевая трубка (ЭЛТ). Принцип формирования изображения аналогичен используемому в телевидении. Электронный луч, скользя по экрану, оставляет на нем светящийся след Специальное устройство, называемое блоком разверток, заставляет луч вычерчивать строки: начиная с верхней левой части экрана, он проводит горизонтальную линию, затем возвращается к левому краю, но уже несколько ниже прежнею положении, и опять проводит горизонтальную линию. Таким образом луч пробегает по всей поверхности экрана и затем возвращается в исходное положение (верхний левый край экрана), формируя кадр.

Управляя интенсивностью электронного луча, можно разбивать строки на точки и рисовать таким образом любые Фигуры. Это делает видеосигнал, формируемый контроллерами А/Ц и графического дисплеев. Отметим, что изображение формируется только при прямом ходе развертки - при движении луча слева направо. При обратном ходе, когда луч возвращается в начало следующей строки или в начало кадра, им управляет блок разверток с помощью сигналов вертикальный и горизонтальный бланк, которые отключают луч, так что следа на экране не остается.

Получаемое таким образом изображение называют растровым.

Длительность полной строки (включая обратный ход) составляет 64 мкс и соответствует телевизионному стандарту. В ранних версиях "Корвета" иногда использовалась длительность 65,6 мкс (отход от стандарта был вызван плохим качеством мониторов). Длительность прямого хода горизонтальной развертки (т. е. время, в течение которого формируется изображение строки) - 51,2 мкс. Если его сопоставить с разрешением по горизонтали (512 точек), становится ясно: чтобы отобразить одну точку, надо включить луч на 100 мс.

Число отображаемых строк соответствует разрешению по вертикали и равно 256, таким образом длительность прямого хода вертикалькой развертки составляет 256 * 64 = 16,384 мс. Частота смены кадров - 50,08 Гц, что также близко к телевизионному стандарту. Отметим, что время обратного хода вертикальной развертки - 4096 мкс - довольно велико в масштабе процессора.

Теперь можно перейти к устройству контроллера А/Ц дисплея. Его функциональная схема приведена на рис. 1.

TODO: ЗДЕСБ ДОЛЖЕН БЫТЬ РИСУНОК 1

Рисунок 1. Функциональная схема А/Ц дисплея "Корвета".

Данные с системной шины данных ШД поступают на вход видеопамяти и микросхему КР580ВВ55. На вход видеопамяти поступает сигнал инверсии с триггера инверсии. Мультиплексор адреса выдает на шину видеоадреса ВA адрес данных в видеопамяти, синтезированный из адресов, поступивших с системной шины адреса ША, или состояний счетчиков контроллера ЭЛТ (СО - С5 определяют номер символа в строке, С10 - С13 - номер текстовой строки). С выхода видеопамяти видеоданные ВД поступают через буфер на системную шину данных (если это предписывает сигнал чтения видеопамяти) и знакогенератор. Знакогенератор из видеоданных и состояний счетчиков контроллера ЭЛТ (С6 - С9 определяют номер телевизионной строки в пределах одной текстовой строки) формирует сигналы управления яркостью луча ЭЛТ. Сдвиговый регистр превращает байт данных в последовательность поочередно передаваемых битов. Видеовыход - полноценный видеосигнал, способный управлять лучом черно-белого алфавитно-цифрового монитора. НBlank и VBlank - сигналы гашения луча ЭЛТ при обратном ходе по горизонтали и вертикали; VBlank поступает также на один из выходов порта А.

Известно, что экран А/Ц дисплея способен отобразить 16 текстовых строк (не путать с телевизионными строками), по 64 символа в строке, всего 1024 символа. Уже говорилось, что в состав А/Ц дисплея входят 10249-битовые ячейки. Каждой из них соответствует свое место на экране, и высвечивается на этом месте символ, код которого записан в соответствующую ячейку. В 8 битах можно закодировать любой из 256 символов, а экзотический 9-й бит используется для хранения признака инверсии (определяет, будет ли символ белым на черном фоне или черным на белом).

Следующий важный узел - контроллер ЭЛТ. Он генерирует синхронизирующие и управляющие сигналы, определяет движение электронного луча по экрану и синхронно задает адрес ячейки видеопамяти, соответствующей текущей позиции экрана.

Контроллер ЭЛТ сделан "на жесткой логике", программирования не требует и не допускает, начинает работать сразу после включения питания. Отчасти управлять им, однако, может сигнал MODE (см. рис. 1), меняющий режим отображения. Если его значение 1, то отображаются 32 символа в строке, если 0 - то 64. Управлять же сигналом можно через бит 3 видеорегистра (порт С), но об этом чуть позже.

Следующий элемент функциональной схемы - знакогенератор, формирующий изображения символов на экране.

Экран разбит на так называемые знакоместа - прямоугольники размером 8 точек по горизонтали и 16 по вертикали. Символы А/Ц дисплея могут высвечиваться только в этих прямоугольниках. С каждым знакоместом связана своя ячейка видеопамяти; нулевая ячейка соответствует левому верхнему углу экрана, далее адреса возрастают слева направо и сверху вниз, как это показано на рис. 2. Когда электронный луч пробегает по какому-либо знакоместу, из соответствующей ячейки видеопамяти извлекается код символа, который должен быть отображен. С изображением символа код не имеет ничего общего, он задает адрес таблицы, описывающей изображение. Собственно, набор этих таблиц и есть знакогенератор. Это просто массив байтовых ячеек памяти, каждая из которых несет информацию об одной строке (телевизионной) одного символа, т. е. о 8 из составляющих его точек. После того как байт выбран, дальнейшее преобразование записанной в нем информации производит сдвиговый регистр; но как найти нужный байт?

TODO: ЗДЕСЬ ДОЛЖЕН БЫТЬ РИСУНОК 2

Рисунок 2. Номерация ячеек экрана

Поскольку каждый символ состоит из 16 строк, таблица символа занимает 16 байтов. Чтобы идентифицировать один из них, достаточно 4-битового номера строки (нетрудно догадаться, что его формирует контроллер ЭЛТ). Таблица символа, как уже было сказано, адресуется 8-битным кодом символа, считанным из видеопамяти. Таким образом и получается 12-битный (старшие биты - код, младшие - номер строки) адрес нужного байта.

"Строение" таблицы, описывающей изображение символа, продемонстрировано на рис. 3. В левой части рисунка каждая строка представляет байт таблицы в двоичном представлении, а в правой - отображение на экране. Обратите внимание, что первым отображается старший бит считанного из знакогенератора байта, соответственно на экране он расположен слева.

TODO: ЗДЕСЬ ДОЛЖЕН БЫТЬ РИСУНОК 3

Рисунок 3. Изображение символа

В "Корвете" в качестве знакогенератора используется ПЗУ. Программируется оно на заводе, так что пользователю доступен только фиксированный набор символов. Есть, однако, возможность несколько расширить его. Дело в том, что микросхема ПЗУ, используемая в качестве знакогенератора, имеет емкость 8 Кбайт, в то время как 256 символов занимают, как легко сообразить, 16 * 256 = 4 Кбайт. Следовательно, можно иметь два набора по 256 символов каждый. Правда, их нельзя использовать одновременно. Менять наборы можно, воздействуя на бит 2 видеорегистра. Состоянию 0 этого бита соответствует основной набор. На рис. 1 соответствующий сигнал обозначен FONT; он добавляет 13-й бит к адресу знакогенератора. К сожалению, не все могут воспользоваться вторым набором: в некоторых "Корветах" он не запрограммирован.

Продолжим рассмотрение схемы дисплея. Сигнал с выхода сдвигового регистра поступает на элемент "исключающее или" (XOR), позволяющий управлять инверсией. Если на второй его вход поступает 0 из 9-го бита видеопамяти, то мы видим прямое изображение символа, если 1, то мы видим символ в "негативном" изображении - черным на светлом фоне.

Приведенные сведения нужны в основном для лучшего понимания архитектуры А/Ц дисплея. Теперь же начинается самое интересное - рассказ о том, как процессор может выводить информацию на экран.

Из функциональной схемы видно, что процессор имеет возможность напрямую обмениваться данными с видеопамятью. Ее начальный адрес в адресном пространстве "Корвета" зависит от текущей конфигурации (существуют конфигурации, в которых она недоступна, например 5СН, используемая в некоторых версиях МикроДоса). "Вывод" символа на экран заключается в записи его кода в ячейку видеопамяти, связанную с нужной позицией на экране.

Процессор записывает и считывает информацию байтами, размер же ячейки видеопамяти 9 битов. Доступ к 9-му биту осуществляется через триггер инверсии (см. рис. 1); когда процессор производит запись в видеопамять, в 9-й бит записывается состояние этого триггера.

Работой триггера инверсии управляют биты 4 и 5 видеорегистра. Если бит 5 установлен, а бит 4 сброшен, то триггер инверсии ставится в безусловно сброшенное состояние и все, что записывает процессор, отображается в прямом виде. Если бит 5 сброшен, а бит 4 установлен, то символы отображаются в инверсном виде. Если установлены оба бита, то триггер работает в режиме памяти, т. е. сохраняет свое предыдущее состояние. Впрочем, оно все же может измениться - в результате чтения из видеопамяти: при этом в триггер заносится состояние 9-го бита читаемой ячейки. Используя этот режим, процессор может копировать 9-битовые ячейки байтовыми обращениями, что бывает необходимо, например, при скраитинге (сдвиг экрана вверх или вниз).

Ситуация, когда оба бита сброшены, является запрещенной. Конечно, если она все-таки возникнет, ничего не сломается. В данном случае запрет означает отсутствие смысла.

Узнать состояние триггера инверсии (и проанализировать благодаря этому состояние бита инверсии любой ячейки видеопамяти) процессор может через бит 3 регистра 38Н (порт А на рис. 1). Кстати, к биту 1 этого же регистра подведен сигнал обратного хода вертикальной развертки VBlank. Пользуясь им, можно, например, синхронизировать работу программы с кадровой разверткой или измерять время.

На этом закончим теорию и перейдем к практике. Упражнения помогут вам использовать полученные знания, но сначала несколько общих замечаний.

Программы-примеры написаны преимущественно на Паскале, точнее Экспресс-Паскале (см. статьи М. Ковтуна). Почему не на ассемблере, который обычно используется для программ, управляющих "железом"? Паскаль гораздо понятнее и выразительнее, чем ассемблер, разобраться в описанном им алгоритме значительно легче, а цель именно в этом. При необходимости Паскаль-программу легко трансформировать на любой язык, в том числе и на ассемблер. Последний будет использоваться лишь в тех случаях, когда представление программы на Паскале невозможно (например, по причине недостаточного быстродействия). Кроме того, иногда пример на Паскале будет продублирован примером на ассемблере - если существует прием, значительно повышающий эффективность программы.

Почему не Бейсик? И потому, что алгоритм, записанный на Паскале, понятнее, и потому, что Бейсик имеет больше ограничений при управлении аппаратурой. Во многих случаях Паскаль-программу можно переписать на Бейсике. Если для такой трансформации есть ограничения, они будут оговорены. Сразу укажем первое, довольно общее: поскольку некоторые версии МикроДос имеют базовую конфигурацию памяти 5СН, в соответствующих версиях дискового Бейсика адреса регистров смещены, а видеопамять и вовсе отсутствует в адресном пространстве процессора. Поэтому прямая трансформация возможна только в Бейсик, зашитый в ПЗУ, и в дисковый Бейсик, пригодный для СР/М.

Итак, пример 1. Эта программа позволит вам просмотреть содержимое знакогенератора. Компиляцию лучше проводить в память (это ускорит работу); записывать исходные тексты на диск можно, но не обязательно.

    {Пример 1}
    program CharSet:
    const VideoRamBase = #FC00;         {начало видеопамяти в конфигурации 1CH}
    var   i      :integer;
    begin
        ClrScr;                         {очистка экрана}
        for i := 0 to 255 do            {перебор всех возможных кодов символов}
        mem[VideoRamBase+i+i] := i;     {запись этих кодов в видеопамять}
    end.

Запустите эту программу и объясните себе, почему возникла такая картинка. Обязательно во всем докопайтесь до сути.

Мы говорили, что знакогенератор содержит два набора символов. Следующий пример позволит убедиться в этом. Если второй набор в вашем компьютере отсутствует, вы увидите белый экран.

    {Пример 2}
    program CharSets:
    const VideoRegister = #FB3A;        {адрес видеорегистра}
          Font          =     4;        {маска бита фонта}
    
    procedure CharSet:
    const VideoRamBase = #FC00;         {начало видеопамяти в конфигурации 1CH}
    var   i      :integer;
    begin
        ClrScr;                         {очистка экрана}
        for i := 0 to 255 do            {перебор всех возможных кодов символов}
        mem[VideoRamBase+i+i] := i;     {запись этих кодов в видеопамять}
    end;

    begin                               {тело программы}
        CharSet;                        {рисуем все знаки}

        while ReadKey <> #27 do   {если ESC, заканчиваем}
            mem[VideoRegister] := mem[VideoRegister] xor Font; {переключаем фонт, не трогая остальные биты видеорегистра}
        
        mem[VideoRegister] := mem[VideoRegister] xor #FF; {восстановим основной фонт}
    end.

Здесь производится запись в видеорегистр; скажем несколько слов о работе с ним и с регистром 38Н.

Оба эти регистра являются портами микросхемы параллельного интерфейса КР580ВВ55, включенного в состав регистров РЕГ1 (возможны чтение и запись) и имеющего смещение (адрес относительно начала области РЕГ1) 38Н. Это означает, что в конфигурации 1СН, принятой в СР/М, базовой адрес этого интерфейса равен 0FB38H. Этот адрес есть адрес порта А, настроенного на ввод в режиме 0; мы называем его регистром 38Н. Функцию видеорегистра выполняет порт С, настроенный на вывод в режиме 0. В конфигурации 1СН он имеет адрес OFB3AH.

Если мы что-либо записываем в видеорегистр, соответъ твующие сигналы мгновенно появляются на выводах интерфейса, тем самым воздействуя на электрические цепи. При чтении из него читается та информация, которую мы записали. Зак в любой момент можно узнать, в каком состоянии находятся те или иные сигналы. Последнее используется весьма активно.

Отметим, что порт С допускает побитовую установку через регистр управления. Мы этой возможностью пользоваться не будем, а желающих разобраться с этим режимом отсылаем к описанию микросхемы КР580ВВ55.

Регистр 38Н выполняет функцию ввода ряда сигналов. Попытка что-либо записать в него не приводит ни к каким результатам. При чтении из него процессор получает "слепок" состояния выводов порта А интерфейса.

А теперь попробуем переключить режим отображения (16*64 и 16*32). Программа, делающая эту операцию, отличается от предыдущей лишь номером бита, на который надо воздействовать.

    {Пример 3}
    program Modes:
    const VideoRegister = #FB3A;        {адрес видеорегистра}
          Font          =     8;        {маска бита режима}

    procedure CharSet:
    const VideoRamBase = #FC00;         {начало видеопамяти в конфигурации 1CH}
    var   i      :integer;
    begin
        ClrScr;                         {очистка экрана}
        for i := 0 to 255 do            {перебор всех возможных кодов символов}
        mem[VideoRamBase+i+i] := i;     {запись этих кодов в видеопамять}
    end;

    begin                               {тело программы}
        CharSet;                        {рисуем все знаки}

        while ReadKey <> #27 do   {если ESC, заканчиваем}
            mem[VideoRegister] := mem[VideoRegister] xor Font; {переключаем режим, не трогая остальные биты видеорегистра}

        mem[VideoRegister] := mem[VideoRegister] xor #FF; {восстановим основной режим (16*64)}
    end.

Маленький фокус: изменим в программе одну цифру, и широкие символы режима 16X32 пропадут с экрана.

    {Пример 4}
    program Modes:
    const VideoRegister = #FB3A;
          Font          =     8;

    procedure CharSet:
    const VideoRamBase = #FC01;
    var   i      :integer;
    begin
        ClrScr;
        for i := 0 to 255 do
        mem[VideoRamBase+i+i] := i;
    end;

    begin
        CharSet;

        while ReadKey <> #27 do
            mem[VideoRegister] := mem[VideoRegister] xor Font;

        mem[VideoRegister] := mem[VideoRegister] xor #FF;
    end.

Происходит это потому, что в режиме 16*32 могут использоваться, точнее высвечиваются, только четные адреса видеопамяти - ведь для широких символов есть только 512 знакомест и соответственно достаточно вдвое меньшего числа ячеек памяти.

Следующие два примера помогут научиться выводить текст в произвольное место экрана, они различаются способами задания позиции экрана. В примере 5 явно присутствуют позиция в строке и номер строки, пример 6 демонстрирует доступ к экрану непосредственно по адресу. Нажатие клавиши ESC прекращает работу программ.

    {Пример 5}
    program OutXY:
    const VideoRamBase = #FC00;
          MaxX         =  63;
          MaxY         =  15;
          MaxCod       = 255;
    var   x,       {для хранения координаты X}
          y,       {для хранения координаты Y}
          c: byte; {для хранения кода символа}
    begin
        ClrScr;
        Randomize; {инициализируем генератор случайных чисел}
        while ReadKey <> #27 do
        begin
            x := Random(MaxX);
            y := Random(MaxY);
            c := Random(MaxCod);
            mem[VideoRamBase+x+64*y] := c;
        end;
    end.
    {Пример 6}
    program OutXY:
    const VideoRamBase = #FC00;
          MaxAddr      =  1023;
          MaxCod       =   255;
    var   Adr: integer; {для хранения относительного адреса}
          c:   byte;
    begin
        ClrScr;
        Randomize; {инициализируем генератор случайных чисел}
        while ReadKey <> #27 do
        begin
            Adr := Random(MaxAddr);
            c   := Random(MaxCod);
            mem[VideoRamBase+Adr] := c;
        end;
    end.

Потренируемся в управлении инверсией. Пример 7 покажет, как воздействуют биты 4 и 5 видеорегистра на вывод знаков на экран (клавиши F1, F2 и F3 управляют этими битами).

    {Пример 7}
    program TestInv:
    const VideoRamBase  = #FC00; {начало видеопамяти в конфигурации 1CH}
          VideoRegister = #FB3A;
          MaxAddr       =  1023;
          F1            = 80;
          F2            = 81;
          F3            = 82;
          ESC           = 58;
    var   Adr: integer; {для хранения относительного адреса}
          a,s,m: byte; {для хранения кода символа}
    begin {тело программы}
        ClrScr;
        Adr := 0;
        repeat
            ReadKeyExt(a,s,m);
            case s of
                {выключаем инверсию не трогая остальные биты видеорегистра}
                F1: mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
                F2: mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $10;
                F3: mem[VideoRegister] := mem[VideoRegister] OR $30;
                ESC: exit; {если ESC, заканчиваем}
            else
                if a > 31 then 
                begin
                    mem[VideoRamBase+Adr] := a; {запись этих кодов в видеопамять}
                    if Adr < MaxAddr then 
                        Adr := adr + 1
                    else
                        Adr := 0;
                end;
            end;
        until false;
    end.

Отсюда не очень ясно что делает клавиша F3,- кажется, что она ничего не меняет. Чтобы наглядно продемонстрировать ее действие, изменим программу так. чтобы клавиши F4 и F5 сдвигали содержимое экрана вверх и вниз соответственно. Набрав программу, разберитесь, как зависит процесс сдвига экрана от того, какая из клавиш F1 - F3 была перед этим нажата.

    {Пример 8}
    program TestInv:
    const VideoRamBase  = #FC00; {начало видеопамяти в конфигурации 1CH}
          VideoRegister = #FB3A;
          MaxAddr       =  1023;
          OurRow        = VideoRamBase + 64 * 8;
          F1            = 80;
          F2            = 81;
          F3            = 82;
          F4            = 83;
          F5            = 84;
          ESC           = 58;
          SPACE         = 32;
    var   Adr: integer; {для хранения относительного адреса}
          a,s,m: byte; {для хранения кода символа}
    begin {тело программы}
        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
        for adr := VideoRamBase to VideoRamBase+MaxAddr do
            mem[Adr] := SPACE; {очищаем экран}
        for adr := OutRow to OutRow+20 do
            mem[Adr] := ord('*');
        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $10; {включаем инверсию}
        for adr := OutRow+20 to OutRow+40 do
            mem[Adr] := ord('*');
        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
        for adr := OutRow+40 to OutRow+60 do
            mem[Adr] := ord('*');
        repeat
            ReadKeyExt(a,s,m);
            case s of
                {выключаем инверсию не трогая остальные биты видеорегистра}
                F1: mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
                F2: mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $10;
                F3: mem[VideoRegister] := mem[VideoRegister] OR $30;
                F4: begin
                        for adr := 0 to MaxAddr-64 do
                            mem[VideoRamBase+Adr] := mem[VideoRamBase+Adr+64];
                        for adr := MaxAddr-64 to MaxAddr do
                            mem[VideoRamBase+Adr] := SPACE; {очищаем последнюю строку}
                    end;
                F4: begin
                        for adr := MaxAddr-64 downto 0 do
                            mem[VideoRamBase+Adr+64] := mem[VideoRamBase+Adr];
                        for adr := 0 to 63 do
                            mem[VideoRamBase+Adr] := SPACE; {очищаем первую строку}
                    end;
                ESC: exit; {если ESC, заканчиваем}
            end;
        until false;
    end.

А теперь подумаем, как можно управлять курсором. У "Корвета" курсором обычно служит инверсное знакоместо. Если текст на экран выведен в обычном, прямом, виде, проблем нет: для организации курсора достаточно приемов, использованных в примере 7. Если же часть текста выведена в инверсном виде, возникает необходимость модифицировать курсор в зависимое ти от его местоположения (делать его инверсным или прямым). Таким образом, прежде чем высветить курсор, нужно определить состояние бита инверсии данного знакоместа. Для этого воспользуемся регистром 38Н. В программе 9 курсор "слушается" стрелок влево и вправо; ESC, как обычно, выход из программы.

    {Пример 9}
    program TestInv:
    const VideoRamBase  = #FC00; {начало видеопамяти в конфигурации 1CH}
          VideoRegister = #FB3A;
          MaxAddr       =  1023;
          OurRow        = VideoRamBase + 64 * 8;
          Reg38H        = $FB38;
          InvBit        =  8;
          LEFT          = 68;
          RIGHT         = 70;
          ESC           = 58;
          SPACE         = 32;
    var   Adr: integer;
          a,s,m: byte;
          PosCursor,OldPosCursor:integer;
    
    procedure InvToggle(Pos:integer); {включает/выключает курсор}
    var c:byte;
        inv:boolean;
    begin
        mem[VideoRegister] := mem[VideoRegister] OR $30; {включаем "память"}
        c := mem[OurRow + Pos]; {прочитали символ из текущей позиции}
        Inv := (mem[Reg38H] AND InvBit) = InvBit; {состояние инверсии}
        if Inv then
            mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20 {выключаем инверсию}
        else 
            mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $10; {включаем инверсию}
        mem[OurRow + Pos] := c; {вернули символ в инверсном виде}
    end;

    begin {тело программы}

        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
        for adr := VideoRamBase to VideoRamBase+MaxAddr do
            mem[Adr] := SPACE; {очищаем экран}
        for adr := OutRow to OutRow+20 do
            mem[Adr] := ord('*');
        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $10; {включаем инверсию}
        for adr := OutRow+20 to OutRow+40 do
            mem[Adr] := ord('*');
        mem[VideoRegister] := (mem[VideoRegister] AND $CF) OR $20;
        for adr := OutRow+40 to OutRow+60 do
            mem[Adr] := ord('*');
        PosCursor := 0;
        OldPosCursor := 0;
        InvToogle(PosCursor);
        repeat
            ReadKeyExt(a,s,m);
            case s of
                LEFT: if PosCursor > 0 then Dec(PosCursor);
                RIGHT: if PosCursor < 63 then Inc(PosCursor);
            end;
            if PosCursor <> OldPosCursor then
            begin
                InvToogle(OldPosCursor); {погасили курсор на прежнем месте}
                InvToogle(PosCursor); {зажгли курсор на новом месте}
                OldPosCursor := PosCursor; {запомнили где это сделали}
            end;
        until s = ESC; {если ESC заканчиваем}
        mem[VideoRegister] := (mem[VideoRegister] :=  AND $CF) OR $20; {при выходе выключаем инверсию}
    end.

Следующие два примера продемонстрируют использование возможности читать состояние сигнала VBlank (бит 1 регистра 38Н). Сначала испробуем синхронизоваться с разверткой монитора. Алгоритм работа программы прост: дожидаемся обратного хода вертикальной развертки и выводим символ Ж в верхнюю строку экрана. Снова дожидаемая, теперь уже прямого хода развертки, затем делаем небольшую задержку (ее можно изменять) и, наконец, выводим в то же место экрана новый символ - X.

Клавиши ВВЕРХ и ВНИЗ плавно меняют величину задержки. Наберите эту программу и, меняя задержку, получайте ее воздействие на экран. Не правда ли, интересный эффект?

    {Пример 10}
    program TestBlank:
    const VideoRamBase  = #FC00;
          VideoRegister = #FB3A;
          OurPos        = VideoRamBase + 32;
          Reg38H        = $FB38;
          BlankBit      =     2;
          UP            =    72;
          DOWN          =    66;
          ESC           =    58;
          SPACE         =    32;
    var   ValDisplay: integer; {величина задержки}
          a,s,m: byte; {для хранения кода символа}

    Function TestBlank:boolean;
    begin
        TestBlank := (mem[Reg38H] AND BlankBit) = 0;
    end;

    procedure Del(count:integer);
    begin
        for i := 0 to count do;
    end;

    begin {тело программы}
        ClrScr;
        ValDelay := 8;
        repeat
            repeat
            until TestBlank; {ждем начала вертикального бланка}
            mem[OurPos] := ord('Ж');
            repeat
            until TestBlank; {ждем начала вертикального бланка}
            Del(ValDelay);
            mem[OurPos] := ord('Х');
            if keypressed then 
                ReadKeyExt(a,s,m)
            else s := 0;
            case s of
                UP:   Inc(ValDelay);
                DOWN: Dec(ValDelay);
                ESC: exit;
            end;
        until false;
    end.

Теперь посмотрим, какой модификации ваш компьютер. Ранее говорилось, что у разных моделей может быть разная длительность горизонтальной развертки и, как следствие, разная длительность кадра. Отличаются они незначительно, примерно на 0,1 %, но различие все же можно зафиксировать. Для этого кроме компьютера нам понадобятся часы.

Наберите следующую программу и запустите ее, точно зафиксировав время начала работы. Ровно через час (3600 с, с точностью до секунды) нажмите ПРОБЕЛ. Выведенное число даст частоту кадров, если его поделить на 3600. Частота, близкая к 50,08 Гц,- признак новой модели, 50,02 Гц - старой.

Обратите внимание на то. что все сказанное об обратном ходе справедливо и для графического экрана.

    {Пример 11}
    program TestScan:
    const Reg38H        = $FB38;
          BlankBit      =     2;
    var   Count: LongInt; {счетчик кадров}

    Function TestBlank:boolean;
    begin
        TestBlank := (mem[Reg38H] AND BlankBit) = 0;
    end;

    begin {тело программы}
        ClrScr;
        WriteLn('Засеките время и нижмите ПРОБЕЛ');
        if ReadKey = '' then ;
        WriteLn('Пошел счет... Через 3600 с нажмите ПРОБЕЛ');
        Count := 0;
        repeat
            repeat
            until TestBlank; {ждем начала вертикального бланка}
            repeat
            until NotTestBlank; {ждем конец вертикального бланка}
            Inc(Count);
        until KeyPressed;
        WriteLn('Показания счетчика = ', Count);
    end.

Графический дисплей

Основа графического дисплея (его упрощенная функциональная схема приведена на рис. 1) - графическое запоминающее устройство. Контроллер ЭЛТ, как и в случае А/Ц дисплея, формирует все необходимые управляющие сигналы, в том числе и адрес отображаемой ячейки памяти. Байт, считанной им из видеопамяти поступает в сдвиговый регистр и преобразуется в восемь последовательных (по горизонтали) точек экрана (старший бит отображается слева!).

TODO: Здесь должен быть рисунок 1.

Рисунок 1. Упрощенная функциональная схема графического дисплея

Напомним, что графический экран "Корвета" состоит из 512*256 точек; значит для его заполнения необходимо (512*256)/8=16384 ячеек памяти. Именно такой объем адресного пространства занимает графическая память.

Правила отображения графической памяти на экран аналогичны правилам А/Ц дисплея, т. е. в левом верхнем углу отображается нулевой байт; номера отображаемых ячеек возрастают (через каждые 8 точек экрана) слева направо и сверху вниз. В каждой строке отображается 512/8=64 байта (рис. 2).

TODO: Здесь должен быть рисунок 1.

Рисунок 2. Отображение графической памяти

Но этого достаточно только для одноцветного изображения, а у "Корвета", как мы знаем, каждая точка графического экрана может отображаться любым из восьми цветов. Следовательно, нужен не один, а три бита на точку!

Все верно. Поэтому существует не один, а три одинаковых банка графической памяти по 16 Кбайт, причем в режиме отображения все они работают синхронно. Каждый банк образует свою графическую плоскость, в формировании любой точки экрана каждая плоскость участвует одним своим битом, и, если мы хотим зажечь точку, например, желтого цвета, нам необходимо соответствующий этой точке бит плоскости 0 установить в 0, а биты плоскостей 1 и 2 в 1. Для этого можно:

Именно так происходило бы зажигание точки, если бы архитектура дисплея соответствовала рис. 1; времени на эти манипуляции потребовалось бы очень много. Для ускорения процесса вывода графической ичфор мации "Корвет" имеет некоторые аппаратные ухищрения (они повышают эффективность работы графикой раз в 10): во-первых, запись байта осуществляется сразу во все три плоскости; во-вторых, записываются только те биты, которые следует изменить.

Оба механизма реализуются сходными способами - с использоганием образцов-масок. Во втором случае, чтобы не считывать данные из ГЗУ перед их записью, а прямо записывать только нужные биты, используется маска разрешения записи. Ее роль играет байт данных, поступающий из процессора. Нули в его битах блокируют запись, единицы разрешают, так что в соответствующем байте ГЗУ при записи меняются только некоторые биты.

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

Таким образом, для высвечивания точки выполняется не девять операций, а только две - занесение кода в регистр цвета и запись байта с соответствующей единичкой в ГЗУ. Если же учесть, что цвет, как правило, меняется не часто, а среди соседствующих по одному байту восьми точек высвечиваются порой и две, и три, и все восемь, то оказывается, что в большинстве случаев одной операцией воздействуют на несколько точек. Отсюда и заметный выигрыш по времени.

Таблица 1. Регистр цвета
Бит Режим цвета Плоскостной режим
7 Режим (1 - цвета, 0 - плоскостной)
6
5
4
Код цвета для сравнения Разрешение чтения плоскостей
3
2
1
Код цвета для записи Разрешение записи в плоскость - да, 1 - нет
0 Не играет роли Записываемые данные
TODO: ЗДЕСБ ДОЛЖЕН БЫТЬ РИСУНОК 2

Рисунок 2. Функциональная схема графического дисплея.

Здесь: Din - записываемые графические данные (data in); WE - сигнал разрешения записи (write enable); WED - маска разрешения записи (write enable data); BP - сигналы с вицеорегистра; ГрА - адрес в ГЗУ; LА - адрес в просмотровой таблице (от look tip table address); ВГД - видеографические данные.

Компаратор сравнивает значения троек битов, поступающих с трех плоскостей ГЗУ, со значениями битов кода цвета.

Мультиплексор адреса просмотровой таблицы в зависимости от ситуации пропускает в качестве адреса к таблице либо биты с шины данных (при обращении к таблице процессора), либо биты с выходов плоское сей ГЗУ и А/Ц (при осуществлении процесса отображения данных на экране).

К сожалению, такой режим доступа к ГЗУ (он называется цветовым) не позволяет обращаться к отдельным байтам (а это бывает необходимо, если мы, например, используем одну из плоскостей для хранения не графических, а обычных данных). Поэтому реализован и другой режим (плоскостной). Он включается записью 0 в бит 7 регистра цвета.

В плоскостном режиме биты 1-3 регистра цвета превращаются в биты разрешения записи в отдельные плоскости: значение 0 (внимание!) разрешает запись в соответствующую плоскость, а 1 - запрещает.

Данные, поступающие из процессора, воздействуют на процесс записи так же, как в режиме цвета, т. е. разрешают или запрещают запись в биты заданного байта ГЗУ (это несколько неудобно, но зато позволяет не усложнять схему дисплея): какая именно цифра, 0 или 1, записывается, определяет содержимое бита 0 регистра цвета.

Приведем пример: запись в плоскостном режиме произвольного байта (обозначим его С) по адресу А в плоскости 0 ГЗУ:

Итак, с записью в ГЗУ мы разобрались. Пои чтении оттуда тоже есть свои хитрости, в частности два режима обращения - плоскостной и цвета.

Чтение в графических операциях требуется довольно редко, чаще всего при заливке (закрашивании нарисованного контура). Поскольку эта операция является еще и самой длительной, чтение в режиме цвета оптимизировано в расчете на ее выполнение: оно выдает результат сравнения кода цвета заданной точки экрана и содержимого битов 4-6 регистоа цвета (если код цвета точки совпадает с битами 4-6, то соответствующий бит читаемого байта будет 1, в противном случае - 0). Таким образом процессор получает результат сравнения сразу по восьми точкам.

Основную работу по закраске контура будут делать всего две операции: чтение байта из графической памяти и проверка выхода на границу; если 0 (отсутствие границы), то запись байта в графическую память (закрашивание). Если же считанный байт не 0, значит, достигнута граница контура.

При чтении в плоскостном режиме биты 4-6 регистра цвета выполняют функцию разрешения чтения: если бит установлен в 1, чтение из соответствующей плоскости разрешено, если 0 - запрещено. Одновременно можно разрешить чтение из нескольких плоскостей; в этом случае получаемый байт есть результат операции поразрядного ИЛИ между считанными из каждой плоскости байтами.

И наконец, последний механизм воздействия на изображение. Это просмотровая таблица (на рис. 3 она изображена в правом нижнем углу), позволяющая изменять соответствие реальных цветов цветовым кодам. Например, коду 5 (логическому цвету 5) может быть поставлен в соответствие любой из восьми реальных цветов, в том числе и черный.

Просмотровая таблица - это 16 4-разрядных ячеек памяти. На нее поступает четыре потока информации: из трех плоскостей ГЗУ и А/Ц дисплея. Из этих четырех битов формируется адрес, опредетяющий номер ячейки таблицы, содержимое которой будет вывещ но на экран в очередной его точке. Адрес формируется так:

Содержимое каждой ячейки таблицы, в свою очередь, задает реальный цвет:

Если мы хотим получить некий цветовой эффект, изменяя содержимое просмотровой таблицы, нам необходимо записать новое значение в ячейку, соответствующую требуемому коду логического цвета, через специальный регистр (назовем его регистром просмотровой таблицы). Он имеет адрес FBH на странице управляющих регистров. В его биты 0-3 записывается номер ячейки просмотровой таблицы, в биты 4-7 - код физического цвета, записываемый в ячейку (бит 7 задает интенсивность (яркость) изображения, бит 6 - наличие красного, бит 5 - зеленого, бит 4 - синего цвета).

Так, если мы хотим, чтобы точка, которой назначен логический цвет 5, была зеленого цвета, нужно записать в регистр просмотровой таблицы 00100101В. Если мы хотим сделать ее ярко-зеленой, нужно записать 10100101В.

Теперь мы знакомы со всеми тонкостями устройства графического дисплея. Впрочем, есть у него еще одна особенность. ГЗУ может быть собрано из микросхем двух типов: емкостью 16 Кбит либо 64 Кбит. В последнем случае объем ГЗУ достигает 192 Кбайт. Это очень полезное добавление, которое можно использовать, в частности, в качестве электронного диска. К сожалению, оно встречается довольно редко, поскольку стоимость микросхем емкостью 64 Кбит более высока.

В ГЗУ увеличенного объема можно организовать четыре независимые графические страницы; каждая из них может хранить свое изображение, причем переключаются они мгновенно. Более того, можно одну страницу отображать на экране, а на другой готовить следующий кадр.

Как управлять страницами графической памяти, можно понять из рис. 3. Биты 0 и 1 видеорегистра (он уже встречался нам при описании А/Ц дисплея) задают номер отображаемой страницы, а биты 6 и 7 - к какой странице в данный момент обращается процессор.

Пора приступать к примерам. Напомним, что обращение к ГЗУ требует переключения карты памяти, вследствие чего переписывать последующие программы на Бейсике нельзя, а при работе с Express Pascal следует соблюдать определенные правила. Для простоты условимся запускать программу только из среды Express Pascal (компиляция в память). Кроме того, на время переключения карты памяти необходимо запрещать прерывания.

При обращении к ГЗУ нам понадобится регистр цвета. Он относится к группе регистров "только для записи" и в конфигурации 1СН (конфигурация СР/М) имеет адрес OFABFH.

Для начала - традиционное развлечение: зажигание отдельных точек (звездное небо), но не обычным путем, через функции языка программирования, а непосредственным воздействием на ГЗУ.

    {Пример 12, звездное небо}
    program Stars:
    const GrRamBase     = $4000;
          SysRegister   = $FA7F; {адрес системного регистра}
          ColorRegister = $FABF; {регистр цвета}
          SysGrRegister = $FF7F; {адрес системного регистра в конфигурации $3C}
          SysConfig     =   $1C; {конфигурация CP/M}
          GraphCoinfig  =   $3C; {конфигурация при включенной графике}
          ESC           =    58;
          SPACE         =    32;
          iDI           =   $F3;
          iEI           =   $FB;
          Convert:array[0..7] of byte = ($80,$40,$20,$10,8,4,2,1); {для преобразования номера точки}
    var Addr  : integer;
        x,y   : integer; {для координат}
        Color : byte;
        Mask  : byte;

    begin {тело программы}
        ClrScr;
        ClrGScr;
        Randomize;
        repeat
            x := Random(512);
            y := Random(256);
            Color := Random(8);
            mem[ColorRegister] := $80 OR (Color shl 1); {цвет}
            Mask := Convert[x and 7];
            Addr := GrRamBase+y*64+(x shr 3);
            inline (iDI); {запретили прерывания}
            mem[SysRegister] := GraphCoinfig;
            mem[Addr] := Mask;
            mem[SysGrRegister] := SysConfig;
            inline (iEI); {разрешили прерывания}
        until KeyPressed;
    end.

ПРОДОЛЖЕНИЕ СЛЕДУЕТ В ВЫПУСКЕ 5,6-1992