Код:
;ПК8000
;Прекрасный ассемблер КР580ВМ80А
;http://asdasd.rpg.fi/~svo/i8080/
;Программа позволяет клавишами управления курсором перемещать точку по
;экрану. Процедура определения адреса точки в видео-ОЗУ и маски выделена
;в отдельный модуль, который вызывается из процедур "зажигания" и
;"гашения" точки.
.binfile GraphCursor.bin
.org 4000h ;начальный адрес программы в ОЗУ
;---------------------------------------------------------------------------------
; Константы
;---------------------------------------------------------------------------------
;Включение режима экрана. Точка входа в ПЗУ: 006Ah или 286Dh A=режим {0,1,2}
Screen equ 286dh
;Опрос нажатия Упр+Стоп.3759h Если «Упр+Стоп», то флаг C:=1
CtrlBreak equ 3759h
;Возврат в СПА
Exit equ 0ea8ah
;---------------------------------------------------------------------------------
; Переменные
;---------------------------------------------------------------------------------
GraphCursor db 0ah,0ah ;координаты графического курсора x,y (0-255,0-191)
AdressVRAMCursor db 0,0
BackgroundCursor db 0
;область в ОЗУ, в которой сохраняется состояние клавиатурной матрицы
Keyboard db 0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh
;---------------------------------------------------------------------------------
; Главный модуль
;---------------------------------------------------------------------------------
Start:
mvi a,02h ;выбор номера режима
call Screen ;установка режима 2
call Grid ;рисуем на фоне сетку
Drawing:
call GetAdressCursor ;определение адреса и маски точки в массиве графики
shld AdressVRAMCursor
xra b ;зажигаем точку
mov m,a ;кладём её в видео-ОЗУ
lxi h,BackgroundCursor
mov m,b ;сохраняем состояние
EventLoop:
call KeyPress ;опрос клавиатуры
jz EventLoop
lxi h,BackgroundCursor
mov b,m ;восстанавливаем видео-ОЗУ
lhld AdressVRAMCursor
mov m,b
;нажатие и анализ
lxi h, Keyboard+8 ;адрес строки с состоянием кнопок управления курсором
mov b, m ;запоминаем это состояние, чтобы не дёргать ОЗУ постоянно
mvi a, 0ffh ;сбросили клавиатуру в исходное состояние
mov m, a
lhld GraphCursor ;помещаем в de координаты графического курсора
xchg
mvi a, 080h ;нажата стрелка вправо?
ana b
cz MoveRight
mvi a, 040h ;нажата стрелка вниз?
ana b
cz MoveDown
mvi a, 020h ;нажата стрелка вверх?
ana b
cz MoveUp
mvi a, 010h ;нажата стрелка влево?
ana b
cz MoveLeft
xchg
shld GraphCursor
call CtrlBreak
jnc Drawing
mvi a,0h ;выбор номера режима
call Screen ;установка режима 0
call Exit ;возврат в СПА
;---------------------------------------------------------------------------------
; Процедуры
;---------------------------------------------------------------------------------
;изменение координат графического курсора
MoveDown:
mvi a, 0c0h ;нижний предел экрана x=192
inr d ;координата y:=y+1
cmp d
rnz ;всё в порядке, мы внутри, штатная ситуация
dcr d ;отмена изменения координаты курсора
ret
MoveUp:
mvi a, 0ffh ;верхний предел экрана y=-1 (логически)
dcr d ;координата y:=y-1
cmp d
rnz ;всё в порядке, мы внутри, штатная ситуация
inr d ;отмена изменения координаты курсора
ret
MoveRight:
xra a ;правый предел экрана x=256 (логически)
inr e ;координата х:=х+1
cmp e
rnz ;всё в порядке, мы внутри, штатная ситуация
dcr e ;отмена изменения координаты курсора
ret
MoveLeft:
mvi a, 0ffh ;левый предел экрана x=-1 (логически)
dcr e ;координата х:=х-1
cmp e
rnz ;всё в порядке, мы внутри, штатная ситуация
inr e ;отмена изменения координаты курсора
ret
;---------------------------------
;Процедура опроса клавиатуры и регистрации нажатых клавиш
;Идея состоит в том, чтобы опросить состояние строк клавиатурной матрицы
;и запомнить его в ОЗУ для дальнейшего анализа
;если флаг Z=0, то ничего не нажато
;hl - адрес последней строки формируемого массива
;b - текущее состояние порта 82h
;с - счётчик количества строк клавиатуры (0ah)
;d - флаг нажатия. Если ничего не нажато, то 0ffh
KeyPress:
di
lxi h, Keyboard+9 ;адрес ОЗУ, в который сохраняем состояние кнопок
mvi c, 0ah ;счётчик количества строк клавиатуры (10)
in 82h ;читаем порт, чтобы не потерять его состояние
ani 0f0h ;формируем в младшем полубайте номер строки клавиатуры
adi 09h ;опрос от 9-й строки к 0-й
mvi d, 0ffh ;флаг нажатия. Если нажато, появляются нулевые биты
loop001:
out 82h ;выбираем текущую строку клавиатуры
mov b, a ;сохраняем состояние порта 82h
in 81h ;читаем состояние текущей строки
mov m,a ;сохранили состояние текущей строки в массиве KeyPress
ana d ;если есть нажатие, то появятся нулевые биты
mov d, a ;запоминаем любые нули, важно их наличие в принципе
dcx h ;очередной байт в массиве KeyPress
dcr b ;очередная строка клавиатурной матрицы
dcr c ;счётчик количества строк
mov a,b ;формируем данные для выбора следующей строки
jnz loop001
mvi a,0ffh ;проверка наличия нажатых клавиш
xra d ;флаг Z=0, если имеется хотя бы один нулевой бит
ei
ret
;---------------------------------
;Процедура определяет адрес байта в массиве графики для графического курсора
;Массив графики располагается в адресах ОЗУ 0000h-17ffh
;Нумерация идёт по принципу знакомест 32х24. Знакоместо (0,0) имеет адреса
;0000h-0007h, (0,1) 0008h-000fh, (1,0) 0100h-0107h и т.д.
;в hl - искомый адрес видео озу, с - счётчик, номер бита точки на которую
;указывает графический курсор, b - байт, извлечённый из видео-ОЗУ
;a - маска для операции с точкой (гашение/свечение)
;
;Временно по найденной координате зажигается точка.
GetAdressCursor:
lhld GraphCursor ;координаты курсора в привычной форме
mov a,l ;взяли координату х
ani 07h ;в аккумуляторе остаток от деления x/8
;это - номер (положение) точки внутри байта в массиве графики
inr a ;используется как счётчик при формировании маски
mov c,a ;запоминаем его в регистре c
mov a,l ;взяли повторно координату х
ani 0f8h ;отбросили остаток от деления x/8
;это - адрес нулевого байта соответствующего знакоместа
mov l,a ;запоминаем его в регистре l
mov a,h ;взяли координату y
ani 07h ;остаток от деления x/8 - номер строки внутри знакоместа
add l ;прибавили его к адресу нулевого байта знакоместа
mov l,a ;младший полубайт адреса готов.
mov a,h ;взяли координату y
ani 0f8h ;отбросили остаток от деления y/8
rrc
rrc
rrc ;разделили нацело y/8
mov h,a ;старший полубайт адреса готов
xra a ;формируем в аккумуляторе маску для видеоОЗУ
inr a ;инициализация маски. Записываем в аккумулятор 01h
loop002:
rrc ;устанавливаем соответствующий бит маски
dcr c ;номер бита, полученный ранее из координаты x
jnz loop002
mov c,a ;сохранили маску в регистр c
;извлечение байта данных из видео-ОЗУ, сохраняем в регистре b
di ;обработчики прерываний в ПЗУ, поэтому запрещаем прерывания
in 80h ;сохраняем состояние порта 80h в регистре d
mov d,a
mvi a,0ffh ;записываем в порт 80h единицы, тем самым выбирая ОЗУ для
out 80h ;всех диапазонов адресов
mov b,m ;читаем байт из видеоОЗУ в регистр b
mov a,d ;восстанавливаем конфигурацию адресного пространства
out 80h
mov a,c ;кладём маску в аккумулятор
ei ;разрешаем прерывания
ret
;---------------------------------
;Процедура Grid рисования сетки на экране
Grid:
lxi h,1800h ;чтобы сработало тело цикла при нулевом значении "+1"
mvi c,08h ;считаем попутно байты внутри знакоместа
Grid001:
dcx h
mvi m,81h ;рисуем вертикальные линии
dcr c
jnz Grid002
mvi m,0ffh ;в нужных местах вертикальные линии заменяем горизонтальными
mvi c,08h
Grid002:
mvi a,07h ;в нижней строке знакоместа тоже горизонтальная линия
cmp c
jnz Grid003
mvi m,0ffh
Grid003:
mov a,l ;проверяем, досчитали до нуля или ещё нет
ora h
jnz Grid001
ret
Если кому интересно, как это работает в эмуляторе, ниже файл состояния эмулятора EMU. Разархивируем, открываем файл из EMU, попадаем сразу в Debugger, в нём жмём F5, оказываемся в среде ASSM, в которой подаём команду на запуск G 400F. Возврат в ASSM по нажатию Ctrl+F12 (Упр+Стоп).