|
Системные программы - Резидентная программа "Часы".
|
Резидентная программа "Часы".
© Константин Парчевский, п. Научный, Крым, 1994.
Внутренние часы SPECTRUMa "тикают" примерно 50 раз в секунду. Эти "тики" подсчитываются резидентной программой и хранятся в переменной TIC. Каждые полсекунды время выводится в правый верхний угол экрана в формате "час: мин", причем, когда TIC=25 - с двоеточием, а когда TIC=50 - без двоеточия). Этим достигается мигание двоеточия с частотой 1 Гц. Однако все оказалось не так просто. Так как внутри процедуры происходит вывод на экран, то мы должны следить за тем, чтобы случайно не сменить текущий канал, ведь процедура может быть вызвана в любой момент времени. Последовательность действий при выводе информации на экран следующая.
1. Очистить место на экране, куда будет выводиться информация, ведь вывод ведётся в режиме OVER 1 (наложить) и новая информация наложится на старую, сделав её нечитаемой.
2. Запомнить, какой канал в данный момент открыт.
3. Открыть канал 2 (вывод на основной экран), вывести текущее время.
4. Восстановить прежний канал.
Ещё одна тонкость, связанная с выводом. Мы хотим видеть на экране десятичное представление времени, в то время как в компьютере вся информация представлена в HEX формате (в конечном итоге в двоичном коде), значит, в процессе вывода мы должны перекодировать числа из шестнадцатеричной системы в десятичную. Это трудоёмко и занимает много времени. (Не забывайте, что программа работает постоянно и весьма важно, чтобы время её выполнения было как можно меньше). Для уменьшения времени перекодировки из HEX- в DEC-систему часы, минуты и секунды представлены в упакованном BCD формате (если, скажем, значение часов равно 21, то в упакованном BCD формате оно будет равно #21). Таким образом, для перекодировки достаточно занести старший и младший полубайты #21 в отдельные байты, прибавить к ним #30 и напечатать получившиеся ASCII коды, как символы. Теперь проблема состоит в том, что мы не можем производить арифметические действия с такими байтами как с обычными числами. К счастью, у профессора Z-80 есть команда DAA десятичной коррекции при работе с числами в BCD формате, так что достаточно давать эту команду после каждой команды, выполняющей арифметические действия - и всё будет в порядке.
Хотелось бы остановиться на методе определения номера текущего потока. По-видимому, в ПЗУ нет стандартной процедуры, выполняющей такие действия (по крайней мере, я такой процедуры не нашел). Где-то в оперативной памяти (не важно, по какому адресу) есть область, содержащая информацию о каналах (смахивает на начало русской народной сказки "В тридевятом царстве, в некотором государстве...). На каждый канал нам отводится по 5 байт. Адрес этой области можно взять из системной переменной CHANS (#5C4F). В системной переменной CURCHL (#5С51) находится адрес первого байта описания текущего канала (в области информации о каналах). Чтобы получить смещение адреса текущего канала относительно начала, необходимо от содержимого CURCHL отнять содержимое CHANS. Запомним этот адрес и обозначим его, скажем, как OFFSET_1. Далее, в области системных переменных с адреса STRMS (#5С14) расположена таблица двухбайтовых чисел, указывающих на то, какой поток с каким каналом связан. Первое число соответствует потоку - 3, следующее - 2, и т.д. Эти двухбайтовые числа содержат значение на единицу большее смещения канала, который связывается с данным потоком, относительно начала области информации о каналах (Уф, ну и предложеньеце! Хорошо бы в грамматику ввести скобки, указывающие приоритет). Короче, если мы начнем сравнивать OFFSET_1+1 с этими двухбайтовыми числами, то номер того числа, с которым совпадет это значение и укажет нам номер текущего потока. Напомним, что потоки нумеруются с -3.
После запуска программы выяснилось, что часы отстают примерно на 2,5 "тика" в секунду. Это, видимо, связано с тем, что дважды в секунду происходит вывод на экран, причем он длится чуть больше 1/50 секунды, так что два "тика" пропускаются полностью и ещё чуть-чуть. В исходный текст уже внесена коррекция (вместо 25 стоит 24, а вместо 50 стоит 48), но отставание на "полтика" в секунду осталось. Можно сократить число "тиков" на единицу, поставив 47, но тогда часы будут спешить на "полтика" в секунду. Количественно отставание на "полтика" в
секунду проявляется в том, что часы за час отстают на 30 секунд, что не так уж и плохо, особенно если учесть, что существенно большей точности добиться невозможно, так как часы останавливаются, когда запрещены прерывания (скажем при работе с магнитофоном (ввод/вывод). Листинг резидентной программы "Часы".
00010 |
|
ORG |
#FDFF |
|
00020 |
|
DISP |
0 - #9000 |
|
00030 |
|
DEFW |
CLOCK |
;Вектор прерывания. |
00040 |
TIME |
DEFB |
21,1,22,0,26 |
;PRINT OVER 1; AT 0,2 6; |
00050 |
|
DEFM |
" 00 00" |
;Маска для печати времени. |
00060 |
HOR |
DEFB |
0 |
;Часы в BCD формате. |
00070 |
MIN |
DEFB |
0 |
;Минуты в BCD формате. |
00080 |
SEC |
DEFB |
0 |
;Секунды в BCD формате. |
00090 |
TIC |
DEFB |
0 |
;Счетчик "тиков". |
00100 |
MSG |
DEFB |
#80 |
;Таблица сообщений для вывода на |
00110 |
|
DEFM |
"Input hour:" |
|
00120 |
|
DEFB |
#A0,13 |
|
00130 |
|
DEFM |
"Input min.:" |
|
00140 |
|
DEFB |
#A0 |
|
00150 |
; Резидентный |
обработчик маскируемого прерывания. |
00160 |
CLOCK |
PUSH |
AF |
|
00170 |
|
PUSH |
BC |
|
00180 |
|
PUSH |
DE |
|
00190 |
|
PUSH |
HL |
|
00200 |
|
LD |
HL,TIC |
;Увеличить значение |
00210 |
|
INC |
(HL) |
;счетчика "тиков". |
00220 |
|
LD |
A,(HL) |
|
00230 |
|
CP |
24 |
;Если (TIC)=24 |
00240 |
|
JR |
NZ,LOC1 |
;то переход. |
00250 |
|
LD |
HL,TIME+8 |
;Прошло полсекунды, (TIC)=24. |
00260 |
|
LD |
(HL) ,": |
;Установить ":" в буфер печати. |
00270 |
|
PUSH |
HL |
|
00280 |
|
CALL |
PTIME |
;Напечатать время. |
00290 |
|
POP |
HL |
|
00300 |
|
LD |
(HL) ,#20 |
|
00310 |
|
JR |
END |
|
00320 |
LOC1 |
CP |
48 |
|
00330 |
|
JR |
NZ,END |
;Переход, если (TIC)=48. |
00340 |
|
LD |
HL,TIC |
;Прошла секунда, (TIC)=48. |
00350 |
|
LD |
(HL) ,0 |
;Обнулить счётчик "тиков". |
00360 |
|
DEC |
HL |
;HL=SEC. |
00370 |
|
LD |
B,1 |
|
00380 |
LOOP1 |
LD |
A,(HL) |
;Число секунд (минут) |
00390 |
|
INC |
A |
;увеличить на 1. |
00400 |
|
DAA |
|
|
00410 |
|
CP |
#60 |
|
00420 |
|
JR |
NZ,LOC2 |
;Переход, если нет переполнения. |
00430 |
|
LD |
(HL) ,0 |
;Иначе обнулить секунды. |
00440 |
|
DEC |
HL |
;HL=MIN (HL=HOR). |
00450 |
|
DEC |
B |
|
00460 |
|
JR |
NZ,LOOP1 |
;Повторить для минут. |
00470 |
|
LD |
A,(HL) |
;HL=HOR. |
00480 |
|
INC |
A |
|
00490 |
|
DAA |
|
|
00500 |
|
CP |
#24 |
|
00510 |
|
JR |
NZ,LOC2 |
;Переход, если нет переполнения. |
00520 |
|
LD |
(HL) ,0 |
|
00530 |
|
JR |
LOC7 |
|
00540 |
LOC2 |
LD |
(HL),A |
;Сохранить часы, мин. или сек. |
00550 |
LOC7 |
CALL |
PTIME |
;Печать с пробелом вместо ":". |
00560 |
END |
POP |
HL |
|
00570 |
|
POP |
DE |
|
00580 |
|
POP |
BC |
|
00590 |
|
POP |
AF |
|
00600 |
|
JP |
#38 |
;На стандартную обработку прерывания |
00610 |
; Процедура печати времени на |
экране. |
00620 |
PTIME |
LD |
DE,TIME+6 |
|
00630 |
|
LD |
HL,HOR |
|
00640 |
|
LD |
A,(HL) |
|
00650 |
|
CALL |
SBYT |
;Преобразовать HOR из BCD в ASCII. |
00660 |
|
INC |
HL |
|
00670 |
|
INC |
DE |
|
00680 |
|
INC |
DE |
|
00690 |
|
LD |
A,(HL) |
|
00700 |
|
CALL |
SBYT |
;Преобразовать MIN из BCD в ASCII. |
00710 |
|
CALL |
CLST |
;Очистка места для печати. |
00720 |
|
CALL |
CHNL |
;Возвращает в B текущий поток. |
00730 |
|
LD |
A, B |
|
00740 |
|
CP |
2 |
|
00750 |
|
JR |
NZ,LOC5 |
|
00760 |
|
LD |
DE,TIME |
;Текущим является вывод на экран |
00770 |
|
LD |
BC, 11 |
|
00780 |
|
CALL |
#203C |
;Напечатать время. |
00790 |
|
RET |
|
|
00800 |
LOC5 |
PUSH |
AF |
;Текущий поток=2, запомнить его. |
00810 |
|
LD |
A, 2 |
|
00820 |
|
CALL |
#1601 |
;Открыть поток "S". |
00830 |
|
LD |
DE,TIME |
|
00840 |
|
LD |
BC, 11 |
|
00850 |
|
CALL |
#203C |
;Напечатать время. |
00860 |
|
POP |
AF |
|
00870 |
|
CALL |
#1601 |
;Восстановить текущий поток. |
00880 |
|
RET |
|
|
00890 |
; Процедура определения текущего потока. |
00900 |
; Возвращает |
в В номер текущего потока (-1,0,1,2,...) |
00910 |
CHNL |
LD |
B,#FF |
|
00920 |
|
LD |
HL,(#5C51) |
;HL=адрес байта текущего канала. |
00930 |
|
LD |
DE,(#5C4F) |
;DE=адрес области с информацией о ;каналах. |
00940 |
|
XOR |
A |
;CY=0. |
00950 |
|
SBC |
HL, DE |
|
00960 |
|
INC |
HL |
;HL содержит ту же информацию, ;что содержится в таблице STRMS. |
00970 |
|
LD |
DE,#5C14 |
;DE=STRMS. |
00980 |
LOOP2 |
LD |
A,(DE) |
;Поиск в табл.STRMS значения HL. |
00990 |
|
CP |
L |
|
01000 |
|
JR |
Z,LOC3 |
;Переход, если L совпало. |
01010 |
|
INC |
DE |
|
01020 |
LOC4 |
INC |
DE |
|
01030 |
|
INC |
B |
;Перейти к следующему слову в таблице |
01040 |
|
JR |
LOOP2 |
;При этом B содержит номер потока. |
01050 |
LOC3 |
INC |
DE |
01060 |
|
LD |
A, (DE) |
01070 |
|
CP |
H |
01080 |
|
RET |
Z ;Закончить, если и Н совпало. |
01090 |
|
JR |
LOC4 |
01100 |
; Процедура очистки области на экране. |
01110 |
; Т. к. |
вывод времени на экран ведется в режиме OVER 1, |
01120 |
;то прежде, чем |
что-нибудь выводить, необходимо очистить |
01130 |
;место. Ниже приводятся адреса экрана, куда будет |
01140 |
;осуществляться |
вывод и которые нужно очистить. |
01150 |
; #401A #401B... |
#401F, #411A #411B... #411F,... |
01160 |
; ... #471A #471B...#471F. |
01170 |
; |
|
|
01180 |
CLST |
LD |
HL,#401A |
01190 |
LOOP4 |
PUSH |
HL |
01200 |
LOOP3 |
LD |
(HL),0 |
01210 |
|
INC |
HL |
01220 |
|
LD |
A, L |
01230 |
|
CP |
#20 |
01240 |
|
JR |
NZ,LOOP3 |
01250 |
|
POP |
HL |
01260 |
|
INC |
H |
01270 |
|
LD |
A, H |
01280 |
|
CP |
#48 |
01290 |
|
JR |
NZ,LOOP4 |
01300 |
|
RET |
|
01310 |
; Процедура перевода упакованного BCD байта в 2 байта |
01320 |
; кода |
ASCII и запись их в буфер печати. |
01330 |
;Входные параметры: А - упакованный BCD байт, |
01340 |
; DE - |
указатель |
на буфер ASCII символов. |
01350 |
SBYT |
PUSH |
AF |
01360 |
|
SRL |
A |
01370 |
|
SRL |
A |
01380 |
|
SRL |
A |
01390 |
|
SRL |
A |
01400 |
|
ADD |
A, #30 |
01410 |
|
LD |
(DE) ,A |
01420 |
|
INC |
DE |
01430 |
|
POP |
AF |
01440 |
|
AND |
#F |
01450 |
|
ADD |
A, #30 |
01460 |
|
LD |
(DE) ,A |
01470 |
|
RET |
|
01480 |
; Процедура инициализации часов. |
01490 |
INIT |
IM |
1 ;Часы - останавливаются. |
01500 |
|
LD |
A, #FD |
01510 |
|
LD |
I,A ;!=старший байт адреса вектора ;прерывания. |
01520 |
|
CALL |
#D6B ;Очистить экран. |
01530 |
|
LD |
A, 2 ;Открыть |
01540 |
|
CALL |
#1601 ;поток 2. |
01550 |
|
LD |
DE,MSG ;DE=адрес таблицы сообщений. |
01560 |
|
XOR |
A ;А=0. |
01570 |
|
LD |
(SEC) ,A ; Удобный случай обнулить секун. |
01580 |
|
CALL |
#C0A ;Печать сообщения "Input hour: |
01590 |
CALL |
INBYT ;Ввод значения для установки часов. |
01600 |
LD |
(HOR),A ;Установить часы. |
01610 |
LD |
DE,MSG |
01620 |
LD |
A,1 |
01630 |
CALL |
#C0A ;Печать сообщения "Input min.:". |
01640 |
CALL |
INBYT ;Ввод значения для установки минут. |
01650 |
LD |
(MIN),A ;Установить минуты. |
01660 |
IM |
2 ;Запустить часы. |
01670 |
RET |
|
01680 |
; Процедура ввода байта в BCD формате. |
01690 |
/Возвращает: А - |
байт в BCD формате. |
01700 |
INBYT CALL |
INDIG |
01710 |
LD |
B, A |
01720 |
SLA |
B |
01730 |
SLA |
B |
01740 |
SLA |
B |
01750 |
SLA |
B |
01760 |
CALL |
INDIG |
01770 |
OR |
B |
01780 |
RET |
|
01790 |
; Процедура ввода BCD цифры. |
01800 |
/Возвращает: А - |
цифра в BCD формате. |
01810 |
INDIG LD |
HL,#5C3B |
01820 |
RES |
5, (HL) |
01830 |
IN1 BIT |
5,(HL) |
01840 |
JR |
Z,IN1 ;Ждать нажатия клавиши. |
01850 |
LD |
A, (#5C08) ;Читать код клавиши из LAST K. |
01860 |
PUSH |
AF |
01870 |
RST |
#10 ;Эхо на экран. |
01880 |
POP |
AF |
01890 |
SUB |
#30 ;Преобразовать код клавиши в BCD цифру. |
01900 |
RET |
|
01910 |
; Процедура деинициализации часов. |
01920 |
SHUT IM |
1 |
01930 |
RET |
|
|
Перед тем, как начать набор текста в ZEUS ASSEMBLER, необходимо выйти в бейсик |
командой "Q", после чего дать команду CLEAR 27999 и вернуться в ZEUS (RANDOMIZE USR |
57344) |
|
|
|
После набора текста |
и его ассемблирования сохраните готовый машинный код командой: |
SAVE |
'clock c"CODE 28159,331. |
|
Бейсик-загрузчик. |
|
10 REM SET Clock = 65269 |
20 REM OFF Clock = 65351 |
30 CLEAR 65022 |
|
40 LOAD "clock c" CODE |
65023 |
50 RANDOMIZE USR 652 69 |
|
ИФК: Кодовый блок |
программы "часы" можно набрать, пользуясь шестнадцатеричным |
дампом с контрольными суммами, который мы даём ниже. |
FDF8 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
2A |
1F |
FE0 0 |
FE |
15 |
01 |
16 |
00 |
1A |
20 |
30 |
92 |
FE0 8 |
30 |
20 |
30 |
30 |
00 |
00 |
00 |
00 |
B6 |
FE10 |
80 |
49 |
6E |
70 |
75 |
74 |
20 |
68 |
26 |
FE18 |
6F |
75 |
72 |
3A |
A0 |
0D |
49 |
6E |
0A |
FE2 0 |
70 |
75 |
74 |
20 |
6D |
69 |
6E |
2E |
09 |
FE2 8 |
3A |
A0 |
F5 |
C5 |
D5 |
E5 |
21 |
0F |
A4 |
FE30 |
FE |
34 |
7E |
FE |
18 |
20 |
0E |
21 |
43 |
FE38 |
09 |
FE |
36 |
3A |
E5 |
CD |
74 |
FE |
D1 |
FE4 0 |
E1 |
36 |
20 |
18 |
28 |
FE |
30 |
20 |
03 |
FE4 8 |
24 |
21 |
0F |
FE |
36 |
00 |
2B |
06 |
FF |
FE50 |
01 |
7E |
3C |
27 |
FE |
60 |
20 |
11 |
BF |
FE58 |
36 |
00 |
2B |
05 |
20 |
F3 |
7E |
3C |
89 |
FE60 |
27 |
FE |
24 |
20 |
04 |
36 |
00 |
18 |
19 |
FE68 |
01 |
77 |
CD |
74 |
FE |
E1 |
D1 |
C1 |
90 |
FE7 0 |
F1 |
C3 |
38 |
00 |
11 |
07 |
FE |
21 |
91 |
FE7 8 |
0C |
FE |
7E |
CD |
E1 |
FE |
23 |
13 |
E0 |
FE80 |
13 |
7E |
CD |
E1 |
FE |
CD |
CD |
FE |
53 |
FE88 |
CD |
AE |
FE |
78 |
FE |
02 |
20 |
0A |
A1 |
FE90 |
11 |
01 |
FE |
01 |
0B |
00 |
CD |
3C |
B3 |
FE98 |
20 |
C9 |
F5 |
3E |
02 |
CD |
01 |
16 |
98 |
FEA0 |
11 |
01 |
FE |
01 |
0B |
00 |
CD |
3C |
C3 |
FEA8 |
20 |
F1 |
CD |
01 |
16 |
C9 |
06 |
FF |
69 |
FEB0 |
2A |
51 |
5C |
ED |
5B |
4F |
5C |
AF |
27 |
FEB8 |
ED |
52 |
23 |
11 |
14 |
5C |
1A |
BD |
70 |
FEC0 |
28 |
05 |
13 |
13 |
04 |
18 |
F7 |
13 |
37 |
FEC8 |
1A |
BC |
C8 |
18 |
F6 |
21 |
1A |
40 |
ED |
FED0 |
E5 |
36 |
00 |
23 |
7D |
FE |
20 |
20 |
C7 |
FED8 |
F8 |
E1 |
24 |
7C |
FE |
48 |
20 |
F0 |
A5 |
FEE0 |
C9 |
F5 |
CB |
3F |
CB |
3F |
CB |
3F |
BA |
FEE8 |
CB |
3F |
C6 |
30 |
12 |
13 |
F1 |
E6 |
E2 |
FEF0 |
0F |
C6 |
30 |
12 |
C9 |
ED |
56 |
3E |
4 F |
FEF8 |
FD |
ED |
47 |
CD |
6B |
0D |
3E |
02 |
AC |
FF00 |
CD |
01 |
16 |
11 |
10 |
FE |
AF |
32 |
E3 |
FF08 |
0E |
FE |
CD |
0A |
0C |
CD |
24 |
FF |
E 6 |
FF10 |
32 |
0C |
FE |
11 |
10 |
FE |
3E |
01 |
A9 |
FF18 |
CD |
0A |
0C |
CD |
24 |
FF |
32 |
0D |
29 |
FF2 0 |
FE |
ED |
5E |
C9 |
CD |
35 |
FF |
47 |
79 |
FF2 8 |
CB |
20 |
CB |
20 |
CB |
20 |
CB |
20 |
D3 |
FF30 |
CD |
35 |
FF |
B0 |
C9 |
21 |
3B |
5C |
61 |
FF38 |
CB |
AE |
CB |
6E |
28 |
FC |
3A |
08 |
4 F |
FF4 0 |
5C |
F5 |
D7 |
F1 |
D6 |
30 |
C9 |
ED |
14 |
FF4 8 |
56 |
C9 |
00 |
00 |
00 |
00 |
00 |
00 |
66 |
|
|