<IMG SRC="pierotating.gif" WIDTH=80 HEIGHT=80 BORDER=0>

Introduction

Lesson 1
Lesson 2
Lesson 3
Lesson 4
Lesson 5
Lesson 6

TI-85 Page
Ash

Welcome to maybe to most interesting (and definately most wanted) lesson in the Ash school! Now you'll learn what the stack is, and maybe more imporant, graphics! Quick graphics!

CONTENTS


STACK

The stack is one of the most important things you must know when programming. Think of the stack as a deck of cards. When you put a card on the deck, it will be the top card. Then you put another card, then another. When you remove the cards, you remove them backwards, the last card first and so on. The stack works the same way, you put (push) words (addresses or register pairs) on the stack and then remove (pop) them backwards. That's called LIFO, Last In First Out.

The Z80 uses a 16 bit register to know where the stack top is, and that register is called SP. You should NOT change that register with Inc, Dec or something else if you don't know what you're doing!

PUSH AND POP

As you may have guessed, push and pop is used to push things on the stack and then take them off. When you push something, the stack counter will decrease with 2 (the stack "grows" down, from higher addresses to lower, but you usually don't have to bother about that) and then the register pair is loaded into the stack. When you pop, the register pair is first lifted of the stack, and then SP increases with 2.

You can push (and pop) all register pairs: BC, DE, HL and AF. When you pop AF, remember that all flags may be changed. You can't push an immediate value. If you want, you'll have to load a register pair with the value and then push it. Perhaps it's worth noting that when you push something, the contents of the registers will still be the same; they won't be erased or something. Also, if you push de, you can pop it back as hl (you don't have to pop it back to the same register where you got it from).

Actually, the stack is also updated when you call and return from subroutines. The PC (program counter which points at the current instruction being executed) is pushed to the stack and the calling address is loaded into PC. When returning, PC is popped from the stack.

So, when is this useful? It's almost always used when you call subroutines. For example, you have an often used value stored in HL. You have to call a subroutine that you know will destroy HL (with destroy I mean that HL will be changed to another value, which you perhaps don't know). Instead of first saving HL in a memory location and then loading it back after the subroutine you can push HL before calling and directly after the calling pop it back. Of course, it's often better to use the pushes and pops inside the subroutine. All registers you know will be changed are often pushed in the beginning of a subroutine and then popped at the end, in reverse order! Don't forget - last in first out. Of course you shouldn't pop back a register whos contents you're intersted in. If you call a routine to get a value that will be stored in B, you should not push and pop bc since then B would have the same value as you had before you called the routine.

If you want to only push one 8 bit register, you still have to push it's "friend". Therefore, be aware that if you want to store away D with pushing and popping, remember that E will also be changed back to what it was before. In those cases (if you don't want that to happen you should try first to change register (try to store the information in E in another register if you can) or else you have to store it in a temporary variable.

Before executing a program, you should keep track of your pushes and pops, since they are responsible for 99% of all calculator crashes! For example, if you push HL and then forget to pop it back, the next RET instruction will cause a jump to HL, which can be anywhere in the ROM/RAM and the calculator will crash. That's also a way to jump to the location stored in HL, but then you should use JP (HL), which does the same thing.

Push and pop doesn't change any flags, so you can use them between a compare and a relative jump depending on a condition, which is often very useful. I should also add that you can almost always use SP when you can use BC or DE.

Now I think you know everything about the stack. I don't know exactly how much you can push (after a while SP will point to someplace in the RAM where variables are stored, which will corrupt the memory). If anyone figures that out (can't be so hard I guess), I would be interested in knowing.

THE DISPLAY

On the TI82 the only way to communicate with the display is by using the ports. You do this by sending commands to one port and data to another port. The commands and the data is then handled by the display controller which takes care of displaying the data on the display. If you want to make really fast graphics this is the way to do it, but there is a simpler way. The ROM includes a rutine which display the data you place the graph mem, so we can use the this instead. Using the graph mem is slower than using the display controller, so maybe using the display controller should be part of another lesson ?

The graph mem is a part of the RAM which the system uses to save the last graph it displayed in, since we are not going to do any graphing in our assembly program we can use this for displaying graphics. Each byte of the buffer holds 8 pixels (since the pixels are either white or black (0;1) each pixel takes one bit). The top left corner is located at GRAPH_MEM (an alias defined in ti82.h). A simpel formula would tell us where in graph mem we would change one pixel: GRAPH_MEM+y*12+x DIV 8. But we also have to know where in the byte, which bit, we should change! The bits are numbered 0-7, and bit 0 is the right most pixel.

We could very easily write a routine that determinates which bit and which address we should change (try it, it's good practice), but there exist already such a routine in the ROM, so why reinvent the wheel? It's fast (you can make a faster one, but it's not worth it), but it has one stupid "feature": the origin is at the bottom left corner, not the top left. Now that's easy to change, just take y=63-y and that's done with. Before using the routine (called FIND_PIXEL) you also have to swap ROM page to ROM page 4. You only have to do that once in the program (in the beginning) and it looks like this:

    LD A,$8C
    OUT (2),A
    
Put these two rows in the beginning of the program if you use FIND_PIXEL. Now you should ask what input values FIND_PIXEL uses and what registers it will change. You store the x location in B and the y register in C before calling FIND_PIXEL and it'll return the address in HL (but you have to add GRAPH_MEM to it) and 2^bit in A. It's good that it returns 2 is raised to bit, because if you want to set bit A, you can't SET A,(HL) because the first value must be known! Instead, you use logical operators.

PLOTTING PIXELS

Here are four routines for putting a pixel, removing a pixel, changing a pixel, and checking to see if a pixel is lit (all in the graph mem). I have made a small library which includes most of the functions here, butin a version which uses the display controller to write directly to the display. you can find it at the Ash homepage or on www.ticalc.org.

    PutPixel: ; Puts a pixel at B,C
     CALL FIND_PIXEL
     ld de,GRAPH_MEM
     add hl,de
     or (hl)
     ld (hl),a
     ret
     
    RemovePixel: ; Removes a pixel at B,C
     CALL FIND_PIXEL
     ld de,GRAPH_MEM
     add hl,de
     cpl
     and (hl)
     ld (hl),a
     ret
     
    ChangePixel: ; Changes the pixel at B,C
     CALL FIND_PIXEL
     ld de,GRAPH_MEM
     add hl,de
     xor (hl)
     ld (hl),a
     ret
     
    TestPixel: ; Tests the pixel at B,C. If the Z flag is set, no pixel, else a pixel is lit
     CALL FIND_PIXEL
     ld de,GRAPH_MEM
     add hl,de
     and (hl)
     ret
    

All these routines will destroy A, DE and HL, so you should push and pop these if you don't want too loose those values. These routines also shows you that all logical operators are useful.

All routines starts with with three identical rows, which make HL point to the right address and A will be the byte you should "do something with". In PutPixel, we OR A with (HL) and then we put the result (which will be stored in A) back in (HL), since it's in the graph mem location we want the change made. When removing, we changes all bits in A with CPL, and then we mask out the bit to remove. Since A in the beginning only have one bit set (I really hope you get that), A will know have 7 bits set, and the bit that isn't set is the one that will disappear, since AND requires both bits to be set.

The third example is simple, XOR changes the pixel. Since seven bits are unset, those seven location won't change, and the one bit that is set will make a change. The fourth example masks out the tested bit, and if A is zero, there were no pixel and the zero flag will be set (AND sets the zero flag if the result is 0).

It's very important that you understand this section to 100%, else you'll get in big trouble later on. Try rereading it if you don't understand, and experiment by yourself and try to change some instructions and understand what the result should be.

PUT IMAGES

Now when you know how to put pixels, you could also put images. But putting them pixel by pixel is a stupid way to do it. First, if you use FIND_PIXEL for every pixel, it will be slow and it's really simple to do it another way.

Now I'll show you a simple way to put images that you'll just use as background. As you've seen of how the graphic memory works, it's a lot easier to make aligned pictures, that is, you change all bits in one byte. That's the reason why many games have the same GUI with sprites that are 8 pixels wide. That's because you don't have to bother about bits, and it's much faster too.

The image putting routing below always starts on first bit (bit 7, since bit 7 is the leftmost bit in a byte) since that makes it much easier. You just copy 8 pixels at the same time from the data at the end of the program straight into the graph mem. Simple! Then you go to the next byte and do the same until the whole picture is created. Remember this function draw the image in the graph mem not on the display, to show it you have to call DISP_GRAPH.

This is how I do it. Comments below.

    PutImage:    ; Puts the image stored at (HL).  { 28 bytes }
     ld e,(hl)
     inc hl
     ld d,(hl)
     inc hl
     ld b,(hl)
     inc hl
     ld c,(hl)
     inc hl
    PI_NewRow:
     push bc
     push de
    PI_NewCol:
     ld a,(hl)
     inc hl
     ld (de),a
     inc de
     djnz PI_NewCol
     pop de
     ld bc,12
     ex de,hl
     add hl,bc
     ex de,hl
     pop bc
     dec c
     jr nz,PI_NewRow
     ret
    

And here is how the image is stored in the end of the program:

     
    Image:
     .dw GRAPH_MEM+12
     .db 2,16
     .db %00000111,%11100000
     .db %00011000,%00011000
     .db %00100000,%00000100
     .db %01000000,%00000010
     .db %01000000,%00000010
     .db %10000000,%00000001
     .db %10000001,%10000001
     .db %10000010,%01000001
     .db %10000010,%01000001
     .db %10000001,%10000001
     .db %10000000,%00000001
     .db %01000000,%00000010
     .db %01000000,%00000010
     .db %00100000,%00000100
     .db %00011000,%00011000
     .db %00000111,%11100000
    

Let us first look at how the image is stored. First we have defined a word (.dw) with the address. Since these kind of pictures won't move we can calculate the address in the head so we don't have to waste unnecessary bytes in the program to do that. If you want it to move, you could change the routine in the beginning by using a FIND_PIXEL.

On the second row, I've defined first the x width in bytes, that is, how many bytes wide it is, not how many bits!. The second byte is the y width, which is the number of rows. After that the image is stored. I've used % (that means that the value is in binary) so you can see how the sprite will look like easier.

Now let us look at the PutImage routine. The first 8 rows loads DE with the address (note that E is first read, then D. The reason for that is that the LSB is stored first when you define a word, or store a word for that matter), and then loads B with the x size in bytes and C with the y size. Then we push BC because we will return here again, and then we want B to be the same and we push DE so we can pop it back at the end of routine and add 12 (12 = exactly one row down) to it later.

After the label NewCol we just load A with the first byte of the image (HL is after four increases in the beginning now pointing at the image itself) and then we store it at (DE), which is the address in the graph memory. See, we changes 8 pixels at once, without caring about the bits! They'll just fall into place! We also increase HL so it points to the next byte in the image and DE so it points to the next byte in the graph memory. Then we repeat NewCol B times (B was the x size, which is the number of times we want it to be repeated).

When one row has been put on the screen, we pop DE to get the address where it was on the beginning of the line, because then we can easily get to the next row by adding 12 to DE. If we don't push and pop DE in that way, we have to add DE with 12-xsize, and to calculate that takes a few more bytes. But how do we add to DE? Only HL is allowed in 16 bit addition. It's time for a new, very useful instruction, when you deal with 16 bit registers, EX DE,HL. It's really simple, it swaps the contents of DE and HL. This instruction is special, because it doesn't work on any other common registers (it works on some other register, more about that in a later lesson).

Anyway, we just change DE with HL, then add 12 to HL, and then change back. A easy way to do it. Note that we have to load BC with 12, because you can't add with an immediate value. Luckily, the contents of BC doesn't matter for the moment, so we don't have to push it. As you see, we pop it back the next instruction, and then BC will contain the x size and y size once again. Now we decrease the y size, and if it's not zero, we'll jump back and start with a new row. This time when we push BC, the y size will be one less, which is exaclty what we wanted.

Puh... that was a long explanation of how to put an image... Now we're getting to how to put non aligned sprites (that is, we put sprites, images, between two graph memory bytes).

SPRITES

I did not have the time to finish this section, but i decided to release this lesson anyway so you could get started with graphics programming. When i get the time to write this part you should be able to find two routines which display non aligned sprites using the graph mem. A section which includes information on other graphics routines might also be added. Any news on this will be announced on the main page.





This page is maintained by Dines Justesen. All lessons are based on Jimmy Mårdels Online ZShell School.
(c)1999 Content: Dines Justesen and Jimmy Mårdel; Design, Graphics, Animation: mousewasher's WebDesign
These sites are optimised for IE4+ 800x600