Особенности программирования внешних устройств
в учебной микропроцессорной системе
Введение
В конце 40-х – начале 50-х годов XX века американский учёный Джон фон Нейман сформулировал свои знаменитые принципы организации и построения электронных вычислительных машин (ЭВМ). Эти принципы не потеряли своего значения и в наши дни – большая часть современных компьютеров организована и функционирует согласно этим принципам.
Процессор неймановского компьютера последовательно читает из памяти команду за командой и выполняет их, производя тем самым обработку данных. Но эта обработка окажется бесполезной для конечного пользователя, если вычислительное устройство не сможет «общаться» с внешним миром – принимать данные для обработки и выдавать результат. Для выполнения этих действий существуют многочисленные устройства ввода-вывода. Однако, кроме самих этих устройств, в микропроцессорной системе должны существовать архитектурные решения, обеспечивающие передачу данных между ядром системы и конкретной периферией.
1. Порты ввода-вывода
Обмен данными между микропроцессорной системой и внешними устройствами (ВУ) можно организовать различными способами. Можно, например, использовать для этой цели часть адресного пространства, предназначенного для адресации ячеек оперативной памяти (ОЗУ), как это было принято в отечественной микро-ЭВМ «Электроника-60» и её зарубежных предшественниках – PDP-11, LSI-11 фирмы DEC. Суть этого способа в следующем.
Каждая ячейка ОЗУ, как известно, имеет свой адрес. Пусть диапазон этих адресов 0…65h…FFFFh), что соответствует памяти (или её участку) 64 Кбайт при однобайтовых ячейках. Предположим, что ячейки ОЗУ реально занимают лишь диапазон 0000h…FBFFh, что соответствует объёму памяти 63 Кбайт, а оставшееся адресное пространство FC00h…FFFFh, соответствующе оставшемуся 1 Кбайту, отдано в распоряжение разработчика внешних устройств. Здесь, в области верхнего килобайта, можно размещать нужные разработчику регистры, через которые и будет вестись обмен информацией. Этот способ удобен тем, что в системе команд центрального процессора (ЦП) не нужны специальные команды ввода-вывода. Обращение к ВУ будет производиться теми же командами (например, MOV), что и к оперативной памяти. Пусть, например, регистр данных дисплея находится по адресу FC01h. Теперь процессор, производя пересылку какого-либо байта данных по адресу F000h, произведёт запись в ОЗУ; производя же пересылку по адресу FC01h, он выведет данные на экран.
Существует и другой подход, широко распространённый, в частности, в процессорах фирмы Intel. Здесь предусматривается возможность обращения к набору (множеству, массиву) отдельных, никак не связанных с ОЗУ, регистров, которые называются портами ввода-вывода. Каждый такой порт имеет собственный номер (адрес) в отдельном множестве адресов, которое называется адресным пространством ввода-вывода. Это пространство насчитывает, как правило, десятки тысяч адресов (например, 65536 = 216), так что в реальных компьютерных системах используется лишь малая его часть.
При таком способе работы с ВУ нужны, разумеется, специальные команды, которые осуществляют обмен данными именно с портами ввода-вывода. Такие команды имеют символические (на уровне Ассемблера) обозначения IN и OUT. Заметим, что в различных цифровых устройствах могут использоваться порты только для чтения (с ними работает только команда IN), только для записи (с ними работает только команда OUT), а также двунаправленные порты, допускающие как чтение, так и запись информации.
Использование портов ввода-вывода отнюдь не препятствует выделению части адресного пространства ОЗУ для обмена данными с ВУ (например, для организации видеопамяти). Именно такой комбинированный способ применяется во всех стандартных видеоадаптерах IBM-совместимых персональных компьютеров.
Как же конкретно выполняется работа с портами ввода-вывода, в частности, в нашей модели? Ввод данных из порта с номером N осуществляется командой
IN регистр, N
Вывод в порт осуществляется командой
OUT N, регистр
В качестве регистра может быть указан только регистр процессора AL либо AX. Адрес порта может быть задан только константой, причём в нашей модели могут быть использованы лишь адреса 0, 1, 2, 3, 4. Все возможные варианты команд IN и OUT приведены в файле справки системы AsmEd.
Порты с адресами 2, 3 и 4 в нашей модели расположены на вкладке «Ввод-вывод» и снабжены символьными строками (объект TEdit в терминологии Delphi). Порты с адресами 0 и 1 являются служебными и в данном практикуме не используются. Особенности портов 2 и 3 состоят в том, что они:
· двунаправленные, то есть могут как отображать информацию (в пределах байта или двухбайтового слова), так и принимать ввод со стороны пользователя;
· способны работать как с десятичными, так и с шестнадцатеричными числами, что отмечается соответственно отсутствием или наличием буквы “h” в конце записи числа.
Порт 4 предназначен для управления режимами портов 2 и 3 (выбора десятичного или шестнадцатеричного представления) и недоступен для непосредственного ввода с клавиатуры. Доступ к этому порту возможен только командой OUT; формат управляющего байта описан в файле справки системы AsmEd. Впрочем, изменить упомянутые режимы можно и без помощи записи в порт 4 – рядом с изображением этого порта имеется кнопка, которая открывает диалоговое окно с элементами управления.
Приведём пример самой простой программы, которая вводит байт из порта 2, увеличивает его на единицу и записывает результат в порт 3:
IN AL, 2
INC AL
OUT 3, AL
HLT
2. Виртуальный текстовый дисплей
Известно, что в вычислительных машинах первых поколений в качестве устройств вывода применялись электрические пишущие машинки и телетайпы. Позже появились дисплеи (видеотерминалы) на электронно-лучевых трубках (ЭЛТ).
Для того чтобы пользователь мог видеть стабильное изображение на экране ЭЛТ, оно должно периодически обновляться с частотой несколько десятков раз в секунду (исключение составляют так называемые запоминающие ЭЛТ, но этот экзотический случай мы не рассматриваем). Следовательно, отображаемая на экране информация должна храниться в каком-то устройстве памяти (видеопамять) и быть доступной для чтения электронному блоку, осуществляющему вывод на экран.
В большинстве конструкций видеотерминалов вплоть до середины 80-х годов видеопамять представляла собой самостоятельный узел, не имеющий никакого отношения ни к адресному пространству центрального процессора (ЦП) ЭВМ, ни к структуре ОЗУ. Управление видеопамятью со стороны ЦП осуществлялось через несколько специальных регистров или портов, куда пересылались необходимые данные.
Иной подход был использован в персональных компьютерах фирмы IBM (IBM PC), массовый выпуск которых был освоен во второй половине 80-х годов. Здесь видеопамять входит в адресное пространство ЦП и непосредственно доступна как для чтения-записи со стороны ЦП, так и для чтения со стороны электронных схем, осуществляющих регенерацию (периодическое обновление) изображения на экране. Широкое распространение жидкокристаллических (ЖК) мониторов, пришедших в начале XXI века на смену ЭЛТ, не привело к кардинальному изменению структуры видеопамяти.
Сразу же заметим, что видеоподсистема IBM PC и всех совместимых с ними современных ПК может работать в двух режимах – текстовом и графическом. Далее речь пойдёт о структуре видеопамяти и методах работы с ней только в текстовом режиме.
В реальных IBM-совместимых компьютерах видеопамять начинается с адреса B800h:0000h (напоминаем, что такая запись означает «сегмент : смещение») и содержит, как правило, четыре страницы по 4000 байт каждая – итого 16 000 байт. Это связано с тем, что экран в стандартном текстовом режиме разделён на 25 строк по 80 символов (знакомест) в каждой – всего, очевидно, 25*80 = 2000 знакомест. Для работы с каждым знакоместом используется два байта – следовательно, на весь экран необходимо 2000*2 = 4000 байт. Всего в видеопамяти можно сохранить до четырёх таких экранов, представленных четырьмя страницами.
Что же представляют собой упомянутые выше два байта, несущие информацию о каждом конкретном знакоместе (символе на экране)? Символы – это латинские и русские буквы, цифры, служебные знаки и так называемые псевдографические изображения (например, закрашенный прямоугольник). Напомним, что каждому такому символу поставлен в соответствие однобайтовый (0…255) числовой код согласно стандарту ASCII (хотя этот стандарт, строго говоря, не распространяется на русские буквы). На рисунке, приведённом ниже, изображены фрагменты экрана и видеопамяти:

Каждый байт видеопамяти с чётным адресом (0, 2, 4…) содержит ASCII-код символа, отображаемого в соответствующем знакоместе. В нашем примере стандартный код заглавной латинской буквы А равен 65, код буквы В равен 66 и т. д. В третьем знакоместе первой строки здесь изображена буква С, следовательно, по адресу (смещению) +4 будет записано 67. Следующий за ним байт с нечётным адресом (1, 3, 5…) содержит набор битов, определяющий цвет символа и цвет фона, на котором этот символ отображается – так называемый цветовой атрибут. В нашем примере он везде равен 15, что означает ярко-белый символ на чёрном фоне (противоположно тому, как мы видим текст на бумаге). Как именно устроен этот набор битов, показывает следующий рисунок:

Цвет фона – чёрный, то есть биты фона I = R = G = B = 0, цвет символа – ярко-белый, что получается установкой битов символа R = 1, G = 1, B = 1 (белый цвет есть своеобразная логическая сумма красного, зелёного и синего), а также I = 1 (признак яркости, или интенсивности). Получаем набор битов , такое двоичное число равно 15.
В нашей учебной модели видеопамять располагается по адресу 0003h:F600h (отличающимся от стандартного B800h:0000h), и её содержимое можно просмотреть и даже изменить в окне редактора, выбрав сегмент № 3 (ES, Extra Segment, дополнительный сегмент данных). Однако наша задача – изучить систему команд и методы программирования.
Рассмотрим несколько примеров разработки программ для виртуального дисплейного адаптера – от простых к более сложным.
Вывод одиночного символа и строки символов
Пусть необходимо вывести изображение, скажем, заглавной буквы «В» в левый верхний угол экрана. Цвет символа пусть будет ярко-белым, цвет фона – зелёным.
Для осуществления этого замысла нам следует выполнить пересылку (запись) ASCII-кода латинской буквы «В» в байт по адресу F600h (смещение относительно начала ES), а цветового атрибута – по адресу F601h. Нужный нам код символа можно взять из справочных таблиц или определить с помощью встроенного редактора системы AsmEd – он равен 66 (десятичное значение). Код цветового атрибута придётся составить самостоятельно. Ярко-белый цвет соответствует установке всех четырёх битов (см. рисунок выше) I, R, G, B в единицу; зелёный цвет фона, очевидно, отвечает комбинации G=1, R=0, B=0. Получаем двоичный набор , после перевода в шестнадцатеричную систему получим 2Fh.
Назначим теперь определённые роли регистрам процессора. Для адресации сегмента видеопамяти будем использовать регистр ES, а для указания смещения внутри сегмента – регистр DI (Destination Index – индекс получателя). В данном примере это смещение равно F600h при записи ASCII-кода и на единицу больше – при записи цветового атрибута. В простейшем случае другие регистры не нужны. Собственно запись байтов в память можно выполнить с помощью команды MOV, хорошо известной нам по предыдущим разделам практикума.
При записи текста программы надо учитывать следующее. Во-первых, если представление шестнадцатеричной константы начинается с буквы (например, А000, С150, F600 и т. п.), следует записывать константу с предшествующим нулём (0А000h, 0С150h, 0F600h и т. п.), чтобы компилятор отличил константу от символического имени. Не будем забывать также приписывать “h” в конце. Во-вторых, если первый операнд команды MOV – это ссылка на память, а второй операнд – константа (то есть как раз наш случай), то следует записывать дополнительный параметр “byte ptr” или “word ptr” в зависимости от того, предполагается пересылка байта или 16-разрядного слова. Нам нужна запись байта. В результате приходим к следующей программе:
mov DI, 0F600h ; задали внутрисегментный адрес
mov byte ptr ES:[DI], 66 ; вывели на экран букву «В»
inc DI ; увеличили адрес на единицу – подготовились к записи атрибута
mov byte ptr ES:[DI],2Fh ; переслали цветовой атрибут
hlt ; на этом всё!
Набрав эту программу в системе AsmEd, откомпилируем её, как обычно (F9), затем нажмём F4 для переключения на вкладку под названием «Ввод-вывод» и запустим программу, нажав F5. Мы увидим в левом верхнем углу экрана ярко-белую букву «В» на зелёном фоне. Далее нажмём F2 для возврата в окно (вкладку) редактора.
Разобранная только что программа предельно проста, но обладает целым рядом недостатков. Как правило, приходится выводить на экран не один, а несколько символов или даже строк, где символы задаются по определенному закону. Запись кода в виде константы здесь уже не подойдёт. К тому же команда MOV – не самое лучшее средство для таких манипуляций. В нашем первом примере она использована исключительно ввиду своей простоты. В системе команд микропроцессоров Intel x86 существует несколько так называемых «команд обработки строк», одну из которых мы сейчас научимся применять.
Речь идёт о команде STOSW (разбирать остальные строковые команды и их разновидности мы здесь не будем), которая записывается в программе без всяких операндов (то есть использует подразумеваемую адресацию) и выполняет следующее:
· используя содержимое регистра DI как адрес внутри сегмента (смещение), записывает содержимое регистра AL в память по адресу ES:[DI];
· увеличивает содержимое DI на единицу;
· записывает содержимое регистра AH в память по адресу ES:[DI], используя изменённое на предыдущем шаге значение DI;
· ещё раз увеличивает содержимое DI на единицу.
Говоря другими словами, команда STOSW (Store to String as Word) записывает в сегмент памяти ES 16-разрядное слово из регистра AX, используя DI как смещение, после чего «продвигает» DI на следующее 16-разрядное слово памяти, увеличивая DI на два. Это очень удобно использовать при работе с видеопамятью. Рассмотрим второй пример программы, которая выполняет то же, что описано ранее, но для разнообразия выводит символ (цифру) «1» с кодом 49:
mov DI, 0F600h ; задали внутрисегментный адрес
mov AL, 49 ; подготовили вывод символа
mov AH, 2Fh ; подготовили вывод атрибута
STOSW ; всё разом записали в видеопамять и увеличили DI
hlt
Это решение в программистском смысле более красиво, чем показанное ранее. А теперь попробуем вывести строку, состоящую из трёх разноцветных цифр 1,2,3:
mov DI, 0F600h
mov AL, 49 ; цифра 1
mov AH, 10 ; ярко-зелёного цвета (0
STOSW
mov AL, 50 ; цифра 2
mov AH, 12 ; ярко-красного цвета (0
STOSW
mov AL, 51 ; цифра 3
mov AH, 14 ; ярко-жёлтого цвета (0
STOSW
hlt
Как видим, при таком подходе не надо заботиться об изменении содержимого (модификации) DI, думать о том, каков должен быть очередной адрес для записи и т. п. Эта программа содержит чётко выделенные повторяющиеся действия – отсюда остаётся один шаг до организации цикла.
Напишем программу, которая выводит строку с латинским алфавитом. Строка будет выводиться в цикле, повторяющемся 26 раз. Коды соседних латинских букв различаются на единицу, код первой буквы «А» - 65. Следовательно, для перехода к каждой следующей букве надо просто увеличить на 1 содержимое AL:
mov DI, 0F600h
mov CX,26
mov AL, 65
mov AH, 10
Next:
STOSW
inc AL
loop Next
hlt
Теперь Вы можете смело приступать к выполнению учебных заданий!


