4. Архитектура видеоадаптера CGA

Видеоадаптер CGA построен на основе мкросхемы Motorolla 6845 или ее аналога. Эта микросхема содержит контроллер электронно-лучевой трубки (ЭЛТ). Контроллер ЭЛТ Motorolla 6845 устанавливает формат экрана, управляет курсором и световым пером, а также управляет цветовыми характеристиками изображения.

Адаптер CGA имеет 16К байт видеопамяти. Видеопамять периодически отображается на экране дисплея, формируя изображение. Процессор может непосредственно обращаться к видеопамяти, которая расположена в адресном пространстве процессора начиная с адреса B800:0000.

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

В зависимости от режима работы адаптера, видеопамять имеет различную структуру. Ниже рассмотрены текстовые и графические режимы видеоадаптера CGA.

4.1. Текстовые режимы CGA

В текстовых режимах (режимы 1, 2, 3, 4) на экране могут отображаться только текстовые символы, а также символы псевдографики. Символы псевдографики позволяют в текстовых режимах работы видеоадаптеров строить вертикальные и горизонтальные линии.

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

Стандартные текстовые режимы работы видеоадаптеров позволяют вывести на экран 25 строк по 40 или 80 символов. Для кодирования каждого знакоместа экрана (символа) используются два байта. Первый из них содержит ASCII код отображаемого символа, а второй - атрибуты символа. Коды символов имеют четные адреса, а их атрибуты - нечетные.

Атрибуты определяют цвет символа и цвет фона. Благодаря такому режиму хранения информации достигается значительная экономия памяти по сравнению с графическим режимом. Формат байта атрибутов символа приведен ниже:

D2-D0 Цвет символа.
D3 Интенсивность символа.
D6-D4 Цвет фона символа.
D7 Мигание фона символа или интенсивность фона.

Рисунок 5.1 Байт атрибутов символа.

При отображении символа на экране происходит преобразование его из формата ASCII в двумерный массив пикселов. Для этого преобразования используется таблица трансляции символов (знакогенератор).

Режимы 0 и 2 являются режимами с подавлением цвета. Тоесть в этих режимах вместо цвета выводится сигнал яркости серого. К сожалению цвет подавляется только на композитном выходе адаптера, а не на выходе RGB. Это ведет к различиям при работе видеоадаптера в этих режимах с разными дисплеями.

Знакогенератор

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

Для "русификации" (использования символов кирилицы) или изменения шрифтов, используемых в текстовых режимах видеоадаптеров MDA, CGA и Hercules, необходимо перепрограммировать микросхему ПЗУ знакогенератора, расположенную на плате видеоадаптера. Это не относится к использованию символов кирилицы в графических режимах видеоадаптера CGA.

4.2. Видеопамять в графических режимах CGA

Распределение видеопамяти в графических режимах работы видеоадаптера CGA отличается от распределения видеопамяти в текстовых режимах. Это вызвано тем, что в графических режимах необходимо хранить информацию о каждом пикселе изображения.

Видеоадаптер CGA поддерживает три графических режима - режимы 4, 5 и 6. В следующих двух разделах подробно рассмотрена структура видеопамяти для этих режимов.

Режимы 4 и 5

Это режимы низкого разрешения (320х200), используются четыре цвета. Поддерживаются видеоадаптерами CGA, EGA и VGA.

Режим 5 является режимом с подавлением цвета. Тоесть в этом режиме вместо цвета выводится сигнал яркости серого. Напомним, что как и в режимах 0 и 2, цвет подавляется только на композитном выходе адаптера.

Из-за особенностей микросхемы Motorolla 6845, используемой видеоадаптером CGA, отображение видеопамяти на экран не является непрерывным: первая половина видеопамяти (начальный адрес B800:0000) содержит данные относительно всех нечетных линий экрана, а вторая половина (начальный адрес B800:2000) - относительно всех четных линий. Каждому пикселу изображения соответствуют два бита видеопамяти. За верхний левый пиксел экрана отвечают биты D7 и D6 нулевого байта видеопамяти. На рисунке 5.1 изображено соответствие видеопамяти пикселам экрана.

Рисунок 5.1 Структура видеопамяти для режимов 4 и 5.

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

Следующие формулы позволяют определить смещение байта от начала станицы видеопамяти и номера битов в нем, управляющие пикселом с координатами (x,y):

Если y четное число, то смещение байта = 50h*(y/2)+(x/4)
Если y нечетное число, то смещение байта = 2000h+50h*((y-1)/2)+(x/4)

Номер первого бита = 7-mod(x/4)*2

В этом выражении функция mod(x/y) возвращает остаток от деления x на y.

Ниже представлена таблица соответствия значений битов, определяющих пиксел цвету пиксела:

Значение битов      Стандартный     Альтернативный
пиксела             цвет            цвет


00                  черный          черный 

01                  светло-синий    зеленый

10                  малиновый       красный

11                  ярко-белый      коричневый

Таблица 5.1

В режимах 4 и 5 имеются два набора цветов - стандартный и альтернативный. Для выбора используемого набора цветов можно воспользоваться функцией 0Bh прерывания INT 10h.

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

#include "sysp.h"


Pixel_Offs_4_5(unsigned x, unsigned y,
         unsigned *offset, unsigned char *shift) {

   unsigned char   bit_shift;
   unsigned        byte_offset;

   _asm {

   ; записываем в bx x-координату пиксела

      mov bx,x

   ; запоминаем в регистре cl младший байт переменной x

      mov cl,bl

   ; записываем в ax y-координату пиксела

      mov ax,y

   ; вычисляем смещение байта, определяющего пиксел с
   ; координатами (x,y)

   ; ax = 100h * y

      xchg ah,al

   ; в бит D7, регистра al, записываем младший бит
   ; переменной y; этот бит равен единице, если значение
   ; переменной y нечетное, и равен нулю, если оно четное
   
   ; al = 80h * (y&1)
   ; ax = ax/2 = 80h * y

      shr ax,1

   ; bx = x + 8000h*(y&1)

      add bh,al

   ; ax = 100h*(y/2)

      xor al,al

   ; bx = x + 8000h*(y&1) + 100h*(y/2)

      add bx,ax

   ; ax = 40h*(y/2)

      shr ax,1
      shr ax,1


   ; bx = x + 8000h*(y&1) + 100h*(y/2) + 40h*(y/2) =
   ;    = x + 8000h*(y&1) + 140h*(y/2)

      add bx,ax

   ; bx = x/4 + 2000h*(y&1) + 50h*(y/2)

      shr bx,1
      shr bx,1

   ; теперь bx содержит смещение байта, определяющего
   ; пиксел с координатами (x,y) относительно начала
   ; видеопамяти (страницы видеопамяти)

      mov byte_offset,bx

   ; определяем биты, отвечающие за пиксел (x,y)
   ; записываем в регистр cl два последних бита переменной
   ; x

      mov ah,3
      and cl,ah

   ; cl = 3 - (x & 3)

      xor cl,ah

   ; cl = cl * 2

      shl cl,1

   ; регистр задает число бит для сдвига влево

      mov bit_shift,cl
   }

   *shift = bit_shift;
   *offset = byte_offset;
}


//
//   главная функция
//

void main(void) {

   unsigned offset,i;
   unsigned char mask,shift,color;

   unsigned char   far *ptr;
   unsigned char   current, w_pixel;

   // переводим видеоадаптер в режим 4

   _asm {
      xor ax,ax
      mov al,4
      int 10h
   }

   // отображаем на экране несколько точек
   // непосредственно через видеопамять

   for(i=0;i<198;i++){

      // изменяем цвет пиксела
      color = i % 4;

      // определяем смещение (offset) и сдвиг (shift)
      // для пиксела с координатами x и y

      Pixel_Offs_4_5(i,i,&offset,&shift);

      // получаем указатель на байт, определяющий
      // нужный нам пиксел

      ptr = (unsigned char far*) (FP_MAKE(0xB800,
           offset));

      // получаем старое значение байта, определяющего
      // пиксел

      current = *ptr;

      // записываем в видеопамять старое значение,
      // но с измененными битами, отвечающими за наш 
      // пиксел

      w_pixel = (unsigned char) color << shift;
      *ptr = current | w_pixel;
   }
   getch();
}

Режим 6

Режим 6 (640х200) является режимом наибольшего разрешения для видеоадаптера CGA.

На рисунке 5.2 отображено соответствие видеопамяти и пикселов экрана.

Как и в режимах 4 и 5, первая половина видеопамяти содержит данные относительно всех нечетных линий экрана, а вторая половина - относительно всех четных линий.

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

Рисунок 5.2 Структура видеопамяти в режиме 6.

При непосредственном доступе к видеопамяти вы можете воспользоваться следующими формулами:

Если y четное число, то смещение байта = 50h*(y/2)+(x/8)
Если y нечетное число, то смещение байта = 2000h+50h*((y-1)/2)+(x/8)

Номер бита = 7-mod(x/8)

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

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

#include "sysp.h"


void Pixel_Offs_6(unsigned x, unsigned y,
            unsigned *offset, unsigned char *shift) {

   unsigned char   bit_shift;
   unsigned        byte_offset;

   _asm {

   ; записываем в bx x-координату пиксела

      mov bx,x

   ; запоминаем в регистре cl младший байт переменной x

      mov cl,bl

   ; записываем в ax y-координату пиксела

      mov ax,y

   ; вычисляем смещение байта, определяющего пиксел с
   ; координатами (x,y)

   ; ax = 100h * y

      xchg ah,al

   ; в бит D7, регистра al, записываем младший бит
   ; переменной y; этот бит равен единице, если значение
   ; переменной y нечетное, и равен нулю, если оно четное

   ; al = 80h * (y&1)
   ; ax = ax/2 = 80h * y

      shr ax,1

   ; bx = x/2

      shr bx,1

   ; bx = x/2 + 8000h*(y&1)

      add bh,al

   ; ax = 100h*(y/2)

      xor al,al

   ; bx = x/2 + 8000h*(y&1) + 100h*(y/2)

      add bx,ax

   ; ax = 40h*(y/2)

      shr ax,1
      shr ax,1

   ; bx = x/2 + 8000h*(y&1) + 140h*(y/2)

      add bx,ax

   ; bx = x/8 + 2000h*(y&1) + 50h*(y/2)

      shr bx,1
      shr bx,1

   ; bx = byte offset in video buffer

      mov byte_offset,bx

   ; cl = x & 7

      and cl,7

   ; cl = number of bits to shift left

      xor cl,7

      mov bit_shift,cl
   }

   *shift = bit_shift;
   *offset = byte_offset;
}


//
//   главная функция
//

void main(void) {

   unsigned offset,i;
   unsigned char mask,shift;

   unsigned char   far *ptr;
   unsigned char   current,w_pixel,color;

   // устанавливаем режим 6

   _asm {
      xor ax,ax
      mov al,6
      int 10h
   }

   // отображаем на экране несколько точек
   // непосредственно через видеопамять

   for(i=0;i<199;i++){

      color = i % 2;

      // определяем смещение (offset) и сдвиг (shift)
      // для пиксела с координатами x и y

      Pixel_Offs_6(i,i,&offset,&shift);

      // получаем указатель на байт, определяющий
      // нужный нам пиксел

      ptr = (unsigned char far*) (FP_MAKE(0xB800,
                            offset));

      // получаем старое значение байта, определяющего
      // пиксел

      current = *ptr;

      // записываем в видеопамять старое значение,
      // но с измененными битами, отвечающими за наш 
      // пиксел

      w_pixel = (unsigned char) color << shift;
      *ptr = current | w_pixel;
   }

   getch();
}

Организация видеопамяти адаптера Hercules

Для полноты картины мы рассмотрим структуру видеоадаптера Hercules. В графических режимах видеоадаптер Hercules использует один бит на пиксел. Разрешающая способность составляет 720 пикселов по горизонтали и 348 пикселов по вертикали. Hercules создан на основе микросхемы 6845 контроллера ЭЛТ и имеет еще более сложную организацию памяти, чем CGA. Видеопамять разделена на четыре части. Структура памяти приведена на рисунке 5.3.

Рисунок 5.3 Структура памяти видеоадаптера Hercules в графических режимах.

Формулы приведенные ниже позволяют определить смещение байта от начала станицы видеопамяти и номер бита в нем, управляющего пикселом с координатами (x,y):

Если [y/4]=0, то смещение байта = 5Ah*(y/4)+(x/8)
Если [y/4]=1, то смещение байта = 2000h+5Ah*((y-1)/4)+(x/8)
Если [y/4]=2, то смещение байта = 4000h+5Ah*((y-2)/4)+(x/8)
Если [y/4]=3, то смещение байта = 6000h+5Ah*((y-3)/4)+(x/8)

Номер бита = 7-mod(x/8)