128 ANIMATION

from ZX Computing, December 1986



Toni Baker shows how the 128's extra memory

can be used for super smooth animation.





As I sit here at my TV screen I am looking at an amazing effect which

I would not have thought possible only a few months ago. A

multi-faceted spiked shape is rotating before my eyes, taking about

five seconds for each revolution. The shape - which can only be

adequately described as a first-stellated dodecahedron - is drawn as a

framework, so that I can see the back of the shape as well as the

front. It's rather incredible - but beautiful.



But, what is most astonishing of all is that the effect is being

produced by none other than the ZX Spectrum! The secret lies in the

vast untapped resources of memory on the Spectrum 128, making It

possible to store whole screens, and recall them in sequence. Full

screen high resolution animation is at last possible.



Even more astonishing is the knowledge that the BASIC program which

drew the individual frames was knocked up in only a few hours. The

skeletal appearance of the rotating figure is a consequence of the

simplicity of the BASIC program. Had I spent a few days playing with

an art studio program I could have achieved full colour shading with

light glinting off each facet as it passes the screen - but that kind

of modification I shall leave to you. There are many methods of

drawing screen pictures, but the animation effect was produced by a

little piece of machine code of my own.





Memory map



To understand how the program works it is necessary to know all about

the organisation of the memory on the Spectrum 128. This is very

simple, so I shall now explain it. Memory on the 128 is arranged in

PAGES. There are eight such pages, numbered from zero to seven, and

each page of memory contains 16K. Hence we have 16 * 8 = 128K of

memory altogether. In addition there are two ROMs, but these aren't

important as far as this program is concerned.



Addresses in paged memory begin at C000 and end at FFFF. Thus for each

page of RAM the addresses overlap. Address CDEF on page zero is not

the same thing as address CDEF on page one. They are different

locations, despite having the same address. How then is it possible to

access all of this memory if it's all superimposed on top of itself

with the same address referring to any one of eight pages?



The answer is a technique called PAGING. Only one 16K page may be

"paged in" at a time. "Paged in" means that a page may be accessed -

it may be PEEKed or POKEd or used in the normal way. If a page is NOT

paged in then it may not be accessed at all (with just two exceptions

- which we'll come to later).



Because the addresses overlap, only one page may be paged in at a

time. At all times, one of the pages will be paged in. When you use

the computer normally, either in BASIC or machine code then page zero

will be paged in, and it is important for any machine code program to

restore page zero before returning to BASIC.



As traditional machine- codeists will know, memory on the Spectrum

appears to start at 4000h and go continuously all the way up to FFFF,

and then stop. Below 4000h is the ROM, or at least one of the ROMs!

But appears is the operative word. Memory is organised in eight 16K

pages as I've explained, and the appearance of a continuous stretch of

RAM from 4000h to FFFF is a 48K illusion, and it's all brought about

by hardware, not software.





Pages



The first chunk of memory runs from 4000h to 7FFF. This chunk is in

fact RAM page five! The second chunk runs from 8000h to BFFF - this is

RAM page two! The last chunk, which runs from C000 to FFFF is RAM page

zero. In practice this means that If you page in RAM page five then

addresses C000 to FFFF will access precisely the same memory locations

as addresses 4000 to 7FFF. POKEing an address in the range C000 to

FFFF will actually POKE the corresponding address in the range 4000 to

7FFF. In a similar fashion, if page two is paged in then PEEKing an

address in the range C000 to FFFF will PEEK the corresponding address

in the range 8000 to BFFF. Thus the appearance of continuity is

maintained. For the machine code programmer it means that pages two

and five are special. They appear to be fixed in memory, and have

fixed addresses less than C000. Pages two and five are, in fact,

always paged in, and this is an advantage.



Page five is special in another way too - a more familiar way. It

stores the screen. POKEing an address in the range 4000 to 57FF (or an

address in the range C000 to D7FF when page five is paged in) will

directly POKE the screen. This you know, but the Spectrum 128 has not

one but TWO memory mapped screens. Let us for a while explore this concept.





Screens



There are two screens, but only one of them may be visible on your TV

at any one time. Normally this is screen zero. Screen zero is said to

be ACTIVE whenever its contents appear on the TV, and similarly screen

one is said to be ACTIVE whenever ITS contents appear on the TV.

Screen one is stored in page seven, and this is a hardware

manifestation, not a software one, so you cannot change the location

of screen one. In this way page seven is special too. Locations C000

to D7FF store the screen bytes, while locations D800 to DAFF store the

attributes, but remember these addresses refer to RAM page seven, not

to RAM page zero, so it is not sufficient to POKE the addresses. Page

seven must be paged in first.



As far as the screens are concerned, it doesn't make any difference

which page is paged in. Screen zero or screen one may be active

regardless of whichever page of RAM is paged in. Only one screen may

be active at a time (fairly obviously), and it is impossible to

deactivate both screens at once - so either one or the other will

always be showing on the TV.



This means that it is possible to create flicker free animation! If

you draw on screen one whilst screen zero is active then only screen

zero will appear on the screen. Once the drawing is complete then you

can activate screen one and the TV picture will change INSTANTLY!!!

Now, with screen one active, you can draw on screen zero - only screen

one (the previously completed drawing) will be showing on the TV. When

this drawing is complete you can reactivate screen zero. Once again

the TV image will change INSTANTLY with no flicker whatsoever. This,

then, is the principle of my program.



When we write Spectrum 128 addresses down it is conventional to use a

five digit hexadecimal number, rather than a four digit number. The

first digit refers to the RAM page number. In this way I could

uniquely refer to address BEAD on page four as address 48EAD. This

would be distinct from, say, 6BEAD, which refers to address BEAD on

RAM page six. Whilst the machine code instruction set makes it

impossible to refer to such locations directly (eg LD A,(4C000) is

impossible) it is nonetheless a useful notation for we human beings.

In this notation we could say that screen one occupies addresses 7C000

to 7DAFF inclusive (including the attribute bytes).



This notation has its disadvantages too. Because RAM page five is

permanently mapped in at 4000 to 7FFF then we can describe the

position of screen zero in one of two different ways - either as 4000

to 5AFF, or as 50000 to 5DAFF. Both of these descriptions refer to the

same chunk of memory.



For completeness, I should add that there are also two 16K ROMs,

although, as has already been stated, the ROMs aren't really relevant

to this program. The two ROMs each occupy addresses 0000 to 3FFF, so,

as with the RAM pages, only one ROM may be paged in at a time. Using

the same convention as for the RAM pages we can uniquely specify a ROM

address as a five digit hexadecimal number, so that 01234 refers to

address 1234 in ROM zero, whereas 11234 refers to address 1234 in ROM

one. Surprisingly, the ROM which appears to be paged in normally

(which you can PEEK either from BASIC or machine code) is actually ROM

page ONE, not zero. This ROM is the same as the old 16K ROM which was

present on 16K and 48K Spectrums, with just a couple of changes.



Enough of ROMs - let's get back to RAM pages and screens. Exactly HOW

do you page them? The answer is the OUT instruction, and a new system

variable called BANK_M. Its address is 5B5C (or 5DB5C to keep harping

on about the same point over and over again). Figure one will explain

exactly what each of its bits is for. In machine code it is possible

to change RAM page, or to change which screen is active, by the simple

procedure of loading BC with 7FFD, loading the A register with a value

constructed from Figure one, and then performing two steps - in this

order:

(i) Store the value from the A register in the system variable (BANK_M);

(ii) then use the machine code instruction OUT (C),A.

The order of the last two instructions is important. If you put these

the wrong way round then the system will go wrong if an interrupt

occurs between the two instructions - this way round it's quite safe.

It is also possible to change ROM page by this method, but interrupts

must be disabled whilst the last two instructions are carried out in

this case.





--



Figure 1. The meaning of the bits of the system variable BANK_M at

address 5B5C. When output to port 7FFD will change pages as follows:



 --- --- --- --- --- --- --- --- 

|   |   |   |   |   |   |   |   |

 --- --- --- --- --- --- --- --- 

\___ ___/ |   |   | \_____ _____/

    |     |   |   |       |

    |     |   |   |       |

    |     |   |   |       \--------  Bits 2,1,0 = RAM page currently paged in

    |     |   |   \----------------  Bit 3      = Screen number currently active

    |     |   \--------------------  Bit 4      = ROM number currently paged in

    |     \------------------------  Bit 5      = Must be reset always

    \------------------------------  Bits 7,6   = Not used



--





M/C



The program makes use of all of these techniques. The machine code

program is extraordinarily simple, provided you understand the screen

and paging system that I have just described. I have achieved

animation by storing twelve complete screen images throughout the vast

spread of memory available. The attribute bytes in this case are not

saved since they are the same for each frame, but you could adapt the

program to store the attribute bytes as well with no difficulty. I

have stored two complete frames on RAM pages zero, one, two, three,

four and six. I have not used page five because page five contains

screen zero and the BASIC program, along with the system variables,

the machine stack (following the BASIC CLEAR instruction) and so on. I

have not used page seven because page seven contains screen one and a

whole host of new system variables and stuff. Even numbered frames are

stored at address C000 on the relevant page, whIle odd numbered frames

are stored at address D800 on the same page.



Unfortunately it isn't possible to use the machine code LDIR

instruction to transfer bytes from one page of memory to another if

both pages have the same addresses. For instance suppose there were a

frame stored at address 4C000, and I wished to transfer it to address

7C000. The LDIR instruction is not possible. It is possible to load

one byte at a time, provided you change pages between the fetch and

the store, and then back again afterwards, but this takes a

phenomenally long time in machine code terms. To get round the problem

I have made use of a temporary buffer at address 6800 (that is 5E800).

The above example would be solved by paging in page four, using LDIR

to transfer the frame from 4C000 down to 6800, and then paging in page

seven and using LDIR once more - this time to transfer from the buffer

at 6800 up to 7C000.



The machine code is in three parts. It is stored at address B000,

which corresponds to address 2F000. Note that although page two is in

fact used to store screens, these screens occupy locations 2C000 to

2EFFF only. Locations above this are free for machine code and will

not be overwritten by the various frames.





Page A



The first part of the code is called PAGE_A (address B001). It pages

in the required RAM page and activates the required screen, as

specified by the A register, but without changing the current ROM.

This is quite boringly simple. The second piece of code is STORE_FRAME

(address B011) and is called from BASIC to transfer the image

currently on the screen (screen zero that is - the normal screen used

by BASIC) into its specific place in memory. The last piece of code is

called ANIMATE (address B03D) and it is this program which animates

the twelve stored frames at a rate of twelve frames per second. This

rate is completely flicker free and gives one second of continuous

full screen free flowing movement. If the twelve frames are designed

to repeat in a cycle, as in my example, then you have a cycle of

continuous movement which goes on forever. Fortunately my program does

allow you to break out by pressing BREAK.



The BASIC which I have included is an example of how to use the

ANIMATE routine. The outer FOR/NEXT loop, FOR I = 0 TO 11, will draw a

quite pretty geometric figure from twelve different angles. Line 320

will call the machine code STORE_FRAME routine which will store each

picture in memory once it is drawn. Finally, line 340 will call the

ANIMATE routine to set the picture moving. You can adapt, or even

change the BASIC program altogether if you like. The most impressive

thing you could do would be to create twelve full screen pictures

using an art studio type program, and save these on tape once they are

drawn. Then you can rewrite my BASIC program to simply load screen

images from tape and store them in memory one at a time as they are loaded.



This program is quite interesting from a machine code point of view,

and quite impressive from a visual point of view. It is my offering

for the Winter Solstice - a present to you all (or at least those of

you who've got a Spectrum 128 - the Plus Twos out now and you never

know - you might get one as a present this season). Happy Solstice

everyone.







#B000 00       FRAME_NO    DEFB #00

#B001 4F       PAGE_A      LD   C,A          ;C:= required page/screen number

#B002 3A5C5B               LD   A,(BANK_M)   ;A:= current  page/screen number

#B005 E6F0                 AND  #F0

#B007 B1                   OR   C

#B008 01FD7F               LD   BC,#7FFD     ;BC:= port reqd to change page/scr

#B00B 325C5B               LD   (BANK_M),A   ;Store new page/screen

#B00E ED79                 OUT  (C),A

#B010 C9                   RET



#B011 1100C0   STORE_FRAME LD   DE,#C000     ;DE:= addr of even numbered frames

#B014 3A00B0               LD   A,(FRAME_NO) ;A:= frame number to store

#B017 F5                   PUSH AF

#B018 CB3F                 SRL  A            ;Divide by two

#B01A 3002                 JR   NC,ST_FR_2

#B01C 16D8                 LD   D,#D8        ;DE:= addr of odd numbered frames

#B01E FE05     ST_FR_2     CP   #05

#B020 2001                 JR   NZ,ST_FR_3

#B022 3C                   INC  A            ;Note that page 5 must be skipped

#B023 CD01B0   ST_FR_3     CALL PAGE_A       ;Select RAM page A/Screen zero

#B026 210040               LD   HL,#4000     ;HL: points to screen zero

#B029 010018               LD   BC,#1800

#B02C EDB0                 LDIR              ;Store screen in memory

#B02E AF                   XOR  A            ;A:= 00

#B02F CD01B0               CALL PAGE_A       ;Restore RAM page zero/Screen zero

#B032 F1                   POP  AF           ;A:= frame number

#B033 3C                   INC  A            ;A:= next frame number

#B034 FE0C                 CP   #0C

#B036 2001                 JR   NZ,ST_FR_4   ;Jump unless all frames stored

#B038 AF                   XOR  A            ;In which case start again

#B039 3200B0   ST_FR_4     LD   (FRAME_NO),A ;Store new frame number

#B03C C9                   RET               ;Return



#B03D 3E07     ANIMATE     LD   A,#07

#B03F CD01B0               CALL PAGE_A       ;Store RAM page 7/Screen zero

#B042 210058               LD   HL,#5800     ;HL: points to attribute file

#B045 1100D8               LD   DE,#D800     ;DE: points to screen 1 attributes

#B048 010003               LD   BC,#0300

#B04B EDB0                 LDIR              ;Copy attributes into screen 1

#B04D 76       ANIM_LOOP   HALT              ;Wait till next frame

#B04E 2100C0               LD   HL,#C000     ;HL:= addr of even numbered frames

#B051 3A00B0               LD   A,(FRAME_NO) ;A:= frame number to display

#B054 F5                   PUSH AF

#B055 17                   RLA

#B056 17                   RLA

#B057 17                   RLA

#B058 E608                 AND  #08

#B05A 4F                   LD   C,A          ;C:= 00 (even frames) or 08 (odd)

#B05B F1                   POP  AF

#B05C F5                   PUSH AF

#B05D CB3F                 SRL  A            ;A:= page number on which this

                                              frame is stored

#B05F 3002                 JR   NC,ANIM_2

#B061 26D8                 LD   H,#D8        ;HL:= addr of odd numbered frames

#B063 FE05     ANIM_2      CP   #05

#B065 2001                 JR   NZ,ANIM_3

#B067 3C                   INC  A            ;Note that page 5 must be skipped

#B068 B1       ANIM_3      OR   C            ;Incorporate screen bit

#B069 C5                   PUSH BC

#B06A CD01B0               CALL PAGE_A       ;Select RAM page on which frame is

                                             ;stored, and either screen 0 (even

                                             ;frames or screen 1 (odd frames)

#B06D 110068               LD   DE,#6800

#B070 010018               LD   BC,#1800

#B073 EDB0                 LDIR              ;Copy frame into buffer

#B075 C1                   POP  BC

#B076 79                   LD   A,C          ;A:= 00 (evens) or 08 (odds)

#B077 0F                   RRCA

#B078 0F                   RRCA

#B079 EE07                 XOR  #07

#B07B B1                   OR   C            ;A:= 07 (evens) or 0D (odds)

#B07C CD01B0               CALL PAGE_A       ;For even frames select screen 0

                                             ;and RAM page 7; for odd frames

                                             ;select screen 1 and RAM page 5

#B07F 210068               LD   HL,#6800     ;HL: points to copy of frame to

                                             ;display

#B082 1100C0               LD   DE,#C000     ;DE:= address of screen (Note that

                                             ;address C000 on RAM page 5 is the

                                             ;same as address 4000 normally)

#B085 010018               LD   BC,#1800

#B088 EDB0                 LDIR              ;Copy the frame into the screen

                                             ;not being displayed

#B08A F1                   POP  AF           ;A:= frame number

#B08B 3C                   INC  A            ;A:= next frame number

#B08C FE0C                 CP   #0C

#B08E 2001                 JR   NZ,ANIM_4    ;Jump unless all frames displayed

#B090 AF                   XOR  A            ;In which case start again

#B091 3200B0   ANIM_4      LD   (FRAME_NO),A ;Store new frame number

#B094 CD541F               CALL BREAK_KEY    ;Is BREAK key pressed?

#B097 38B4                 JR   C,ANIM_LOOP  ;Loop back to display next frame

                                             ;unless BREAK pressed

#B099 AF                   XOR  A

#B09A CD01B0               CALL PAGE_A       ;Restore RAM page 0/Screen 0

#B09D CF                   RST  #08          ;Generate error report

#B09E 0C                   DEFB #0C          ;"D BREAK - CONT repeats"

