Расширенный экран «Profi», что это такое и как с ним работать. Практикум 001. Загрузка картинки. GRF

в 18:52, , рубрики: cp/m, profi, spectrum, прогрммирование

Продолжение цикла статей по работе с расширенным экраном компьютера "Profi". Первый практикум.

С чего начинается изучение нового? Правильно с "базы". Как правило база - это загрузка и простейший вывод тестовой информации. По этому и тут начнём с загрузки и вывода картинки. Научимся не только выводить данные на экран, но и читать их с носителей.

Статья была опубликована в 2019 году в 26 номере журнала по ZX Spectrum'у "ЗаRulem Печатное Слово".

Статья написана в соавторстве с Вадимом Чертковым.


В предыдущий раз мы рассмотрели строение расширенного экрана компьютера «Profi», этим же материалом открываем раздел «Практикум» в котором будем рассматривать основные (базовые) задачи которые встают при работе с ним. Не планирую давать подробный разбор или универсальные решения. Задача данного материала преодолеть страхи и неуверенность «мол - это сложно, это долго, не смогу разобраться и т.п.» у тех, кто решит заняться этой замечательной машиной.

Весь материал будет ориентирован на работу в ОС CP/M с использованием ассемблера M80. Несмотря на свой почтенный возраст, ассемблер M80 весьма не плох и вполне успешно справляется со своими задачами. Если возникнут вопросы по настройке ОС CP/M или ассемблера M80, обращайтесь, ответим в личном порядке. Возможно, в будущем будет подготовлен материал и на эти темы. Но сейчас эти вопросы выходят за рамки поставленной задачи.

Несмотря на ориентацию материала на ОС CP/M он будет полезен и для работы с расширенным экраном в режиме Спектрум. Подробнее различия между режимами CP/M и Спектрум будет рассмотрены отдельно. Разницы же в этих режимах непосредственно при работе с расширенным экраном нет.

Одной из самой частой задачей при работе с любым экраном, это загрузка на него картинки с внешнего носителя. В настоящее время такими носителями могут быть (касаемо версии Profi 5.06): дисковод FDD, винчестер HDD, CD-ROM, карточки SD Card, карточки CompactFlash, RAM диск. При работе в ОС CP/M, с точки зрения программирования нет ни какой разницы от куда загружается информация, система берёт на себя всю головную боль. Отличаться будет только скорость загрузки данных.

Основных графических форматов в ОС CP/M является формат GRF, его и будем вводить на экран.

Файл GRF состоит из заголовка (1 или более секторов) и графических данных. Формат заголовка выглядит так:

+0

Слово

DW

HSIZE

горизонтальный размер картинки в точках, пикселей

+2

Слово

DW

VSIZE

вертикальный размер в строках картинки, пикселей

+4

Байт

DB

BPP

бит на точку или точек в байте (в зависимости от AMOD)

+5

Байт

DB

AMOD

1 - цвет на каждую точку,

0 - байт атрибутов на байт точек

+6

Слово

DW

BPS

длина образа одной строки растра в байтах

+8

Байт

DB

HLEN

длина заголовка в записях по 128 байт (и 0, и 1 соответствует 128 байт)

+9

Байт

DB

 

0 - признак стандартного формата (если формат будет изменяться, изменится и этот байт)

19 (#13) - файл с палитрой GGGRRRBB

+10

 

DB

 

Резерв 118 х DB 0

или палитра (при +9=19(#13)). 16 байт по 1 байту на цвет в формате GGGRRRBB, далее нули.

Расширенный экран «Profi», что это такое и как с ним работать. Практикум 001. Загрузка картинки. GRF - 1

Как видно размерность картинки может быть до 65535 точек по ширине и высоте, а цветность до 256 цветов на точку, что значительно выше возможностей «Profi». Но пока ещё не неизвестно случаев, что бы кто-либо видел файлы иного формата кроме PROFI-color и PROFI-mono. Конечно, это не означает, что их не бывает.

Для упрощения примем, что выводим картинку размерностью в расширенный экран «Profi» 512х240 пикселей, причём высота может быть любой (в этом случае будет выведены первые 240 строк). Принимаем, что работаем на компьютере «Profi» с палитрой 16 из 256 цветов. И будем различать, только формат «Profi» черно-белые, цветные (стандартные 16 цветов) и с палитрой.

Начнём с беглого разбора (подробный разбор, опять же, выходит за рамки текущего материала), как в ОС CP/M происходит работа с файлами.

Операции с файлами (и все другие) происходят через вызов функций BDOS, для работы с которыми используют стандартные соглашения по вызовам операций BDOS. Доступ, к которым осуществляется через точку входа в ячейке 0005H. При входе в BDOS регистр С содержит номер операции BDOS, а регистровой паре DE содержат байт, слово или адрес информации. BDOS возвращает 1-байтовый результат в регистре А, а 2-байтовые в регистровой паре HL. Кроме того, при возврате регистр А=L, B=H.

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

Для удобства обращения к функциям BDOS был написан макрос «.bdos» (смотри приложение). При его вызове в первом параметре указывается номер операции, а втором дополнительный параметр, если он есть.

Что бы не запоминать номера всех функций, присваиваем им имена. Здесь приведу только те, которые будут использованы в примерах. Полный список можно увидеть в файле «BIOSK.INC» находящемся в архиве с практическими материалами. Сами функции рассмотрим по ходу статьи.

bdExit       equ 0   ; Сброс системы
bdWrite      equ 9   ; Вывод последовательности символов
bdOpen       equ 15  ; Открытие файла
bdReadCon    equ 20  ; Последовательное чтение
bdPDPInst    equ 26  ; Установка адреса буфера ПДП
bdBSVV       equ 50  ; Вызов операции БСВВ или операций расширенной BDOS

Так же в подпрограмме установки палитры используются ресурсы BIOS’а.

BIOS (Basic Input/Output System) - базовая система ввода-вывода (БСВВ). Слово "Concurrent" в названии видимо было выбрано по аналогии с названиями систем фирмы Digital Research: Concurrent DOS и Concurrent DOS/86. Первая версия CBIOS была написана Крестьяниковым А.А. (KiiA) в 1992 году.

С адреса F800h расположен керналь, т.е. группа точек входа. Драйверы подключаются на этапе загрузки через файл CONFIG.SYS. Это более гибкая схема, чем в системах CP/M, где все драйверы "зашиты" в тело BIOS и для изменения какого-либо драйвера необходимо пересоздавать весь модуль.

Мы будем использовать две подпрограммы BIOS’a: SETCMRS и GETADR, а так же вектор (таблицу) TIME.

 SETCMRS – задать значения CMRS. На входе DE=CMRS. Регистр D значение для CMR0 (7FFDh), регистр E значение для CMR1 (DFFDh). Функция выдаёт значения в порты и сохраняет DE в переменной MCMRS. На Profi для управления памятью используется два регистра, в них же задаются биты управления периферией. Т.е. и для изменения доступа к периферии, включение/отключение ПЗУ и для подключения страниц используется данная функция. Мы, в этой программе, будем использовать её для доступа к страницам экранной области. Дополнительно скажем, что все утилиты и драйверы под SP-DOS & CBIOS используют эту процедуру, т.к. регистры CMR0 & CMR1 доступны только на запись. Если все программы будут оставлять последние выданные значения в переменной в ОЗУ, можно легко узнать эти значения.

GETADR – Получить адрес вектора (таблицы). На входе в рег. C номер вектора (таблицы CBIOS), на выходе процедура возвращает в HL адрес начала вектора (таблицы).

TIME – вектор (таблица), состоящий из TIK, SEC, MIN, HOUR, DAY, MONTH, YEAR-1980. Кроме того, со смещением -1 лежит BORD - текущее содержимое регистра бордюра, звука и магнитофона, а со смещением -2 находится INTRFLAG, равный 0FFH во время обработки прерываний, и 0 в остальное время.

Начнем работу программы с установки адреса буфера прямого доступа к памяти ПДП. Это то место, куда будет считываться данные с диска. Устанавливается он операцией BDOS «26. Установка адреса буфера ПДП», которой присвоили имя «bdPDPInst» В нашем случае мы будем читать данные с диска по 1 сектору (128 байт). Прописываем буфер в конце листинга «PDP_SCR: ds 128» и устанавливаем «.bdos bdPDPInst, PDP_SCR».

За число единовременно считываемых с диска секторов отвечает мультиселекторный счётчик, его значение можно изменить операцией BDOS «44. Установка мультиселекторного счётчика» в пределах 1…128 секторов. По умолчанию стоит значение 1, так что в нашем случае менять ни чего не нужно.

Работа с файлами происходит через «Блок управления файлом» (БУФ). Это структура данных, которая организуется и инициализируется транзитной программой, а также используется файловой системой при доступе к файлам через оглавление. Все операции с файлами обращаются за исходной информацией к БУФ. Система хранит текущее состояние файла в БУФ во время исполнения файловых операций.

При вызове операций, осуществляющих доступ к файлам или оглавлению, регистровой паре DE должны содержать БУФ указывающего на файл или файлы, с которыми должна производится операция. Для большинства операций длина БУФ равна 33 байт, а для операций произвольного доступа, вычисление размера файла и свободного места на диске размер БУФ равен 36 байт.

Создать БУФ можно специальной функцией BDOS «152. Подготовка БУФ», но в нашем случае это не потребуется. Для упрощения задачи имя выводимого файла будем передавать в качестве параметра при вызове нашей программы. В «Базовой странице памяти» есть БУФ область 1 (005Ch-0068h), после запуска программ с параметром в него будет занесено-то имя файла, которое идет сразу за именем вызываемой программы. Что нас в данном случае более чем устраивает. Для удобства присвоим буферу символьное имя «scr_buf EQU 005CH».

Теперь у нас всё готово для работы с файлом. Открываем его для доступа. Используем операцию BDOS «15. Открытие файла» «.bdos bdOpen, scr_buf». Операция активизирует указанный БУФ.

В случае ошибки операция «15. Открытие файла» возвращает в регистре A значение 255, а в регистре H код ошибки. Для простоты примем, что любой возврат в регистре A значения 255 означает отсутствие файла на диске. Так что после выполнения операции BDOS «15. Открытие файла» нужно проверить регистр A на равенство содержимого 255, и если это так вывести сообщение об ошибки, выйти из программы.

Для вывода сообщения воспользуемся макросом «say» (смотри приложение), который выводит на экран по текущим координатам текстовую строку, указанную в первом параметре. Если в качестве второго параметра указано имя регистра, то его значение будет выведено в десятичном формате сразу за строкой. Макрос универсальный и сохраняет основные регистры, а значит, не оптимален по скорости и памяти. Но избавляет от многих головных болей и может использоваться при откладке совместно с макросом «pause» (смотри приложение). Который ждет нажатия любой клавиши, если нажата Esc, то производит холодный рестарт системы (то есть выход из текущей программы), а по любой другой продолжает выполнение программы. Макрос «pause» не используется в текущей программе, но уж больно он удобен при отладке, так что я решил его привести.

Теперь, когда мы убедились, что файл существует, и открыли его, читаем из него первый сектор. Это заголовок файла. Для чтения воспользуемся операцией BDOS «20. Последовательное чтение». В качестве параметров передаем БУФ нашего файла. Команда выглядит так «.bdos bdReadCon scr_buf». После выполнения, которой по адресу «PDP_SCR» будет лежать первые 128 байт файла.

Анализ заголовка проводим упрощенный. Текст программы с подробными комментариями приведен в конце статьи, здесь просто опишу порядок и логику действий.

Первым делом проверяем размерность (в пикселях) файла по горизонтали (слово +0 от начала заголовка) на равность 512, то есть ширине экрана. Если они не равны, то выходим с сообщением об ошибке.

Далее, нужно понять, с каким файлом мы имеем дело, для чего читаем байт +9 «Признак стандартности файла». Если в нём содержится значение 19 (#13), то файл с палитрой и нужно её установить.

После чего читаем байт +4 «BPP - число точек в байте», если здесь 8, то файл черно-белый, если 4, то цветной 16 цветов. При этом в переменной «FColor» сохраняем цветность файла: 0 – цвета нет; 1 - стандартные цвета; 2 – палитра 16 из 256 цветов. Это пригодиться в дальнейшем.

Теперь отключаем вывод часов на экран, что бы не портили нам изображение. Для чего воспользуемся макросом «.timeOFF» (смотри приложение). Перед выходом из программы нужно будет, воспользовавшись макросом «.timeON» (смотри приложение) вернуть часы на экран.

Теперь командой «ld de, 0207h or 08D8h; call 0f82dH» включаем экран в нижние 64 кб, графика с #8000, цвет с #4000.

Читаем второй сектор из файла, подпрограммы переброски данных на экран, требуют, что бы начало данных было загружен перед входом в них.

Для ускорения вывода применяем таблицу адресов начал линий по знакоместам. Это избавит от расчетов при переходе между знакоместами. В регистровую пару IX, заносим начало таблицы.

В регистровую пару HL заносим начало буфера прямого доступа к памяти ПДП. А в регистр B – заносим число выводимых знакомест, в данном случае 30.

Инсталляционные работы закончились. Приступаем к непосредственному выводу изображения. Для чего анализируем переменную «FColor», если в ней 0, то выводим черно-белую картинку, иначе цветную.

Вывод цветного и черно-белого изображения будет происходить разными подпрограммами. Это позволит оптимизировать код по скорости, путём отказа от проверки ряда условий. Разберём сначала черно-белый вывод.

Сам вывод происходить внутри трёх вложенных циклов: по знакоместам, по пиксельным линиям в знакоместе, по горизонтали.

Вначале цикла «по знакоместам» в регистровую пару DE, из таблицы заносим адрес первой пиксельной линии в знакоместе. Переход по строкам внутри знакоместа будет происходить увеличением на единицу содержимого регистра D.

Ширина экрана 64 байта, значит, в одном секторе умещается 2 строки. В регистр C заносим число 2, в качестве счетчика строк в секторе.

В цикле «по горизонтали» в одной итерации обрабатываем два смежных байта на двух полуэкранах, соответственно число итераций цикла устанавливаем равной 32. Перебрасываем первый байт через регистр A «ld a, (hl); inc hl; ld (de), a». После переходим на второй полуэкран «res 5, d» и перебрасываем второй байт, возвращаемся на первый полуэкран и смещаемся на знакоместо «set 5, d; inc e».

По окончанию цикла «по горизонтали», уменьшаем счетчик числа линий в секторе на 1. Если достигли нуля, грузим новый сектор, заносим в счетчик числа линий 2, а в регистровую пару HL начало буфера ПДП. Здесь нужно контролировать возврат операции чтения, на окончание файла. Если файл внезапно кончился, выходим из подпрограммы.

Собственно всё! Как закончатся все циклы, картинка будет на экране. Нужно будет уйти на ожидание нажатий любой кнопки, после чего включить часы и выйти из программы.

Отличие загрузки цветной картинки не значительные. У нас теперь в два раза выросла пиксельная строка до 128 байт, то есть до целого сектора, а значит, счетчик линий нам не нужен. Информация в файле чередуется байт графики, байт цвета. После переброски байта графики переходим на цвет «ld a, 11000000B; xor d; ld d, a» и аналогичным образом перебрасываем байт цвета, после чего тем же способом возвращаемся в графику.

Перед выходом из программы, если выводили картинку с палитрой, то восстанавливаем стандартную палитру.

Если у кого остались вопросы он всегда может обратиться по одному из следующих адресов.

  • FidoNet: Tarasow Aleksey 2:5053/57

  • E-mail: tae1980(очень злая собака)yandex.ru

А на форуме, в группе ВК есть много интересного, а все новое будет выкладываться, в том числе и там.

По этой ссылке (https://yadi.sk/d/GCBtRwywJSSmpw или https://vk.com/doc359059980_525959959) можно скачать архив lzh, в котором находятся:

  • m80.com, l80.com - основные файлы пакета m80.

  • LOADGRF.ASM – исподники кода, описанного в этом статье.

  • BIOSK.INC – файл для подзагрузки используемых макросов.

  • LOADGRF.BAT – командные файлы для ассемблирования примера.

  • LOADGRF.COM - уже с ассемблированный код, готовый для запуска. Запускать из командной строки с указанием в качестве первого аргумента имени файла GRF для вывода на экран.

  • Набор черно-белых и цветных GRF файлов для теста.

По этой ссылке (https://yadi.sk/d/105vTeEIqrfbVA или https://vk.com/doc359059980_526413291) можно скачать образ диска в формате «pro» (работа с ним аналогична работе с образами дисков в формате «trd») с описанными файлами. Образ диска загрузочный, так что с него можно загрузиться. На IBM PC наиболее популярны два эмулятора, которые могут эмулировать Profi, это ZXMAK2 (https://archive.codeplex.com/?p=zxmak2) и Unreal Speccy (https://sourceforge.net/projects/unrealspeccy/). У Unreal Speccy больше возможностей, но сложнее настройка, тогда когда ZXMAK2 все настройки проведёт сам.

К сожалению, в настоящее время оба эмулятора отображают цвета расширенной палитры Profi с ошибкой, что сказываться на качестве картинки. Хотя ситуация не приятная, но не критичная. Пока могу посоветовать только, для получения наилучшего результата использовать реальное железо.


Ниже приведен основной текст программы находящейся в файле «LOADGRF.ASM».

          .Z80
          .RADIX 10
;-----------------------------------------------------------

          INCLUDE BIOSK.INC    ; Загрузка макросов.
;----------------------------------------------------------------

scr_buf   EQU 005CH            ;Блок управления файлом (БУФ).
BIOS      EQU 0F800H
GETADR    EQU BIOS+36H
SETCMRS   EQU BIOS+2DH
; Таблицы
TIME      EQU 7
;----------------------------------------------------------------

;Загружаем картинку.
          .bdos bdPDPInst, PDP_SCR ; установили буфер ПДП на нужный адрес
          .bdos bdOpen,    scr_buf
          inc a                ; if a<>255
          jp nz, grf.Title.ok  ; then
          ; else Если на диске нет такого файла
             say ' <!> Нет такого файла <!>'
             .bdos bdExit      ; <!> Выход <!>
grf.Title.ok:
          ;читаем заголовок
          .bdos bdReadCon scr_buf
          ld hl, (PDP_SCR+00)  ; HSize горизонтальный размер
          ld bc, 512           ;; if FHSize=512
          and a                ;;
          sbc hl, bc           ;;
          jr z, grf.Title.1    ;; then
          ;else
             say ' <!> Горизонтальный размер картинки не 512 пикселей <!>'
             .bdos bdExit      ; <!> Выход <!>
grf.Title.1:
          ld a, (pdp_scr+09)   ; признак стандартного файла
          and a                ; if a=0 проверка файла на стандартность
          jr z, grf.Title.std  ; then
          ;else файл не стандартный =19 - палитра 256; иное - ошибка.
             ld a, 02
             ld (FColor), a    ; палитра 256
             ld hl, pdp_scr+10 ; адрес палитры
             call PRGPAL       ; перенос палитры
          jp grf.Title.2
grf.Title.std:  ;файл стандартный
          ld a, (pdp_scr+04)   ; BPP бит на точку или точек в байте
          cp 8                 ; if BPP=8
          jr z,grf.Title.2     ; then картинка чб
          ld a, 01             ; else
          ld (FColor), a       ; BPP=04 стандартные цвета
grf.Title.2:

          .timeOFF             ; убрали часы
          ld de, 0207h or 08D8h ;; включили экран. Графика с #8000, цвет с #4000
          call 0f82dH           ;;

          .bdos bdReadCon, scr_buf ; первый сектор изображения
          ld ix, AdrZNgr       ; таблица адресов знакомест
          ld hl, pdp_scr       ; hl=адрес начала ПДП
          ld b, 30             ; число выводимых знакомест
          di
             ld a, (FColor)    ; a=наличие цвета (0 - чб, 1-стандартные цвета, 2-палитра)
             and a             ;; if a=0
             jp z, grf.chb     ;; then выводим чб картинку
             jp    grf.color   ;; else выводим цветную картинку
grf.endscr:
          ei
grf.pause:
             ld d, 0
             call 0F806H
          cp 0
          jp z, grf.pause
          ld d, 0
          call 0F809H
          .timeON              ; включили часы
          ld a, (FColor)       ; a=наличие цвета (0 - чб, 1-стандартные цвета, 2-палитра)
          ld hl, 0             ; стандартная палитра
          cp 2                 ;; if a=2
          call z, PRGPAL       ;; then устанавливаем стандартную палитру
          .bdos bdExit         ; <!> Выход <!>
;----------------------------------------------------------------------

grf.chb:
;Выводим черно-белую картинку шириною 512 пикселей.
;Портит регистры: hl, bc, de, af, ix, c'
;На входе:
;b  - число выводимых знакомест.
;ix - ссылка в таблице адресов знакомест со смещением на первое выводимое знакоместо.
;hl - адрес начала выводимого фрагмента в ПДП. Первый сектор должен быть уже загружен.
;c' - контроль выхода за переделы ПДП.

grf.chb.zn:                    ; цикл по знакоместам
          push bc
             ld e, (ix+0)      ;; de=адрес начала знакоместа
             ld d, (ix+1)      ;;
             inc ix            ; ix=ix+2 смещаем на следующее знакоместо
             inc ix            ;
             ld b, 8           ; число строк в знакоместе
             ld c, 2
grf.chb.x:                     ; цикл по пиксельным линиям в знакоместе
             .push <de, bc>
                ld b, 32       ; размер полуэкрана
grf.chb.y:                     ; цикл по выводимой ширине экрана
                   ;первый байт
                   ld a, (hl)  ; графика
                   inc hl
                   ld (de), a
                   res 5, d    ;; переходим на следующее знакоместо. выключили бит
                   ;второй байт
                   ld a, (hl)  ; графика
                   inc hl
                   ld (de), a
                   set 5, d    ;; переходим на следующее знакоместо. включили бит
                   inc e       ;;
                djnz grf.chb.y ; цикл по горизонтали
             pop bc
             dec c             ; c=c-1; if c=0
             jr nz, grf.chb.1  ; then
                push bc        ; else
                   .bdos bdReadCon, scr_buf ; последовательное чтение
                pop bc
                ld hl, pdp_scr ; начало ПДП
                ld c, 2
                and a            ; if a=0
                jr z, grf.chb.1  ; then операция завершена без ошибки.
                   .pop <de, bc> ; else
                   jp grf.endscr ; <!> Выход по ошибки
grf.chb.1:
             pop de
             inc d             ; опускаемся на следующую пиксельную линию в знакоместе
             djnz grf.chb.x    ; цикл по линиям
          pop bc
          djnz grf.chb.zn      ; цикл по знакоместам
       jp grf.endscr           ; <!> Выход <!>
;----------------------------------------------------------------------

grf.color:
;Выводим цветную картинку шириною 512 пикселей.
;Портит регистры: hl, bc, de, af, ix, c'
;На входе:
;b  - число выводимых знакомест.
;ix - ссылка в таблице адресов знакомест со смещением на первое выводимое знакоместо.
;hl - адрес начала выводимого фрагмента в ПДП. Первый сектор должен быть уже загружен.
;c' - контроль выхода за переделы ПДП.

grf.color.zn:                  ; цикл по знакоместам
          push bc
             ld e, (ix+0)      ;; de=адрес начала знакоместа
             ld d, (ix+1)      ;;
             inc ix            ; ix=ix+2 смещаем на следующее знакоместо
             inc ix            ;
             ld b, 8           ; число строк в знакоместе
grf.color.x:                   ; цикл по пиксельным линиям в знакоместе
             .push <bc, de>
                ld b, 32       ; размер полуэкрана
grf.color.y:                   ; цикл по выводимой ширине экрана
                   ;первый байт
                   ld a, (hl)      ; графика
                   inc hl
                   ld (de), a
                   ld a, 11000000B ; переходим на цвет
                   xor d           ;
                   ld d, a         ;
                      ld a, (hl)   ;; цвет
                      inc hl
                      ld (de), a
                   ld a, 11000000B ; переходим на графику
                   xor d           ;
                   ld d, a         ;
                   res 5, d        ;; переходим на следующее знакоместо. выключили бит
                   ;второй байт
                   ld a, (hl)      ; графика
                   inc hl
                   ld (de), a
                   ld a, 11000000B ; переходим на цвет
                   xor d           ;
                   ld d, a         ;
                      ld a, (hl)   ;; цвет
                      inc hl
                      ld (de), a
                   ld a, 11000000B ; переходим на графику
                   xor d           ;
                   ld d, a         ;
                   set 5, d        ;; переходим на следующее знакоместо. включили бит
                   inc e           ;;
                djnz grf.color.y   ;  цикл по горизонтали
                .bdos bdReadCon, scr_buf ; последовательное чтение
                ld hl, pdp_scr      ; начало ПДП
                and a               ; if a=0
                jr z, grf.color.end ; then операция завершена без ошибки.
                   .pop <de, bc, bc> ; else
                   jp grf.endscr    ; <!> Выход по ошибки
grf.color.end:
             .pop <de, bc>
             inc d                  ; опускаемся на следующую пиксельную линию в знакоместе
             djnz grf.color.x       ; цикл по линиям
          pop bc
          djnz grf.color.zn         ; цикл по знакоместам
       jp grf.endscr                ; <!> Выход <!>
;----------------------------------------------------------------------

;Программирование палитры.
;На входе: hl  - адрес новой палитры или 0 для установки стандартной [0].
;
;Портит: всё.

PRGPAL:
      di
         push hl
            ld c, TIME        ;; C=номер таблицы. 7 - TIME вектор TIK,SEC,MIN,HOUR, DAY,MONTH,YEAR-1980. Кроме того , со смещением -1 лежит BORD
            call getadr       ;; Получаем адрес таблицы
            dec hl            ;; смещением -1 на BORD
            ld a, (hl)        ;;
            ld (?BORD), a     ;; Что бы не расчитывать в цикле
            ld c, 0           ; C=номер таблицы. 0
            call getadr       ; Получаем адрес таблицы
            ld a, (hl)        ;
            ld e, a           ;
            inc hl            ;
            ld a, (hl)        ;
            ld d, a           ;
         pop hl
         push de
            push hl
               set 5, d
               call SETCMRS
            pop hl
            ld a, h           ; if hl<>0
            or l              ;
            jr nz, PRGPAL.P1  ; then
            ld hl, Palette.Std; else
PRGPAL.P1:
            push hl
               ld hl, (38h)
               ex (sp), hl    ; отправили на стек содержимое 38h в hl=hl с входа
               ld de, 0c9fbh  ; ei ret
               ld (38h), de
               ei
                  halt
               di
;порт палитры 7E, до выдачи данных в рег бордера задаем инверсный код номера ячейки 00..0F
;значение цвета задается в битах A15-A8 при выполнении записи в порт 7E,
;командой out (c),e
;а выводимое значение (E) запишется в порт BORDER для следующего цикла

; hl содержит адрес данных палитры
               ld a, (?BORD)
               or 0Fh         ; в А бит звука и код 0F для цвета (ячейка 0)
               ld e, a
               ld c, 7Eh      ; порт записи для данных палитры
               ld d, 16       ; всего 16 ячеек
               out (c), e     ; сейчас вывели в BORDER значение 0Fh
;конечно, произошло и программирование палитры той ячейки, какой BORDER был до этого, скорее всего
;BORD был равен 7, а это номер ячейки 8 (7 xor 0F) но так как потом мы зальём все 16 ячеек это не страшно
               dec e
PRGPAL.prg2:
                  ld a, (hl)  ; значение цвета 8 бит
                  cpl         ; инверитруем (так надо по схеме компа)
                  ld b, a
                  out (c), e  ; задали цвет и номер бордер для следующей ячейки
                  inc hl
                  dec e       ; следующий цвет
                  dec d
               jr nz, PRGPAL.prg2

               ld a, (?BORD)  ; восстановили BORDER
               out (0feh), a
            pop hl
            ld (38h), hl
         pop de
         call SETCMRS
         rst 38h
      ei
      ret
;----------------------------------------------------------------------

Palette.Std:
        DB      0            ; BLACK
        DB      00000010B
        DB      00010000B
        DB      00010010B
        DB      10000000B
        DB      10000010B
        DB      10010000B
        DB      10010010B    ; WHITE

        DB      0            ; gray
        DB      00000011B
        DB      00011000B
        DB      00011011B
        DB      11000000B
        DB      11000011B
        DB      11011000B
        DB      11011011B    ; WHITE (BRIGHT 1)
;----------------------------------------------------------------------

AdrZNgr:   ; Таблица адресов начала знакомест
          dw 0A000H, 0A020H, 0A040H, 0A060H, 0A080H, 0A0A0H, 0A0C0H, 0A0E0H
          dw 0A800H, 0A820H, 0A840H, 0A860H, 0A880H, 0A8A0H, 0A8C0H, 0A8E0H
          dw 0B000H, 0B020H, 0B040H, 0B060H, 0B080H, 0B0A0H, 0B0C0H, 0B0E0H
          dw 0B800H, 0B820H, 0B840H, 0B860H, 0B880H, 0B8A0H
AdrZNgr_end:
;----------------------------------------------------------------

;Информация о загружаемой картинке
FColor:   db 00      ;Наличие цвета. 0-нет;
                     ;grf 1-стандартные цвета; 2-палитра
FPallet:  ds 16      ;Палитра файла.

PDP_SCR:  ds 128
E_PDP_SCR:
          END

Приложение. Используемые макросы

.bdos     macro com, par
;Вызов операции BDOS.
;com  - код операции
;par  - параметр для передачи

             ifnb <par>
                ld de, par
             endif
             ld c, com
             call 0005h
          endm
;-----------------------------------------------------------

say       macro STR, REG
;Вывод строки на экран.
;Все регистры сохраняются.
;STR - строка для вывода. 
;REG - имя регистра (a, sp, hl, de, bc, ix, iy) для вывода в десятичном виде. []
;Сохраняет: af, hl, bc, de

             local BUFF, B_END
             ifnb <str>
                .push <af, hl, de, bc>
                .bdos bdWrite, buff
                jr B_END
BUFF:           db STR, '$'
B_END:
                .pop <bc, de, hl, af>
             endif
             ifnb <reg>
                .push <af, hl, de, bc>
                   .writed REG
                .pop <bc, de, hl, af>
             endif
          endm
;-----------------------------------------------------------

pause     macro
;Макрос ожидания нажатия любой клавиши.
;Если нажатие ECS – сброс системы, выход из текущей программы.
;Сохраняет: af, hl, bc, de

             local p0
             .push <af, hl, bc, de>
p0:             ld d, 0
                call 0F806H
                cp 0
                jp z, p0
                ld d, 0
                call 0F809H

                .pop <de, bc, hl>
                cp 1bh                ;ESC
                jp z, 0
             pop af
          endm
;-------------------------------------------------------------

.writed  macro REG, IND
;Операция 50/243.
;Вывод в десятичном формате.
;Если на входе:
;ING=0 печатаются регистры A,SP,HL,DE,BC,IX,IY. По умолчанию.
;ING=1 печатаются целые числа.
;Портит все регистры.

           local BUFF1
           if IND                    ;IND<>0
              ld de, REG             ;Передали цифру
              ld (BUFF1+1), DE
           else
              ld (BUFF1+1), REG      ;Передали регистр
           endif
           .bdos bdBSVV, buff1
           jr BUFF1+3
BUFF1:     db 0F3H
           dw 0000
        endm
;-----------------------------------------------------------

.timeOFF  macro
;Отключения вывода таймера.
;Используется процедуры BIOS'а. Для драйвера TIME2.DRV

            ld c,80H
            ld d,1BH
            call 0F80CH
            or 080H
            ld c,a
            ld d,01BH
            call 0F80CH
          endm
;-----------------------------------------------------------

.timeON   macro
;Включение вывода таймера.
;Используется процедуры BIOS'а. Для драйвера TIME2.DRV

            ld c,80H
            ld d,1BH
            call 0F80CH
            and 07FH
            ld c,a
            ld d,01BH
            call
          endm
;-----------------------------------------------------------

.Pop   Macro   Items
; (c) PSWsoft

          Irp x,<Items>
             POP x
          Endm
       EndM
;-----------------------------------------------------------

.Push  Macro   Items
; (c) PSWsoft

          Irp x,<Items>
             PUSH x
          Endm
       EndM

Автор: tae1980

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js