Programming
routines running in interrupts can be tricky under FORTH. In this
article I describe the design and code of 12-hour text mode screen
clock running in interrupt. The system of my choice was Super Forth
64 by Elliot B. Schneider. That was the first FORTH system I have had
experience with and I think it is a good choice to develop
applications and operating systems on C64 because of the powerful
extensions that SF-64 offers. In particular a very well defined
assembler vocabulary as well as C64 specific hardware support
(graphics, sprites and sound).
- Interrupt driven code.
The
tight time constraints of interrupt handler make the assembler an
easy choice language for this kind of task. The single “frame” of
the clock routine must complete within 1/60 of a second. FORTH is
fast, however still an interpreted language. Therefore I decided to
code IRQ handler using Super Forth's assembler vocabulary. Before I coded the clock, I needed
some helper definitions and variables. To minimize code size and
maximize speed, I decided to use an array of clock digits, hard coded
in a 120 bytes long vector, that would hold an array of strings:
“00”, “01”, “02” … “58”, “59”.
The
strings representing current hours, minutes and seconds would be then
selected from the table by indexing with clock counters.
FORTH
DEFINITIONS
:
VECTOR ( n --- ADDR )
CREATE
ALLOT
DOES>
+
;
59953
CONSTANT IRQROM ( ADDRESS OF STANDARD IRQ IN ROM )
2
VECTOR IRQCLKPTR ( POINTER TO CLOCK ROUTINE – NEW ORQ ADDRESS )
2
VECTOR IRQROMPTR ( POINTER TO STANDARD IRQ IN ROM )
IRQROM
0 IRQROMPTR ! ( SET POINTER TO ROM IRQ )
120
VECTOR GMS ( VECTOR WITH CLOCK “DIGITS” )
48
0 GMS ! ( SET “00” )
48
1 GMS !
48
2 GMS ! ( SET “01” )
49
3 GMS !
48
4 GMS ! ( SET “02” )
50
5 GMS !
(NOTE:
continue initialization of GMS vector until “59”)
53
116 GMS ! ( SET “58” )
56
117 GMS !
53
118 GMS ! ( SET “59” )
57
119 GMS !
VARIABLE
LICNIK ( COUNTER, SORRY - I AM POLISH :-) )
VARIABLE
GODZINA ( HOURS )
VARIABLE
MINUTA ( MINUTES )
VARIABLE
SEKUNDA ( SECONDS )
(
INITIALIZE VARIABLES )
0
LICNIK !
2
GODZINA !
0
SEKUNDA !
OK,
I am done with helper vectors and variables. Now there is time to
write some code.
In
SF-64 system the assembler routine starts with the word CODE followed
by the name of the routine. The assembler dictionary in SF-64 is
quite sophisticated. Thanks to the general dictionary based
architecture, the labels used in traditional assembler code are not
needed here. We have branch structures (IF, THEN,) as well as loop
control words that allow to write structurally looking code without
the need for jumps and labels. In a way, the SF-64 assembler is like
a macro turbo-assembler that compiles the code as user types it or as
it is being read from the input stream.
My
interrupt driven clock routine is presented below:
CODE
IRQCLOCK
(
UPDATE COUNTERS, CALLED EVERY 1/60 OF SECOND )
LICNIK
INC,
LICNIK
LDA,
60
# CMP,
0=
IF,
0
# LDA,
LICNIK
STA,
SEKUNDA
INC,
SEKUNDA
INC,
SEKUNDA
LDA,
120
# CMP,
0=
IF,
0
# LDA,
SEKUNDA
STA,
MINUTA
INC,
MINUTA
INC,
MINUTA
LDA,
120
# CMP,
0=
IF,
0
# LDA,
MINUTA
STA,
GODZINA
INC,
GODZINA
INC,
GODZINA
LDA,
26
# CMP,
0=
IF,
2
# LDA,
GODZINA
STA,
THEN,
THEN,
THEN,
THEN,
(
UPDATING COUNTERS DONE, NOW PRESENTATION )
GODZINA
LDX, ( USE GODZINA AS INDEX IN GMS VECTOR )
0
GMS ,X LDA, ( LOAD DIGITS FOR CURRENT HOUR )
1056
STA, ( POKE DIRECTLY TO SCREEN MEMORY )
INX,
( IN THE HOURS FIELD )
0
GMS ,X LDA,
1057
STA,
58
# LDA, ( TAKE COLON CHARACTER CODE ':' )
1058
STA, ( POKE TO THE SCREEN MEMORY, NOW – HH:_____ )
MINUTA
LDX, ( REPEAT PROCEDURE FOR MINUTES )
0
GMS ,X LDA,
1059
STA,
INX,
0
GMS ,X LDA,
1060
STA,
58
# LDA,
1061
STA, ( NOW ON SCREEN – HH:MM:__ )
SEKUNDA
LDX, ( … AND FOR SECONDS )
0
GMS ,X LDA,
1062
STA,
INX,
0
GMS ,X LDA, ( PRESENTATION UPDATE COMPLETE )
1063
STA, ( NOW ON SCREEN (NORTH-EAST) – HH:MM:SS )
IRQROM
JMP ( CONTINUE TO ROM IRQ HANDLING ROUTINE )
END-CODE
(
SETUP THE NEW IRQ POINTER TO OUR CLOCK ROUTINE )
FIND
IRQCLOCK @ 0 IRQCLKPTR !
Now
we need a word defined that would allow to set a new IRQ vector.
Remember the algorithm?
Remember the algorithm?
- Mask interrupts (disable).
- Set new IRQ vector.
- Unmask interrupts (enable).
CODE
SET-NEWIRQ ( LO HI --- )
SEI,
( MASK INTERRUPTS )
BOT
LDA, ( READ BOTTOM OF STACK WHERE LO BYTE IS )
788
STA, ( PUT IN THE C64'S IRQ VECTOR - LO )
SEC
LDA, ( READ NEXT STACK BYTE WHERE HI BYTE IS )
789
STA, ( PUT IN THE C64'S IRQ VECTOR – HI )
CLI,
( UNMASK INTERRUPTS )
NEXT
JMP, ( CONTINUE TO THE NEXT FORTH WORD IN QUEUE )
END-CODE
- Control routines.
Now
that I am done with low level code, time to define words that would
allow the user to set the clock and turn it on and off. This is the
easy part:
FORTH
DEFINITIONS
:
SET-IRQCLOCK
SP!
CR
.”
HOURS (1-12)? “ INPUT 2 * GODZINA ! CR
.”
MINUTES (0-59)? “ INPUT 2 * MINUTA ! CR
.”
SECONDS (0-59)? “ INPUT 2 * SEKUNDA ! CR
.”
CLOCK IS SET “ CR SP
;
:
IRQCLOCK-ON
SET-IRQCLOCK
(
LEAVE ON STACK HI/LO BYTES OF NEW IRQ ADDRESS )
1
IRQCLKPTR C@ 0 IRQCLKPTR C@
SET-NEWIRQ
;
:
IRQCLOCK-OFF
(
LEAVE ON STACK HI/LO BYTES OF ROM IRQ ADDRESS )
1 IRQROMPTR C@ 0 IRQROMPTR C@
1 IRQROMPTR C@ 0 IRQROMPTR C@
SET-NEWIRQ
;
This
is pretty much it. It is amazing how efficient programming in
FORTH can be. I love it.
And now some screens for non-believers :-)
And now some screens for non-believers :-)
Code finished loading/compiling. |
Setting up the clock. |
Clock is running (upper right corner). |
Thank you for reading.
Marek
Karcz
10/22/2012
No comments:
Post a Comment