Tutorial 89 v.5

By Anonymous

The very first assmebly tutorial for the TI-89. This comprehensive aritcle gives an introduction to the basics of programming 68K assembly on the TI-89.

The only tutorial for assembly on the 89 yet! :)

Note: Someone make me a nice title page :)

Table of Contents
Introduction............0.0
The Basics..............1.0
Addressing..............2.0
First Instructions......3.0
Variables...............4.0
Branches/Jumps..........5.0
Library Calls...........6.0
Compiling...............7.0
The Stack...............8.0
ROM Calls...............9 0
Graphics...............10.0
Troubleshooting........11.0
More Help/Credits......12.0

Section 0.0 Introduction

The TI-89 graphing calculator is an extremely powerful tool. The potential to execute programs written in 68k assembly makes it even more so. This tutorial assumes that you have a TI-89, know basic fairly well, but don't know anything about assembly languages. Hopefully some people will learn 68k because of this document, and write good games so that I'm not bored in math and physics class. :)

You should also download 68kguide.txt, and 92guide.zip. Although these are for the TI92, they are very usefull to TI89 programmers. These are available at http://www.ticalc.org/pub/text


Section 1.0 The Basics

The TI-89 uses a Motorola 68000 (68k) processor, therefore, we will be learning 68k assembly (ASM). I recommend using PlusShell, since that is what I use, but DoorsOS is also extremely well written.

The processor only understands binary (base 2 number system). I will use a % to denote a binary number in this document. example: %00110101. I will use a $ to denote a hexadecimal number (base 16). example: $A3. Numbers will be decimal unless otherwise specified. All the data that you store, whether its the number 34, or a string of characters, is stored as binary. This comes in usefull in displaying graphics.

At the heart of the TI-89 is a 10mHz 68k processor. There are several important areas of the processor, first are the registers. The registers are small memory locations located on the processor. Using the registers as data locations is sometime neccesary, and always faster than using a storage location in RAM. These registers are all 32bit long, meaning they can store 32 digits of a binary number. There are 8 data registers (D0,D1,D2...D7), that you can use for anything that you want. These are commonly used when calling library functions to supply arguments. These registers can all be used interchangeably, there is no "main" register like the A register in z80 assembly. There are also 8 address registers (A0,A1,A2...A7) These are used mainly to store pointers to data in the RAM, or to point to routines in the RAM that we will jump to. There is also a status register, which we do not need to use yet. Just know that when you use the bxx instructions, you are using the status register. If you want to know more about the status register, read 68kguide.txt, by Jimmy Mardell. It was written for the TI92, but since they are both 68k processors, the status register is the same.

There are three data lengths on the TI-89. A byte (b) is 8 bits, it can store an 8 digit binary number. A word (w) is two bytes, and a longword (l) is two words.

The 68k uses bigendian to store numbers, so that $1234 is stored as $12 $34, not $34 $12 like the z80.

A semi-colon (;) is used to write a comment. Comments are ignored by the assembler, so add them to help whoever is reading your code. A semi-colon means that the rest of that line is a comment.


Section 2.0 Addressing

In assembly we need to keep track of the address in RAM that things are at, such as data, or functions. There are two types of storing this information, relative to our current position, or absolutely. If we store it relatively, we are saying "This piece of data is 95 bytes before where I am now." This is obviously limited, since if we move, as we do when executing programs, then the address is useless. However, it is smaller, so when we are trimming the bytes of our progam, try to use this. The second way is absolute. Everything in the RAM has a number denoting its location. These numbers are usually written in hexadecimal. For instance, the LCD memory of the TI92 is kept at $4440. Absolute addressing allows you to get data from outside the program, look at the VAT (don't worry if you don't know what that is), etc. Both ways of addressing use the address registers.


Section 3.0 First Instructions

Obviously you need to learn some simple instructions, so that you can program anything. :) If you need to move data from one spot to another, you will use the move.x instruction. The move.x instruction takes two arguments, a source and a destination. The x stands for a length, so substitute wither b, w, or l, depending if you want to move a byte, word, or longword.

examples: move.b  d0,d1        ;moves a byte from d0 to d1
move.w  #10,d2       ;Puts the decimal number 10 in d2

Be careful when moving data into a register, if you move a byte, the byte will fill the lowest byte of the register, and leave the rest alone.

The # denotes an immediate value. To move a hexadecimal number do move.b #$F,d0

If you do move.b $F,d0, it will move the byte stored at memory location $F into d0.

Example:

If d0 contains %10100100101001001010010010100100, and I move the byte %00000000 into it, this is what d0 will look like:

%10100100101001001010010000000000.

The next instruction is clr.x. It takes one argument and it sets all bits in that argument to 0, the x again specifies length.

example: clr.l d0 ;sets all bits in d0 to 0

Now for the math instructions. 68k has fairly powerful math instructions. Here are the basic ones:

add.x, sub.x, mulu.x. muls.x, divu.x, divs.x

add.x and sub.x are easy. They add and subtract. If i want to add d0 and d1, and store the result in d1, i do this: add.l d0,d1 Make sure you specify the length. If the sum exceeds the limits of this length, the answer won't be correct, so be careful. If I want to subtract d1 from d0 and store the answer in d0, I do this: sub.l d1,d0. Again, make sure that you specify the length. Now for multiplication. There are two different instructions, muls and mulu. mulu does unsigned numbers, meaning non-negative. muls is capable of doing negative and positive. Either read 68kguide.txt or newbies.txt to find out how the 68k stores negatives. To multiply d0 by d1 and store the answer in d1, do mulu.x d0,d1. Note that you should be sure the numbers won't go over the limits of the data length you choose. Be sure to specify the data length. Next is division. It is almost the same as multiplication. You have divu.x and divs.x. To divide d0 by d1 (using unsigned division), and store the answer in d0, do divu.x d1,d0.


Section 4.0 Variables

You can use variables in 68k, and store them in your program. These variables retain their last value even after you exit the program and re-run it. This is usefull for hi-score lists. This is how you declare a variable. In your source code put this:

var_name: dc.x 0

substitute the variable's name for var_name, and the datalength for x. You can put a starting value in instead of 0.

To use strings do this:

string_name: dc.b "This is a string",0

You must use dc.b because each character of the string takes up a byte. The ,0 adds a null character to the end of the string. This is neccesary so the calculator knows when the string ends.

You can move.x things into variables like this:

move.x #10,var_name

Just be sure to have the datalength of the move less than or equal to the datalength of the variable. :)


Section 5.0 Branches/Jumps

First, we need labels to go to. You define a label like this:

label_name:

That's all! Don't forget the : though.

The first way to go to another memory location to execute code is the equivalent of goto, jmp. You need to have an absolute address to use this, but I don't recommend using it since you can't return from it with an rts. You should use bsr to do unconditional jumps within your program. Just use: bsr label_name and the calculator will jump to the label and continue executing code. bsr stores the address from which the jump was made. If you use the instruction rts, then the calculator will return to the bsr, and go to the next instruction.

example:

bsr     label_name;---|
move.w  d0,d1     ;---+-|
...               ;   | |
...               ;   | |
label_name:       ;---| |
move.w  #10,d0    ;     |
rts               ;-----|

To do a conditional jump, only go to the label if certain conditions exist, we use the cmp and the bxx instructions. You substitute certain letters for xx to specify which conditions the jump is dependent on.

cmp B,A
bxx name_of_label_to_branch_to
then bxx will branch to the label if:

* when working with unsigned integers:

A > B Bhi ( branch if higher )

A ≥ B Bcc ( branch if carry vlear )

A ≤ B Bls ( branch if lower or same )

A < B Bcs ( branch if carry set )

A = B beq ( branch if equal )

A ≠ B bne ( branch if non equal )

* with signed integers:

A > B bgt ( branch if greater )

A ≥ B bge ( branch if greater or equal )

A ≤ B ble ( branch if lower or equal )

A < B blt ( branch if lower )

A = B beq ( branch if equal )

A ≠ B bne ( branch if non equal )

This actually subtracts one operand from the other, stores part of the result in the status register, and the bxx instructions check the flags in the status register, but all you need to know now is how to use it.

example:

move.w #10,d0
move.w #14,d1
cmp d0,d1
beq never_branches_here

Now for an external jump. Use the instruction jsr to do an external jump. It works the same as bsr. It is mostly used to call library functions.


Section 6.0 Library Calls

Library calls allow for programmers to put commonly used functions in one place on the calculator, and let all programs use them. The two most important libraries on the TI89 are util and tios. tios isn't actually a library, it contains ROM calls. Util contains all of the functions that flib has for Fargo 2. I recommend that you read util.htm, included in the PlusShell zip, to learn about it's functions. There are many useful functions in the util library. An example is clr_scr. clr_scr clears the entire screen, and redraws the dividing line near the bottom of the screen. An example of how to call this function follows:

jsr util::clr_scr

That's all you have to do to clear the screen!

A generic example would be this:

jsr lib_name::function_name

NOTE: You MUST include the library header file at the top of your source file, or the compiler won't know what you mean.

example, at the top of your source file type:

include "util.h"

A generic example: include "library.h"

Do this for each library that you will use in your program, also make sure that the library.89z flie is on the calculator when you run your program :)

Read the util.htm file, as well as most of the .h files for the libraries on Dimension-TI, because usually someone has already written the routine you want, and it probably works well.


Section 7.0 Compiling

Well, that is all well and good, but we need to know how to compile. Using PlusShell, I made the following batch file named setup.bat

@echo off
call e:\ti89\shells\plus\setps e:\ti89\shells\plus

substitute the path for plusshell on your computer for e:\ti89...\plus

If you run this file, you can then use asm89 to compile your source file into a .89z file that the calculator can execute. Name your source name.asm, making name whatever you want.

Then, from a DOS prompt, type:

asm89 name

and your program will compile! It will display any error messages that it gets, and if there are none, it will produce name.89z!

Important things to remeber when using asm89: Since it uses a68k to assemble, all instructions must NOT start in the first column. Either add a tab or a space before the instructions, or the compiler will think they are labels. You can use any text editor to make the .asm file, but it must be in .txt format, not MS Word6.0 or anything fancy. I use MS-DOS Editor (edit.com).

The basic for for a program is this:

    	include "tios.h"   ;Include tios.h so we can use the functions in tios

    	include "util.h"   ;Include util.h so we can use the functions in util

    	xdef _main         ;You must have this line, it tells the calc where to begin execution

    	xdef _comment      ;Tells the calc where the comment for the program is 

    	xdef _ti89         ;Tells the compiler that this is a TI-89 program

    _main:                 ;execution begins at the _main label

    	...                ;your code

    	rts                ;exit the program

    _comment:  dc.b    "Hello World!",0  ;This is the comment for the program

    	end                ;Tells the compiler to stop here.  NECCESARY!!!

Notice that the labels _main: and _comment: can begin on the first column. That is because they are labels, and not instructions. Everything else must not begin in the first column. Note that the comments are after ;.


Section 8.0 The Stack

The stack is an area in memory usually pointed to by register a7. It can be used to give arguments to ROM functions, and is also used in bsr's, jsr's, and rts's. You MUST remember to update the stack pointer. Whatever goes on the stack last comes off first. To move some word onto the stack use the move instruction, we don't need a push instruction.

ex: move.w d0,-(a7)

This moves the word stored in d0 to the stack, after decreasing the address pointed to by a7 by 1 word. We MUST decrease the address, or this word will overwrite some other probably IMPORTANT information. :) to get that word off do this:

move.w (a7),d0
lea 2(a7),a7

The parentheses areound a7 mean the data at the address that a7 holds, not the data IN a7. The lea 2(a7),a7 line updates the stack pointer. lea is an instruction that means Load Effective Address. This line takes a7, increases it by 2, and stores it back in a7. We increase by two because previously we had decreased by one word=2 bytes. Remeber to always update the stack pointer (a7) after using it or your calculator will probably crash!


Section 9.0 ROM calls

ROM calls are very useful on the TI-89. TI has already written many useful routines, and we can use them. Routines such as displaying text, changing fonts, and using the linkport can be used by the programmer. Included in this zip you should find tios.fct, which includes all of the functions in the tios library, what they do, and what arguments they take. You call tios functions the same as normal library functions. The arguments are always on the stack however, and the last argument is pushed first. Also, the ROM functions probably use the registers d0-d2 and some of the address registers, so don't count on them having the same values as they did when you called the ROM function.

An example of a ROM function is DrawStrXY.

Tios.fct contains this:

	;-------------------------------------------------------
	; void DrawStrXY(WORD x, WORD y, BYTE *string, WORD color)
	;
	; Function: prints {string} at {x,y} with current font
	;-------------------------------------------------------

These are the options for color, taken from types.txt from fargo2.

color
-----
0 = white on black
1 = black on black (!!!)
2 = white on black (??)
3 = gray on white
4 = black on white

The * in front of string means a pointer to the string, so we need to find the address of the string we want to display. Here is an example program which uses this function.

    	include "tios.h" 
    	include "flib.h" 
    	include "macros.h"
    	xdef _main
    	xdef _comment       
    	xdef _ti89
     _main:
    	jsr flib::idle_loop
    	jsr flib::clr_scr
    	move.w      #4,-(a7)  ;color=black on white
    	pea         _comment  ;nice instruction, pushes the pointer we need for us
                          	;and takes care of the stack.
    	move.w      #10,-(a7) ;y coordinate of text=10
    	move.w      #10,-(a7) ;x coordinate of text=10
    	jsr tios::DrawStrXY   ;Display it
    	lea     10(a7),a7     ;we pushed a total of ten bytes
    	rts	
     _comment:
        	dc.b    "Hello World!",0
	end

Read the tios.fct file for all the other ROM calls. I also recommend getting the fargo2 zip from Dimension-TI for types.txt, handles.txt, and traps.txt. They are all very useful files.


Section 10.0 Graphics

If you want to be a game programmer, you need to know how to do graphics. Well, first is use the libraries :) Read linelib.g and graphlib.h. They have many useful functions. If you want to do your own graphics though, this is how the TI89 does graphics.

Beginning at the address contained in the variable LCD_MEM, there is an area of memory 3840 bytes long. This area of memory is mirrored on the LCD screen of the calculator. If a bit is set in this area of memory, that pixel will be black on the screen. In this are of memory, each row of pixels is 20 bytes long. Then, there is a buffer of ten bytes, used on the 92+, then the next row of 20 bytes, etc. You can move binary numbers into this memory to display things on the screen.

You can also use the bit manipulation instructions. These instructions affect single bits in a byte. All instructions test the bit before affecting it.

BTST Tests a bit

BSET Tests a bit, then set it (1)

BCLR Tests a bit, then reset it (0)

BCHG Tests a bit, then invert it (0 → 1, 1 → 0)

After these instructions you can use the bxx instructions to branch somewhere if the pixel was turned on.

These instructions all take two arguments, first is the number bit to test, second is the address at which the bit is.

There is a VERY useful function in util called find_pixel.

To use find_pixel, we push a word for the y coordinate of the pixel we want, then a word for the x coordinate. Then, remember to update the stack pointer. Then, d0 contains the bit number we want, and a0 the address we want. We can then do bchg.b d0,(a0) to change that pixel! So, to invert the first pixel of the screen (upperleft) and then branch to pixel_was_black if it was set before we changed it we would do this:

move.w #0,-(a7) ;y=0
move.w #0,-(a7) ;x=0
jsr flib::find_pixel
lea 4(a7),a7 ;update stack pointer, 2 words=4 bytes
bchg.b d0,(a0)
bne pixel_was_black

The Dbxx instructions are very useful for graphics, they are 68k's way to do loops.

Dbxx is an instruction that quits loops. The instruction is very similiar to bxx (same conditions are used, except for a few different ones) except that the first operand is a dataregister that will be decreased with one until is reached -1, then the loop stops. The loop can also quit if the flags are set correctly (specified with the condition). You often use DBRA (Never quit) that will quit the loop when the dataregister has reached -1. If you want the loop the be looped 10 times, you should set a dataregister to 9 (since it ends at -1, not 0). See 68kguide.txt for the other conditions.

Here is the scrolling routine that I use in tunnl, it should be usefull for many programs. You can modify it and/or use it, as long as you give me credit somewhere in the text files accompanying the program.

       scroll: ;scrolls the entire screen down one row.  Leaves the top row
               ;however it was before the scrolling, NOT CLEARED.
        lea     LCD_MEM+3810,a0 ; 3840 bytes long-30 bytes to get the last row
        move.w  #1904,d0           
        RepScrl:                   
        move.w  -(a0),30(a0)           
        dbra    d0,RepScrl                             
        rts

Greyscale may be in a future version of this document. Email me or talk to me on IRC if you want me to add it. (See bottom)


Section 11.0 Troubleshooting

In assembly your program will always crash. Just read the code a lot, and try to figure out where the problem is. It helps if you write one small section at a time, and test each section, so you can isolate problems. If you get a bug in a big program, try commenting out some code to see if you can figure out where the bug is. Sometimes you need to take a break from coding, if you can't figure it out. Don't quit! If you need help with your code, see Section 12.0.


Section 12.0 More help/Credits

If you need more help, don't hesitate to contact me.

Skip Jordan - LGB240@aol.com

Cassius on EFNet, DalNet

Places to go if you need more help:

The Assembly 89 list at http://www.ticalc.org, email me, get on IRC and join #dim-ti and #ti on EFNet. mIRC, a shareware Windows IRC client is available at http://www.mirc.co.uk/

This document is not meant to teach you all of 68k, it is meant to give you a start. Read 68kguide.txt, and read source code for other people's programs. Experiment and you will learn more than this document could ever teach you.

If any experienced programmers, or newbies, find errors in this document, PLEASE tell me!!! I will revise it! Just tell me the version number, and what/where the error is. Thanks. Email me/msg me on IRC if you want me to add sections such as greyscale graphics, interrupts, writing your own libraries, or stuff about the VAT.

Also, please let me now if I need to explain certain things better, this is my first tutorial ever, and I would like your opinions.

Thanks to:

David Ellsworth: for Fargo

Rusty Wagmer: for PlusShell

The Doors Team: for DoorsOS

Jimmy Mardell: for tremendous games and 68kguide.txt and sprmaker

Dimension-TI: for a great website. (http://dim-ti.akromix.net/)

The people in #dim-ti and #ti on EFNet for chatting

Programmers for TI89 assembly: For their great work.


Skip Jordan

Last revised 12/23/98

This is version .5 of Tutorial 89.