<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 lesson #6 of the Ash School! This lesson tells you about some more instructions, some new registers and a few other tihngs. The last sections contains some information about greyscale graphics.

CONTENTS


OPCODES

It's about time that you should know more than just program. You should also know why things happens, and how it happens. For example, a computer (and a calculator) understands machine code, 0 and 1. That's what the assembler does, it translates the assembly language into machine code. The difference between assembly languages and high level languages is that each assembly instruction has it's own opcode, one or more bytes in a row that does exactly what you've told it. In Pascal or C++ for example, every instruction is usually changed into many opcodes. Many of those opcodes are usually unnecessary or could be done in a smarter way. For example if you do a short for loop in a Ash program, you can use the B register and the djnz instruction, which is made for that purpose. If you did a for-loop in Pascal or C++, it would use a variable, a memory location, to store the counter. Each time the loop was repeated it would load that value into a register, increase it with one, store the variable, check if the limit is reached and then repeat if not end of loop Guess why assembly language is faster...

On a Z80, the processor used by the TI-82 if you have forgotten, each instruction (opcode) takes between 1 and 4 bytes. Small instructions are, of course, generally executed faster, but there are 2 byte instructions that are executed faster than 1 byte instruction (it depends on what the instruction does). It's quite easy to guess how long each instructions is, which is useful when optimizing your code. Each time you load a byte into a 8-bit register, the size is two bytes; one byte to tell the processor that you should load an immediate value into a 8 bit register, and one byte where that value is stored. 16-bit registers takes three bytes. Generally, all instructions that has one byte takes two bytes, and the instructions using words (both as immediate values and addresses) takes three bytes. Other instructions, such as loading register A with E, takes one byte. All bit-manipulating instructions except the logical instructions (and, or, xor, not) takes two bytes though. One prefix ($CB) and then the byte that tells what to do.

Now you know some more about how instructions really look like, which will help in the future...

ALIAS AND MACROS

Aliases and Macros are used by the compiler and has nothing to do with assembly languages actually, but they're used quite often since it makes the source much easier to read. In TI-85.H there are lots of aliases and macros (actually, there are only aliases and macros in it, plus comments).

You could probably guess what an alias is. If you type

 Three = 3

the compiler will substitute Three with 3 in the whole source code (when compiling, it won't change the file itself). All "variables" in a program you define in the beginning are just aliases. You could typ those addresses in the source code if you like that, but it's much easier to read if you make aliases... Also, if you want to change an address, and have that address located in many different places in the program, it would have been smarter to make an alias of it. Then you would only have to change the aliases, not every time in the source where the address occured.

Macros are similiar to aliases, and also similiar to subroutines sort of. To make a macro in Table Assembler (if you use another assembler, CAZ for example, you probably do it another way; read the documentation) you can type like this:

 #DEFINE PushAll   push af \ push bc \ push de \ push hl

If you now type PushAll in your code, the compiler will replace it with those four instructions (must be separated with a backslash). You can also give parameters which is useful: (the parameters must be numbers, or aliases, not registers or addresses)

 #DEFINE GotoXY(xy)  push hl \ ld hl,xy \ ld ($800C,hl) \ pop hl

An example on a macro you use in almost every program is ROM_CALL(addr). Just check ti82.h and you'll see. The parameter in the ROM_CALL macro is an aliases by the way. ROM_CALL(D_ZT_STR) could be replaced with ROM_CALL($3914-$1A), but it just makes the source harder to understand.

Creating your own macros is often useful, but remember that if you make a big macro and use if often, it may make the source big... in the GotoXY example above, the push hl \ pop hl part may be unnecessary if there were no important things in the HL register. Macros should generally be small!

MORE REGISTERS!

If you've programmed for a while you've probably quite often thought: "Damn, I'm out of registers again!" It's a fact that the Z80 has few registers, especially compared to the 68k processor which the TI-92 use... There are actually, some more register you could use, though they have limitations. Here they are:

The index registers IX and IY

These registers are handy. The IY register is used by interrupts and rom calls, and should not be changed unless you've disabled the interrupts (see below). The IX register though is free to use anytime! It works almost exactly like the HL register. When you can use the HL register, you can use the IX register! There is one drawback though, the instructions become one or two bytes longer, depending on the use. If you use IX as a dataregister in an instruction, the instruction becomse one byte longer. That byte is a prefix ($DD) which tells the processor that the next instruction will have IX in it, not HL! This is necessary, because the opcode for an instruction which uses IX is the same as for using HL. It's only the prefix that is the difference.

When you use the IX register as an addressregister, a pointer, it becomse two bytes longer, the prefix and a shift byte. That shift byte is useful sometimes, though most often you don't use it (but the instruction gets two bytes longer anyway). The shift value is a signed byte and therefore has the limitation -128..127.

Here are some examples:

 ld ix,$1234     ; The instructions is one byte longer than ld hl,$1234
 ld (ix+5),62    ; Loads the byte 62 in the memory location IX+5. If
                 ; this instruction was executed right after the previos
                 ; one, it would mean at $1239

If you don't use the shift byte, just (ix), you may still have to put it there, (ix+0). I think you must do this on the CAZ assembler, but you don't have to add that if you use TASM.

If you want to use the IY register, it works the same way. The only difference is that the prefix is $FD instead of $DD. Remember to disable the interrupts (see below)!

When you use the IX register (or IY) you can't use the HL register in the same instructions (neither just H or L). For example, add ix,hl wouldn't work, but add ix,ix works fine.

Maybe you've thought why the index registers aren't divided into two 8 bit register, as HL. The answer is they are, there are just no instructions to access those, or at least those instructions are not documented and not supported by assemblers directly. Of course, if you type the opcodes for the instructions it works just fine! First you should know what these 8 bit registers are called. I don't know if this is the "real" name, but IXh, IXl and IYh, IYl is used (h=higher byte, l=lower byte). The opcodes are quite simple... if the assembler doesn't handle those instructions, type this:

 .db $DD \ ld a,h        ; If  you want to ld a,IXh
 .db $FD \ ld l,e        ; If you want to ld IYl,e
 .db $DD \ ld l,(hl)     ; This should be equal to ld IXl,(IX)

The last one I'm not sure about, but it should work that way... try it yourself.

The "not-so-often-used" R and I registers

These registers are very seldom use, because they are very limited. The only way to "communicate" with them is through the accumulator, so the only allowed instructions are:

 ld a,r
 ld r,a
 ld a,i
 ld i,a

What do they do? The R register is called the "Memory Refresh" registers and is updated after every instructions (added with two I think), thus making this register more or less a random register. The only real use for it is in random generators, though it's not a good random generator... First you'll have to shift it one step to the right (since it will only contain even numbers if you don't change it with ld a,r) and then you'll have to mask away the bits you don't want. If you want a random number between 0 and 15 (the range must be a power of two for good generation, see below), you do like this:

 ld a,r  ; Loads the accumulator with the "random" register
 srl a   ; Shift it logically (put a 0 at the highest bit) one step
         ; to the right
 and 15  ; Masks off the unnecessary bits

This works fine if you don't use if often. If you want 100 random numbers and use this routine the randomize will be bad (if you randomize all numbers at once)! It will be even worse if the range isn't a power of two. Let us say we want a number between 0-6. How would you do? You can type and 6 (figure this one out if you're unsure about binary and masking) because then you would get a strange result (only 0, 2, 4 and 6 would occur). The best way is to do like this:

Random:
 ld a,r
 srl a
 and 7          ; between 0-7
 jr z,Random    ; If it's 0, rerandom

This works, and we'll get a random number between 1-7 (decrease with one to get between 0-6). The problem is the frequency... one of the numbers will appear 2/8 of the time, the others 1/8. Why? Because every time the routine have to rerandomize it means that the R register ends with 4 zeros (the last bit that was shifted away plus the three masked bits). The newly randomized numbers will then always be the same (should be 3 or 4 I think) because the number of instructions are always the same.

How to fix this!?!? Well, you could make a random lookup table of 56 bytes and use the R registers in another way. If you really want to know, you could message me. It still won't be good, but much better. Another way which may work is to use the HALT instruction. More about that instruction another time.

Back to the I register... this one is unimportant most of the time. It's only useful when you program interrupt handler and stuff like that. It holds the higher byte of the address which should be called when an interrups occurs and you're in interupt mode 2. If you don't understand what I'm talking about, don't bother... you don't need to know right now. It'll be better explained in another lesson.

You can use it though as a temporary register if the interrupt mode isn't set to 2. The interrup mode is usually set to 1, but when using grayscale graphics it's set to 2, so then the calc would crash if you changed the I register (same thing happens if Game Wizard or any other memory resident program is run)... Since you can only change it through the A register, it's often not worth that use though. One last thing also, you don't have to reset the register if you change it. It's not used in the ROM memory. The TI-82 was never meant to use the I register or interrupt mode 2. More about that in the lesson about interrupts.

The shadow registers

Now something cool... I think many will be surprised to know that there are 7 more 8 bit registers available to use! These registers are called the shadow registers and are a copy of the common registers, A, BC, DE and HL. Their names are similiar also, you add an ' after the register name: A', BC', DE' and HL'. The drawback is quite big though, you can never access those register through any instruction at all actually... There are instructions, however, that will exchange the ordinary registers with the shadow register. Those instructions are:

 ex af,af'    ; Exchange AF with AF' (F' = shadow flag register)
 exx          ; Exchange BC, DE and HL with BC', DE' and HL'

When using these instructions you must have disabled the interrupts, because they're used by the ROM. You don't have to restore them to what they were before (no push/pop needed). To disable interrupts (and enable them again) you use two short instructions:

 di     ; Disable interrupts
 ei     ; Enable interrups

Another useful exchange instruction

I think I've missed a useful instruction before. Here it is:

 ex de,hl    ; Exchanges the contents in HL and DE

It will only work on those two registers, and you must type ex de,hl not ex hl,de (even though they would do the same thing).

There are three more exchange instructions, but they all have to do with the stack pointer, and are of less use. More about those in an upcoming appendix.

STRING INSTRUCTIONS

Now some very useful instructions which I've probably waited too long with to explain. Have you never wanted to copy many bytes from one place to another? In a simple way? Then you should use LDIR (or LDDR)! First LDI and LDD...

LDI is an instructions that does many things at once, you could say. It does this:

 ld (de),(hl)   ; This instruction isn't even allowed...
 inc de
 inc hl
 dec bc

It copes one byte from HL to DE (where they point) and increase both pointers. LDD does the same except that both pointers are decreased. Both instructions also decrease BC with one.

The LDIR (LDDR) instructions repeats the LDI (LDD) instruction until BC becomes 0! If you want to copy 100 bytes from one place to another, just set HL to the source and DE to the pointer and BC to 100 and run LDIR, and you're done! Here's how to copy the graph memory to the apd buffer:

 ld hl,GRAPH_MEM   ; graph mem
 ld de,$8228       ; apd buffer
 ld bc,756         ; 756 bytes to copy. It's $400 and equal to 128 x 64 / 8
 ldir

Most often you use LDIR but sometimes LDDR is necessary.

There are also some other string instructions (that's what they're called) which will be explained in another lesson.

GRAYSCALE GRAPHICS

Both on the TI-85 and the TI-92 many games uses greyscale graphics. Because of the way the displays are handled on the two calcs it is quite easy to make good looking greyscales. On the TI-82 however there are no easy way to do greyscale, this means that greyscale does not look very good, and therefore it has not been used in any games. For this reason i wont go into details, since you probably wont need it.

Greyscale graphics is not something to do with hardware if anyone though that. What you do is that you have two layers, two pictures. To make it grayscale, those pictures are flipped very fast, in 200 Hz. 2/3 of the time picture 1 is shown and 1/3 of the time the other picture is shown. If a pixel is set on both layers, it is shown the whole time (3/3) and becomes black. If it's only set on the first picture it's visible 2/3 of the time and looks like darkgray. Only on the second picture, 1/3 of the time (lighgray) and if it's never visible, well, then it's white.

That's how it works. If you are interested in greyscale graphics I have written a library which makes it a lot easier to do. The library also includes a test program which should show you have it is used. As always this program can be found on the Ash Homepage or at www.ticalc.org.

To use greyscale you will probably need rutine such as sprite routines and line routines, but since i do find greyscale very useful on the TI-82 i wont go into that.

That was all for now I think.... Hope you've learned something at least... Happy programming!!





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