Упс, как говорит Б.Спирс, ай дид ит эгейн. Ну и мы тоже эгейн. В смысле, снова
поработаем с памятью.
Итак, память. Памяти у нас примерно миллион ячеек по 1 байту. Где-то находятся
байты нашей программы, которую процессор сейчас читает байт за байтом и выполняет,
где-то байты программ БИОС-а, где-то - ДОС-а. Ну а начиная с байта нумер 655360
находится так называемый видеобуфер. Можно сказать по-другому - с номера A0000h.
Это - шестнадцетеричная система исчисления. Эти числа равны: 655360=A0000h, просто они
записаны по-разному. Здесь мы не будем подробно разбирать эту систему, приведем только
пример преобразования числа A0000h в "нормальный" вид:
A0000h = A*16^4+0*16^3+0*16^2+0*16^1+0*16^0 = 10*16^4 = 655360.
h - просто обозначение такой системы исчисления;
A = 10, B = 11, C = 12, D = 13, E = 14, F = 15 - поскольку основанием является 16, то для записи
числа НЕ ХВАТАЕТ чисел "привычной" десятичной системы - 0,1,2,3..9.
Вы вполне можете обойтись виндовым или любым другим калькулятором для
такого рода преобразований.
Ага, так что же такое видеобуфер ? Он же - видеопамять, видеокарта, ... Для
нас нет никаких железок, джемперов, жестких дисков, повторяю !
Есть только память, порты и процессор. Видеобуфер для нас в данном
случае - просто КУСОК ПАМЯТИ. Начинается с байта номер A0000h. Почему же мы
выделяем этот кусок особо ? А вот почему: помните, у нас в Уроке 1 был такой код:
{Устанавливаем графический режим монитора 13h, 320x200, 256 цветов}
asm
mov ax,0013h
int 10h
end;
Всего две команды, а делают сколько ! После их выполнения БИОС сделает так, что
всякая запись чисел в этот видеобуфер приведет к изменениям на мониторе !
Как это происходит ? С нашей стороны "забора" мы пишем в ячейки этого куска памяти
числа. Они там и остаются, пока их не изменить. С другой стороны "забора" сидит
аппаратура, "железо", как ее еще называют, которая СОВЕРШЕННО НЕЗАВИСИМО от
выполнения наших программ начинает трактовать эти числа КАК ЦВЕТА ТОЧЕК монитора.
Это ее БИОС так заставил делать. Переключил в такой режим. Бывают и другие режимы
работы видеокарты-видеоаппаратуры - текстовые там, графические ... Мы в графическом.
Но это все нам неважно. Важно нам только то, как надо писать в эту память числа, чтобы
аппаратура эта нарисовала на их основе изображение на мониторе.
Более детально. Вспомним, что режим у нас - 320x200, 256 цветов. 320 точек по
горизонтали, 200 - по вертикали. Каждая точка может иметь 256 различных цветов.
1 байт памяти как раз может иметь 256 различных состояний. Число в байте номер
A0000h аппаратура трактует как цвет верхнего левого пикселя, число в следующем
байте - как цвет следующего(двигаясь направо) пикселя и так далее. На одну строчку
320 пикселей - значит, и в памяти 320 байт. 321 байт начиная с байта A0000h - это уже
самый левый пиксель второй стороки и так далее:
Номер пикселя по
горизонтали/вертикали - номер байта в памяти
0 1 2 3
0 A0000h A0000h+1 A0000h+2 A0000h+3 ...
1 A0000h+320...
2
...
Ага. Нетрудно сообразить, что для всех 320x200 точек монитора требуется
320x200 = 64000 байт памяти. Например, запись в байт с номером
A0000h+320x200-1 приведет к изменению цвета нижнего правого пикселя.
Проверим это. Нарисуем вертикальную горизонтальную линию на мониторе,
желтого цвета, по самой первой строке от левого до правого края монитора.
От уха до уха, так сказать. Для этого мы должны записать
число 14(желтый цвет) в байты с номерами
A0000h,A0000h+1... A0000h+319 - всего 320 последовательных байт.
Вспоминаем, как процессор может делать это:
mov ax,0A000h {Заносим в регистр AX число 0A000h}
mov es,ax {Копируем число из регистра AX в регистр ES, фактически - 0A000h}
mov byte ptr es:[0],14 {Записываем по адресу 0A000hx16+0 байт значением 14}
mov byte ptr es:[1],14 {Записываем по адресу 0A000hx16+1 байт значением 14}
mov byte ptr es:[2],14 {Записываем по адресу 0A000hx16+2 байт значением 14}
...
Не надоело ? Верно, надо делать цикл. Как и в других языках программирования,
в асме тоже есть циклы. Что же за цикл нам нужен ? Цикл из 320 повторений,
в цикле должен вертеться индекс, позволяющий нам записывать СМЕЩЕНИЕ
адреса в команде от 0 до 319. СМЕЩЕНИЕ - это та часть эффективного адреса,
который процессор вычисляет из команды и добавит его к СЕГМЕНТНОМУ
регистру, умноженному на 16:
mov byte ptr es:[1],14
Эффективный адрес = es * 16 + 1
СЕГМЕНТНАЯ ЧАСТЬ СМЕЩЕНИЕ.
В этой команде смещение указано явно. Жестко. Сколько бы раз мы не выполнили
эту команду, она запишет байт 14 в одну и ту же ячейку памяти. Если только не
менять значение сегментного регистра es. Почему бы нам не поменять его ?
А вот, например, почему: нетрудно заметить, что ЛЮБОЕ значение в сегментном
регистре es при смещении = 1 нам НИКОГДА не позволит писать в ячеку памяти
номер A0000h(подумайте, почему !. Теперь вспомним, что в Уроке 2 мы проходили
так называемую КОСВЕННУЮ адресацию:
mov bx,1
mov byte ptr es:[bx],14 {Записываем по адресу 0A000hx16+bx байт значением 14}.
Процессор использовал для получения смещения регистр ! А регистры мы можем
менять ! Тогда начнем писать цикл:
mov ax,0A000h {Заносим в регистр AX число 0A000h}
mov es,ax {Копируем число из регистра AX в регистр ES, фактически - 0A000h}
mov bx,0 {Смещение первой точки равно 0}
mov byte ptr es:[bx],14
ADD bx,1 {Добавить к регистру BX 1 !}
Вот, новая команда(ADD) ! До сих пор мы работали с одной командой засылки
значения - mov(от move, англ.). А команда "ADD" (addition, сложить) позволяет
добавлять к приемнику операции (здесь - регистр BX) значение источника - "1".
И регистр BX станет равен 1 !
Что же дальше ? Последуем классическому типу циклов:
repeat
{Тело цикла}
Until {Условие=TRUE}
Очевидно, что телом цикла у нас выступает команда
закраски - mov byte ptr es:[bx],14. Командой add bx,1 мы изменили регистр BX
и можем анализировать его состояние (Условие=TRUE). Что же мы будем
анализировать ? Если мы хотим задать смещение от 0 до 319, то нам годятся
все значения bx в этом диапазоне, иначе цикл надо прерывать. Делаем это:
add bx,1 {inc(BX) или BX:=BX+1 или BX++}
CMP bx,319 {Сравнить BX и 319}
JBE МЕТКА_НАЧАЛА_ЦИКЛА - @repeat.
Опять, опять новые команды ! Не бойтесь, их вообще не так много, а выучить
их проще простого ! Я обязательно далее раскажу подробно о командах, а пока
лишь вкратце:
CMP bx,319 {COMPARE BX WITH 319}
JBE @repeat {JUMP IF BELOW OR EQUAL}
Вы видите расшифровку сокращений этих команд. CMP-это сравнение.
BX с 319. По результатам этого сравнения мы можем писать команды
переходов. А фактически - приказать процессору перейти на конкретную
команду, да еще и в зависимости от результата сравнения - JBE.
А вот как выглядит весь код:
begin
{Устанавливаем графический режим монитора 13h, 320x200, 256 цветов}
asm
mov ax,0013h
int 10h
end;
asm
{Кусок рисования}
mov ax,0A000h
mov es,ax
mov bx,0
@Repeat:
mov byte ptr es:[bx],14
add bx,1
cmp bx,319
jbe @Repeat
{Кусок рисования - Закончен}
end;
{Ожидаем нажатия клавиши}
asm
mov ah,0
int 16h
end;
{Устанавливаем текстовый режим монитора 03h, 80x25, 16 цветов текста и фона}
asm
mov ax,0003h
int 10h
end;
end.
Примечание ! Обратите внимание, что я выделил комментарием содержательную
часть кода, назвав его "Кусок рисования". Далее я не буду приводить весь текст
этой небольшой программы, Вам досточно будет заменить только код, названный
"Кусок рисования". Остальные команды нам пока непонятны и не будут меняться
ближайшее время.
Выполните этот код. Убедитесь в наличии желтой полосы вверху экрана.
Метка начала цикла у нас называется @Repeat. Почему вначале @ ?
Это особенность asm-вставок языка Паскаль. В настоящем ассемблере
Вы можете извращаться так, как вам хочется, хотя это ограничение не слишком
сильное. Вы могли назвать метку @MY_LABEL. Важно одно: Вы сравнили командой
CMP регистр BX с 319, и, если его значение МЕНЬШЕ(BELOW) или(OR)
равно(EQUAL) 319, то передаете управление на эту метку.
Примечание: Вам должно быть непонятно, ОТКУДА команда JBE знает про
результаты выполнения команды сравнения (CMP).
Усложним слегка пример. Хочу, чтобы мы нарисовали желтую полосу не по
первой сверху строке, а по строке номер 160 ! Думаю, Вам понятно, где
начинаются байты, отвечающие этой строке:
Номер пикселя по
горизонтали/вертикали - номер байта в памяти
0 1 2
160 A0000h+160*320 A0000h+160*320+1 A0000h+160*320+2...
А это означает, что начальное смещение в цикле надо задать просто
не с нуля, а с 160*320:
{Кусок рисования}
mov ax,0A000h
mov es,ax
mov bx,160*320 {Начальное смещение !}
@Repeat:
mov byte ptr es:[bx],14
add bx,1
cmp bx,160*320+319 {Внимание ! Мы изменили и эту команду !}
jbe @Repeat
{Кусок рисования - Закончен}
Воткните этот кусок вместо прежнего и выполните программу. Убедитесь, что
все хоккей - желтая полоса посреди экрана.
Важный момент:
cmp bx,160*320+319 {Внимание ! Мы изменили и эту команду !}
У нас регистр BX уже меняется не от нуля ! И закончим мы рисовать не тогда,
когда он станет 320, а когда 160*320+320 !
А теперь еще навороченне !!! Мы можем задавать эффективный адрес, варьируя
две компоненты: сегмент и смещение. Только что мы сместили полосу на экране,
меняя смещение. Это было естественно, поскольку мы занесли в регистр es такое
число, что процессор, умножая его на 16, как раз втыкался в начало видеобуфера:
es * 16 = 0A000h * 16 = 0A0000h !
И нам было наглядно задавть смещение от нуля(словно индексация в массиве).
Но мы попробуем устроить себе трабл и использовать другое (не 0A000h)
значение, заносимое в сегментный регистр es, чтобы нарисовать ту же желтую
линию в 160 строке. Нам надо задавть в командах такие вот адреса:
Номер пикселя по
горизонтали/вертикали - номер байта в памяти
0 1 2
160 A0000h+160*320 A0000h+160*320+1 A0000h+160*320+2...
A0000h бралось ранее из es. Смещения - 160*320+0,1,2.. - из bx.
А можно ли задать так значение es, чтобы смещение, к примеру,
начиналось с 0 ? Вот так:
0 1 2
160 X+0h X+1 X+2... - ?
Здесь X - это неизвестное. Оно должно быть получено процессором так:
X = es*16
А ячейки памяти должны иметь одинаковые адреса:
X+0h = A0000h+160*320.
Решая это несложное уравнение, найдем, что X = AC800h = 706560.
Значит, es = AC800h / 16 = AC80h ! И измененный код готов:
{Кусок рисования}
mov ax,0AC80h
mov es,ax
mov bx,0 {Начальное смещение ! - опять нуль !}
@Repeat:
mov byte ptr es:[bx],14
add bx,1
cmp bx,319 {Внимание ! Мы изменили и эту команду ! - вернули !}
jbe @Repeat
{Кусок рисования - Закончен}
Заделаем этот пример понагляднее ! Сначала нарисуем желтую полосу
в строке 160 "обычным" способом - занося перед циклом в регистр es число
0A000h, а потом нарисуем поверх желтой полосы фиолетовую, но уже занося
в es значение 0AC80h ! Вот кодец:
{Кусок рисования}
mov ax,0A000h
mov es,ax
mov bx,160*320 {Начальное смещение !}
@RepeatYellow:
mov byte ptr es:[bx],14
add bx,1
cmp bx,160*320+319
jbe @RepeatYellow
{Остановим программу известным нам способом}
mov ah,0
int 16h
{Рисуем, но уже другим способом !}
mov ax,0AC80h
mov es,ax
mov bx,0 {Начальное смещение ! - опять нуль !}
@RepeatMagneta:
mov byte ptr es:[bx],13 {Цвет не желтый !}
add bx,1
cmp bx,319
jbe @RepeatMagneta
{Кусок рисования - Закончен}
Проверьте этот код !
Ну вот. А теперь я хочу, чтобы Вы выполнили два небольших заданьица:
1) Заполнить ВЕСЬ экран каким-нибудь цветом. Желательно - красным.
2) Нарисовать горизонтальную полосу зеленого цвета в 64 строке экрана:
- занеся в регистр es адрес начала видеобуфера;
- занеся в регистр es такое значение, чтобы рисовалась та же полоса в
64 строке экрана, Но вот смещение Вы бы задавали с нуля ! |