Devotes - Предисловие
Уважаеме посетители, Сайт переезжает на другой хост http://www.devote.ru/
   Поиск по сайту:  
» Главная
Книги
Музыка
Download
Форум
[Назад] [Далее]

5.10.4. Видеоадаптеры VGA

VGA-совместимые видеоадаптеры управляются при помощи портов ввода-вывода 03C0h – 03CFh, 03B4h, 03B5h, 03D4h, 03D5h, 03DAh, причем реальное число внутренних регистров видеоадаптера, к которым можно обращаться через это окно, превышает 50. Так как BIOS предоставляет хорошую поддержку для большинства стандартных функций, мы не будем рассматривать подробно программирование видеоадаптера на уровне портов, а только рассмотрим основные действия, для которых принято обращаться к видеоадаптеру напрямую.

Внешние регистры контроллера VGA (03C2h – 03CFh)

Доступ к этим регистрам осуществляется прямым обращением к соответствующим портам ввода-вывода.

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

; процедура wait_retrace
; возвращает управление в начале обратного вертикального хода луча
;
wait_retrace       proc    near
        push       ax
        push       dx
        mov        dx,03DAh             ; порт регистра ISR1
wait_retrace_end:
        in         al,dx
        test       al,1000b             ; проверить бит 3
                                        ; Если не ноль -
        jnz        wait_retrace_end     ; подождать конца
                                        ; текущего обратного хода
wait_retrace_start:
        in         al,dx
        test       al,1000b             ; а теперь подождать
                                        ; начала следующего
        jz         wait_retrace_start
        pop        dx
        pop        ax
        ret
wait_retrace       endp

Регистры контроллера атрибутов (03C0h – 03C1h)

Контроллер атрибутов преобразовывает значения байта атрибута символа в цвета символа и фона. Для записи в эти регистры надо записать в порт 03C0h номер регистра, а затем (второй командой out) — данные для этого регистра. Чтобы убедиться, что 03C0h находится в состоянии приема номера, а не данных, надо выполнить чтение из ISR1 (порт 03DAh). Порт 03C1h можно использовать для чтения последнего записанного индекса или данных.

Функции INT 10h AX = 1000h – 1009h позволяют использовать большинство из этих регистров, но кое-что, например панорамирование, оказывается возможным только при программировании на уровне портов.

Регистры графического контроллера (03CEh – 03CFH)

Для обращения к регистрам графического контроллера следует записать индекс нужного регистра в порт 03CEh, после чего можно будет читать и писать данные для выбранного регистра в порт 03CFh. Если требуется только запись в регистры, можно просто поместить индекс в AL, посылаемый байт — в АН и выполнить команду вывода слова в порт 03CEh. Этот контроллер, в первую очередь, предназначен для обеспечения передачи данных между процессором и видеопамятью в режимах, использующих цветовые плоскости, как, например, режим 12h (640x480x16).

Графический контроллер предоставляет весьма богатые возможности по управлению режимами, использующими цветовые плоскости. В качестве примера напишем процедуру, выводящую точку на экран в режиме 12h (640x480x16) с использованием механизма установки/сброса:

; процедура putpixel12h
; выводит на экран точку с заданным цветом в режиме 12h (640x480x16)
; Ввод:      DX = строка
;            СХ = столбец
;            ВР = цвет
;            ES = 0A000h
putpixel12h        proc    near
        pusha
; вычислить номер байта в видеопамяти
        xor        bx,bx
        mov        ax,dx              ; AX = строка
        lea        еах,[еах+еах*4]    ; АХ = АХ * 5
        shl        ах,4               ; АХ = АХ * 16
                                      ; АХ = строка * байт_в_строке
                                      ; (строка * 80)
        push       cx
        shr        cx,3               ; CX = номер байта в строке
        add        ax,cx              ; АХ = номер байта в видеопамяти
        mov        di,ax              ; сохранить его в DI
; вычислить номер бита в байте
        pop        сх
        and        cx,07h             ; остаток от деления на 8 - номер
                                      ; бита в байте, считая справа налево
        mov        bx,0080h
        shr        bx,cl              ; в BL теперь нужный бит установлен в 1
; программирование портов
        mov        dx,03CEh           ; индексный порт
                                      ; графического контроллера
        mov        ax,0F01h           ; регистр 01h: разрешение
                                      ; установки/сброса
        out        dx,ax              ; разрешить установку/сброс для
                                      ; всех плоскостей (эту часть лучше
; сделать однажды в программе, например сразу после установки
; видеорежима, и не повторять каждый раз при вызове процедуры)
        mov        ax,bp
        shl        ax,8               ; регистр 00h: регистр
                                      ; установки/сброса
        out        dx,ax              ; АН = цвет
        mov        al,08              ; порт 08h: битовая маска
        mov        ah,bl              ; записать в битовую маску нули
                                      ; всюду, кроме
        out        dx,ax              ; бита, соответствующего выводимому пикселю
        mov        ah,byte ptr es:[di]  ; заполнить
                                        ; регистры-защелки
        mov        byte ptr es:[di],ah  ; вывод на экран:
                                        ; выводится единственный бит
; в соответствии с содержимым регистра битовой маски, остальные
; биты берутся из защелки, то есть не изменяются. Цвет выводимого
; бита полностью определяется значением регистра установки/сброса
        рора
        ret
putpixel12h        endp

Регистры контроллера CRT (03D4h – 03D5H)

Контроллер CRT управляет разверткой и формированием кадров на дисплее. Как и для графического контроллера, для обращения к регистрам контроллера CRT следует записать индекс нужного регистра в порт 03D4h, после чего можно будет читать и писать данные для выбранного регистра в порт 03D5h. Если требуется только запись в регистры, можно просто поместить индекс в AL, посылаемый байт — в АН и выполнить команду вывода слова в порт 03D4h.

BIOS заполняет регистры этого контроллера соответствующими значениями при переключении видеорежимов. Так как одного контроллера CRT мало для полного переключения в новый видеорежим, мы вернемся к этому чуть позже, а пока посмотрим, как внести небольшие изменения в действующий режим, например, как превратить текстовый режим 80x25 в 80x30:

; 80x30.asm
; переводит экран в текстовый режим 80x30 (размер символов 8x16)
; (Norton Commander 5.0 в отличие от, например, FAR восстанавливает режим по
; окончании программы, но его можно обмануть, если предварительно нажать
; Alt-F9)
        .model     tiny
        .code
        .186                           ; для команды outsw
        org        100h                ; СОМ-программа
start:
        mov        ax,3                ; установить режим 03h (80x25),
        int        10h                 ; чтобы только внести небольшие изменения
        mov        dx,3CCh             ; порт 3CCh: регистр вывода (MOR) на чтение
        in         al,dx
        mov        dl,0C2h             ; порт 03C2h: регистр вывода (MOR) на запись
        or         al,0C0h             ; установить полярности 1,1 - для 480 строк
        out        dx,al
        mov        dx,03D4h            ; DX = порт 03D4h: индекс CRT
        mov        si,offset crt480    ; DS:SI = адрес таблицы данных для CRT
        mov        cx,crt480_l         ; CX = ее размер
        rep        outsw               ; послать все устанавливаемые параметры
                                       ; в порты 03D4h и 03D5h

; нельзя забывать сообщать BIOS об изменениях в видеорежиме
        push       0040h
        pop        es                   ; ES = 0040h
        mov        byte ptr es:[84h],29 ; 0040h:0084h - число строк
        ret

; данные для контроллера CRT в формате индекс в младшем байте, данные в
; старшем - для записи при помощи команды outsw
crt480  dw    0C11h          ; регистр 11h всегда надо записывать первым,
                             ; так как его бит 7 разрешает запись в другие
        dw    0B06h,3E07h,0EA10h,0DF12h,0E715h,0416h  ; регистры
crt480_l = ($-crt480)/2
        end        start

Еще одна интересная возможность, которую предоставляет контроллер CRT, — плавная прокрутка экрана при помощи регистра 08h:

; vscroll.asm
; Плавная прокрутка экрана по вертикали. Выход - клавиша Esc
;
        .model     tiny
        .code
        .186                        ; для push 0B400h
        org        100h             ; СОМ-программа
start:
        push       0B800h
        pop        es
        xor        si,si            ; ES:SI - начало видеопамяти
        mov        di,80*25*2       ; ES:DI - начало второй страницы видеопамяти
        mov        cx,di
        rep        movs es:any_label,es:any_label   ; скопировать первую
                                                    ; страницу во вторую
        mov        dx,03D4h         ; порт 03D4h: индекс CRT
screen_loop:                        ; цикл по экранам
        mov        cx,80*12*2       ; СХ = начальный адрес - адрес середины экрана
line_loop:                          ; цикл по строкам
        mov        al,0Ch           ; регистр 0Ch - старший байт начального адреса
        mov        ah,ch            ; байт данных - СН
        out        dx,ax            ; вывод в порты 03D4, 03D5
        inc        ax               ; регистр 0Dh - младший байт начального адреса
        mov        ah,cl            ; байт данных - CL
        out        dx,ax            ; вывод в порты 03D4, 03D5

        mov        bx,15            ; счетчик линий в строке
        sub        cx,80            ; переместить начальный адрес на начало
                                    ; предыдущей строки (так как это движение вниз)
pel_loop:                           ; цикл по линиям в строке
        call       wait_retrace     ; подождать обратного хода луча

        mov        al,8          ; регистр 08h - выбор номера линии в первой
                                 ; строке, с которой начинается вывод изображения
        mov        ah,bl         ; (номер линии из BL)
        out        dx,ax

        dec        bx               ; уменьшить число линий,
        jge        pel_loop         ; если больше или = нулю - строка еще не
                                    ; прокрутилась до конца и цикл по линиям
                                    ; продолжается
        in         al,60h           ; прочитать скан-код последнего символа,
        cmp        al,81h           ; если это 81h (отпускание клавиши Esc),
        jz         done             ; выйти из программы,
        cmp        cx,0             ; если еще не прокрутился целый экран,
        jge        line_loop        ; продолжить цикл по строкам,
        jmp        short screen_loop ; иначе: продолжить цикл по экранам

done:                               ; выход из программы
        mov        ax,8             ; записать в регистр CRT 08h
        out        dx,ax            ; байт 00 (никакого сдвига по вертикали),
        add        ax,4             ; а также 00 в регистр 0Ch
        out        dx,ax
        inc        ax               ; и 0Dh (начальный адрес совпадает
        out        dx,ax            ; с началом видеопамяти)
        ret

wait_retrace       proc    near
        push       dx
        mov        dx,03DAh
VRTL1:  in         al,dx            ; порт 03DAh - регистр ISR1
        test       al,8
        jnz        VRTL1            ; подождать конца текущего обратного хода луча,
VRTL2:  in         al,dx
        test       al,8
        jz         VRTL2            ; а теперь начала следующего
wait_retrace       endp

any_label  label   byte             ; метка для переопределения сегмента в movs
        end        start

Горизонтальная прокрутка осуществляется аналогично, только с использованием регистра горизонтального панорамирования 13h из контроллера атрибутов.

Регистры синхронизатора (03C4h – 03C5h)

Для обращения к регистрам синхронизатора следует записать индекс нужного регистра в порт 03C4h, после чего можно будет читать и писать данные для выбранного регистра в порт 03C5h. Точно так же, если требуется только запись в регистры, можно просто поместить индекс в AL, посылаемый байт — в АН и выполнить команду вывода слова в порт 03CEh.

Хотя BIOS и позволяет использовать некоторые возможности этих регистров, в частности работу со шрифтами (INT 10h АН = 11h) и выключение обмена данными между видеопамятью и дисплеем (INT 10h, АН = 12h, BL = 32h), прямое программирование регистров синхронизатора вместе с регистрами контроллера CRT позволяет значительно изменять характеристики видеорежимов VGA, вплоть до установки нестандартных видеорежимов. Наиболее популярными режимами являются так называемые режимы «X» с 256 цветами и с разрешением 320 или 360 пикселей по горизонтали и 200, 240, 400 или 480 пикселей по вертикали. Так как такие режимы не поддерживаются BIOS, для их реализации нужно написать все необходимые процедуры — установку видеорежима, вывод пикселя, чтение пикселя, переключение страниц, изменение палитры, загрузку шрифтов. При этом для всех режимов из этой серии, кроме 320x240x256, приходится также учитывать измененное соотношение размеров экрана по вертикали и горизонтали, чтобы круг, выведенный на экран, не выглядел как эллипс, а квадрат — как прямоугольник.

Установка нового режима выполняется почти точно так же, как и в предыдущем примере, — путем модификации существующего. Кроме того, нам придется изменять частоту кадров (биты 3 – 2 регистра MOR), а это приведет к сбою синхронизации, если мы не выключим синхронизатор на время изменения частоты (записью в регистр 00h):

; процедура set_modex
; переводит видеоадаптер VGA в один из режимов X с 256 цветами
; ввод: DI = номер режима
;       0: 320x200, соотношение сторон 1,2:1
;       1: 320x400, соотношение сторон 2,4:1
;       2: 360x200, соотношение сторон 1,35:1
;       3: 360x400, соотношение сторон 2,7:1
;       4: 320x240, соотношение сторон 1:1
;       5: 320x480, соотношение сторон 2:1
;       6: 360x240, соотношение сторон 1,125:1
;       7: 360x480, соотношение сторон 2,25:1
;       DS = CS
; Для вывода информации на экран в этих режимах
; см. процедуру putpixel_x
setmode_x          proc    near
        mov        ax,12h               ; очистить все четыре цветовые
        int        10h                  ; плоскости видеопамяти,
        mov        ax,13h               ; установить режим 13h, который будем
        int        10h                  ; модифицировать
        cmp        di,7                 ; если нас вызвали с DI > 7,
        ja         exit_modex           ; выйти из процедуры
                                        ; (оставшись в режиме 13h),
        shl        di,1                 ; умножить на 2, так как x_modes -
                                        ; таблица слов,
        mov        di,word ptr x_modes[di] ; прочитать
                                           ; адрес таблицы настроек для
                                           ; выбранного режима
        mov        dx,03C4h             ; порт 03C4h - индекс синхронизатора
        mov        ax,0100h             ; регистр 00h, значение 01
        out        dx,ax                ; асинхронный сброс
        mov        ax,0604h             ; регистр 04h, значение 06h
        out        dx,ax                ; отключить режим CHAIN4
        mov        dl,0C2h              ; порт 03C2h - регистр
                                        ; MOR на запись
        mov        al,byte ptr [di]     ; записать в него
                                        ; значение частоты кадров
        out        dx,al                ; и полярности развертки
                                        ; для выбранного режима
        mov        dl,0D4h              ; порт 03D4h - индекс
                                        ; контроллера CRT
        mov        si,word ptr offset [di+2]
                                        ; адрес строки с настройками
                                        ; для выбранной ширины в DS:SI
        mov        cx,8                 ; длина строки настроек в СХ
        rep        outsw                ; вывод строки слов
                                        ; в порты 03D4/03D5
        mov        si,word ptr offset [di+4] ; настройки для
                                        ; выбранной высоты в DS:SI
        mov        сх,7                 ; длина строки настроек в СХ
        rep        outsw
        mov        si,word ptr offset [di+6] ; настройки
                                        ; для включения/выключения удвоения
                                        ; по вертикали (200/400 и 240/480 строк)
        mov        сх,3
        rep        outsw
        mov        ax, word ptr offset [di+8] ; число байт в строке
        mov        word ptr x_width,ax        ; сохранить
                                              ; в переменной x_width
        mov        dl,0C4h              ; порт 03C4h - индекс синхронизатора
        mov        ах,0300h             ; регистр 00h,  значение 03
        out        dx,ax                ; выйти из состояния сброса
exit_modex:
        ret

; таблица адресов таблиц с настройками режимов
x_modes dw         offset mode_0,offset mode_1
        dw         offset mode_2,offset mode_3
        dw         offset mode_4,offset mode_5
        dw         offset mode_6,offset mode_7

; таблица настроек режимов: значение регистра MOR, адрес строки
; настроек ширины, адрес строки настроек высоты, адрес строки
; настроек удвоения по вертикали, число байт в строке
mode_0  dw         63h,offset mode_320w,offset mode_200h,offset mode_double,320/4
mode_1  dw         63h,offset mode_320w,offset mode_400h,offset mode_single,320/4
mode_2  dw         67h,offset mode_360w,offset mode_200h,offset mode_double,360/4
mode_3  dw         67h,offset mode_360w,offset mode_400h,offset mode_single,360/4
mode_4  dw         0E3h,offset mode_320w,offset mode_240h,offset mode_double,320/4
mode_5  dw         0E3h,offset mode_320w,offset mode_480h,offset mode_single,320/4
mode_6  dw         0E7h,offset mode_360w,offset mode_240h,offset mode_double,360/4
mode_7  dw         0E7h,offset mode_360w,offset mode_480h,offset mode_single,360/4

; настройки CRT. В каждом слове младший байт - номер регистра,
; старший - значение, которое в этот регистр заносится
mode_320w:                              ; настройка ширины 320
; Первый регистр обязательно 11h, хотя он и не относится
; к ширине - он разрешает запись в остальные регистры,
; если она была запрещена (!)
        dw         0E11h,5F00h,4F01h,5002h,8203h,5404h,8005h,2813h
mode_360w:                              ; настройка ширины 360
        dw         0E11h,6B00h,5901h,5A02h,8E03h,5E04h,8A05h,2D13h
mode_200h:
mode_400h:                              ; настройка высоты 200/400
        dw         0BF06h,1F07h,9C10h,0E11h,8F12h,9615h,0B916h
mode_240h:
mode_480h:                              ; настройка высоты 240/480
        dw         0D06h,3E07h,0EA10h,0C11h,0DF12h,0E715h,0616h
mode_single:                            ; настройка режимов без удвоения
        dw         4009h,0014h,0E317h
mode_double:                            ; настройка режимов с удвоением
        dw         4109h,0014h,0E317h
setmode_x          endp

x_width            dw    ?              ; число байт в строке
; эту переменную инициализирует setmode_x, а использует putpixel_x

; процедура putpixel_x
; выводит точку с заданным цветом в текущем режиме X
; Ввод: DX = строка
;       СХ = столбец
;       ВР = цвет
;       ES = A000h
;       DS = сегмент, в котором находится переменная x_width

putpixel_x         proc    near
        pusha
        mov        ax, dx
        mul        word ptr x_width     ; AX = строка * число байт в строке
        mov        di,cx                ; DI = столбец
        shr        di,2                 ; DI = столбец/4 (номер байта в строке)
        add        di,ax                ; DI = номер байта в видеопамяти
        mov        ax,0102h             ; AL = 02h (номер регистра),
                                        ; АН = 01 (битовая маска)
        and        cl,03h               ; CL = остаток от деления
                                        ; столбца на 4 = номер
                                        ; цветовой плоскости
        shl        ah,cl                ; теперь в АН выставлен в 1 бит,
                                        ; соответствующий нужной
                                        ; цветовой плоскости
        mov        dx,03C4h             ; порт 03C4h - индекс
                                        ; синхронизатора
        out        dx,ax                ; разрешить запись только
                                        ; в нужную плоскость
        mov        ax,bp                ; цвет в AL
        stosb                           ; вывод байта в видеопамять
        рора
        ret
putpixel_x         endp

Регистры VGA DAC (03C6h – 03C9h)

Таблица цветов VGA на самом деле представляет собой 256 регистров, в каждом из которых записаны три 6-битных числа, соответствующих уровням красного, зеленого и синего цвета. Подфункции INT 10h AX =1010h – 101Bh позволяют удобно работать с этими регистрами, но, если требуется максимальная скорость, программировать их на уровне портов ввода-вывода не намного сложнее.

Команды insb/outsb серьезно облегчают работу с регистрами DAC в тех случаях, когда требуется считывать или загружать значительные участки палитры или всю палитру целиком, — такие процедуры оказываются и быстрее, и меньше аналогичных, написанных с использованием прерывания INT 10h. Посмотрим, как это реализуется на примере программы плавного гашения экрана.

; fadeout.asm
; выполняет плавное гашение экрана

        .model     tiny
        .code
        .186                            ; для команд insb/outsb
        org        100h                 ; СОМ-программа
start:
        cld                             ; для команд строковой обработки
        mov        di,offset palettes
        call       read_palette         ; сохранить текущую палитру, чтобы
                                        ; восстановить в самом конце программы,
        mov        di,offset palettes+256*3
        call       read_palette         ; а также записать еще одну копию
                                        ; текущей палитры, которую будем
                                        ; модифицировать
        mov        cx,64                ; счетчик цикла изменения палитры
main_loop:
        push       cx
        call       wait_retrace         ; подождать начала обратного хода луча
        mov        di,offset palettes+256*3
        mov        si,di
        call       dec_palette          ; уменьшить яркость всех цветов
        call       wait_retrace         ; подождать начала следующего
        mov        si,offset palettes+256*3    ; обратного хода луча
        call       write_palette        ; записать новую палитру
        pop        cx
        loop       main_loop      ; цикл выполняется 64 раза - достаточно для
                                  ; обнуления самого яркого цвета (максимальное
                                  ; значение 6-битной компоненты - 63)
        mov        si,offset palettes
        call       write_palette  ; восстановить первоначальную палитру
        ret                       ; конец программы

; процедура read_palette
; помещает палитру VGA в строку по адресу ES:DI
read_palette       proc    near
        mov        dx,03C7h             ; порт 03C7h - индекс DAC/режим чтения
        mov        al,0                 ; начинать с нулевого цвета
        out        dx,al
        mov        dl,0C9h              ; порт 03C9h - данные DAC
        mov        cx,256*3             ; прочитать 256 * 3 байта
        rep        insb                 ; в строку по адресу ES:DI
        ret
read_palette       endp

; процедура write_palette
; загружает в DAC VGA палитру из строки по адресу DS:SI
write_palette      proc    near
        mov        dx,03C8h             ; порт 03C8h - индекс DAC/режим записи
        mov        al,0                 ; начинать с нулевого цвета
        out        dx,al
        mov        dl,0C9h              ; порт 03C9h - данные DAC
        mov        cx,256*3             ; записать 256 * 3 байта
        rep        outsb                ; из строки в DS:SI
        ret
write_palette      endp

; процедура dec_palette
; уменьшает значение каждого байта на 1 с насыщением (то есть, после того как
; байт становится равен нулю, он больше не уменьшается  из строки в DS:SI
; и записывает результат в строку в DS:SI
dec_palette        proc    near
        mov        cx,256*3             ; длина строки 256 * 3 байта
dec_loop:
        lodsb                           ; прочитать байт,
        test       al,al                ; если он ноль,
        jz         already_zero         ; пропустить следующую команду
        dec        ax                   ; уменьшить байт на 1
already_zero:
        stosb                           ; записать его обратно
        loop       dec_loop             ; повторить 256 * 3 раза
        ret
dec_palette        endp

; процедура wait_retrace
; ожидание начала следующего обратного хода луча
wait_retrace       proc    near
        push       dx
        mov        dx,03DAh
VRTL1:  in         al,dx           ; порт 03DAh - регистр ISR1
        test       al,8
        jnz        VRTL1           ; подождать конца текущего обратного хода луча,
VRTL2:  in         al,dx
        test       al,8
        jz         VRTL2           ; а теперь начала следующего
        pop        dx
        ret
wait_retrace       endp

palettes:                          ; за концом программы мы храним две копии
                                   ; палитры - всего 1,5 Кб
        end        start

Hosted by uCoz