Table of Contents
The most important part of any program is the ability to interact with the user. So how can you get the calculator to interact with the user? The answer is through the use of input and output methods. Input (prefix: in) is the feedback the user gives the program. Output (prefix: out) is the result of said inputs the program gives the user. There are many ways of getting user input and giving back useful output. The basics will be discussed below.
Displaying Large Text
Displaying text is not as great as it should be. Mostly because it isn't just one instruction like in Ti Basic where you just choose Output(. First you have to set the coordinates of where you want the text to be placed. There are two variables, Rows and Columns. To set the number of rows down, you load A to CurRow. (Current Row) Then you load A to CurCol. (Current Column) Now the calculator knows exactly where to place the text. But before we go any further, you should know that displaying single letters and numbers is different then displaying strings of text.
_PutC is a system call that will display the character on the screen and then move the flashing cursor over to the next place. It "puts" a Character on the screen. Notice that it only displays a character, though. Below is some code that would display text:
Ld A, 2 Ld (CurRow), A Ld A, 6 Ld (CurCol), A Ld A, 'W' b_call(_PutC)
TI's 'ASCII' table of characters
So how does the calculator know that $5A, $38, $30, $20, $52, $4F, $43, $53 means Z80 ROCKS? The answer is the TI 'ASCII' table. It's sort of similar to the ASCII table for computers, except TI changed it slightly. The TI ASCII table is essentially a table of characters that when PutS or any other text displaying routine is called is used to display the correct character. So, when you have a $5A, on the table it equates to the letter 'Z' capitalized, $38 refers to '8', and so on and so forth. But how did we manage to use actual letters earlier and still get the program to work on the calculator? The answer is that the assembler converts each character into the equivalent hex value and stores that in its place. So, you could in theory write a program with nothing but character strings because it to your calculator it is all the same thing (pictures, text, code, data, etc.).
Displaying text one character at a time is extremely time consuming. For this, TI has given us ROM calls that can display entire strings. You will load your coordinates the same, but this time you load the location in RAM the string is stored into HL. _PutS is the new system call and it "puts" a String on the screen.
; Ld HL, string b_call(_PutS) ;more of your code here ret string: .db "The hello wold program is so over rated", 0
As a side note, you'll see that no coordinates have been set. This may sound confusing, but it's perfectly normal. The display character/text ROM calls don't need you to set curRow/curCol every time, but instead take their value as they are when the they are called. It is also important to note that some of the ROM calls will change curRow/curCol.
End Terminating Strings
Why is the string followed by a ,0? The 0 at the end just says that that is the end of the string. If you do not include this, then _PutS will keep displaying characters until it reaches a point in memory that equals 0. Remember, your calculator does not have the ability to distinguish data types, or even code from data.
Length defined Strings
Another way to tell the calculator when to stop displaying text is to have the length of the string be the first byte. Of course, you'll need to use a different ROM call, but sometimes it may be to your advantage to have the string formatted this way as some ROM calls require the length of the string as an input.
Displaying Small Text
It's easy enough to display large text in assembly, so why not small text? There must be some way to display characters and strings in the small font, right? Of course there is! If you have ever programmed TI-BASIC, you'd know that small font is displayed to the "graph screen", and can be displayed anywhere. For small text, though, TI has come up with a different set of coordinate bytes labeled penCol and penRow.
Exactly the same thing as PutC, except it displays in small text.
ld A,6 ;set the coordinates ld (penCol),a ld A,12 ld (penRow),a ld A,'A' bcall(_PutMap)
Again, vPutS is exactly the same as PutS except it's for small text.
ld A,'A' bcall(_vPutS)
Now comes the fun part. Remember the tutorial on flags? TI has conveniently provided their text ROM calls with checks for many of the text-related system flags. That means you can call the same ROM call and if you modified the system flags it would provide a different output in some manner. Feel free to read through what all the system flags do, but below is a compiled list of the text-specific flags.
|Flag Name||IY offset||Equate description||Explanation|
|textEraseBelow||textFlags||0 = Don't erase line below||Only on small text|
|1 = erase line below|
|textInverse||textFlags||0 = Regular text||Affects both small and large text|
|1 = write in reverse video|
|appTextSave||appFlags||0 = Don't write to textShadow||Places a copy of the character written to the display into the textShadow buffer.|
|1 = Save characters written in textShadow|
|appAutoScroll||appFlags||0 = Don't auto scroll||Scrolls text if on last line of large text|
|1 = auto-scroll text on last line|
|textWrite||sGrFlags||0 = small font writes to display|
|1 = small font writes to buffer|
|bufferOnly||plotFlag3||0 = Draw to display and buffer|
|1 = draw to graph buffer only|
|fracDrawLFont||fontFlags||0 = small font with UserPutMap||Affects small text routines|
|1 = draw large font in UserPutMap|
|customFont||fontFlags||0 = Standard OS fonts|
|1 = draw custom characters|
So now we can tell the user what the calculator is doing. That's part of the way to creating a functional program. Now we need to allow for user input. This can be done through three different ways.
This ROM call waits for the user to press a key, and then stores a key code into the accumulator. The specific keycode equates are shown on this list. Note that when you use _GetKey, the calculator will parse for 2nd and alpha keys, meaning that if you want the 2nd/alpha button to do something, it won't work. So, let's show and example of _GetKey in use.
;display the hex value of the key pressed bcall(_ClrLCDFull) ;clears the screen bcall(_GetKey) ld h,0 ;make hl=a and then display it ld l,a bcall(_DispHL)
Not a very elegant method, _GetKey is usually used if the programmer wants the ROM call to parse 2nd/alpha for them. It's a very simple command and easy to master its uses.
A variation of _GetKey. Instead of waiting for a key to be pressed, GetCSC will check to see if a key was pressed, and if so, store a keycode to the accumulator. If a key was not pressed, the accumulator equals zero. Note that because GetCSC gets user input this way, you can use GetCSC to create your own parser for 2nd/Alpha key inputs. Note that _GetCSC has different keycodes from _GetKey, listed here.
loop: ;waits for a key to be pressed bcall(_GetCSC) or a jr z,loop
Note that although _GetCSC doesn't wait for a keypress before "returning to program control", it will wait a little bit if you hold down an arrow key before parsing it as multiple keypresses.
The most primitive form of input. It directly polls the hardware what key was pressed. However, it is a complicated process and won't be discussed here. See this page.
So now we have the basics of input and output via text and keypresses, it's time to introduce picture data. Picture data is just the turning on of individual pixels in any pattern defined by the code. There are several ways to do this, but they ultimately boil down to the same process performed in different ways. That process is discussed here. In the mean time, there are two other main methods and two not-so-common (because they sucks and are assembly versions of how to create pictures in basic) methods for manipulating individual pixels.
Pixel ROM calls (bad)
This method is not recommended, but there are a few ROM calls here that can sometimes be useful if you're feeling lazy. TI has provided in the standard ROM calls several commands that allow you to turn pixels on and off, draw lines, circles, and various other shapes. However, since there is a huge list of such ROM calls (undocumented and documented), look at your 83psysroutines.pdf (chapter 5: System Routines — Graphing and Drawing) to see their actual functionality.
Text Sprites (not too good)
If you have ever programmed in TI-Basic, you might have used text sprites. Text sprites are a method developed by TI-Basic programs who realized that they can "overlap" text to form almost any combination of pixels on and off. For more information, see this file.
DisplayImage ROM call (okay)
DisplayImage is a ROM call TI has provided to do as it says: display images. It takes 2 inputs, but you must structure your picture data in a specific way.
HL: Points to image data
DE: Where to draw on the screen. (0,0) is the top left corner.
ld DE,$0123 ;draw to coordinates (1,35)
Formatting Picture Data
The picture data should be formatted in such a way that the first two bytes determine how large the picture is (number of pixels down; by number of pixels across)
Note that each row of data is divided into bytes, so each bit determines whether a pixel is turned on or off. Note that DisplayImage does not xor pixels ("flip" them), the data only dictates if it's turned on or off.
;draw an 8*8 square to top left corner ld hl,picData ld de,0 bcall(_DisplayImage) ret picData: .db 8,8 ;an 8*8 box .db %11111111 .db %10000001 .db %10000001 .db %10000001 .db %10000001 .db %11111111
As with most ROM calls dealing with the display, you can dictate the function of DisplayImage with flags.
|plotLoc||plotFlags||0 = write to display and buffer|
|1 = write to display only|
|bufferOnly||plotFlag3||0 = Draw to display and buffer|
|1 = draw to graph buffer only|
Writing to a Buffer (good)
Now, we will introduce the best way to draw pictures. This involves a buffer. A buffer is an array of data. How it's structured and what the data means in the buffer is programmer defined (what the program does with it), but be careful because if you want to allocate (not use for any other purpose) a section of memory for a buffer that's structured 20*20, because z80 doesn't recognize "variables", you can inadvertently use it as a 4*100 buffer. Of course, then, your data would make no sense until you used it as a 20*20 buffer again. Here are some good tips on using buffers:
- Create variables that point to:
- Top of buffer (first byte)
- End of buffer (or define the length)
- Pointer (where you are looking at in the buffer)
- Define the buffer in a safe location of memory
- In a saferam area (appbackupscreen, etc.)
- In the program itself (if you're willing to sacrifice space)
- In a location of memory that the calculator already uses for your purposes (plotsscreen for picture buffer)
Now that you have a buffer, now what? There's nothing displayed on the screen yet! The answer is to copy the buffer to the screen. TI has provided us with the GrBufCpy ROM call. It simply copies plotsscreen to the display.
By the looks of it, just using DisplayImage is smaller and faster. It is true that it is smaller; however, DisplayImage has less use, especially when it comes to sprites. So, to make this way faster, a long time ago a fellow z80 programmer named Joe Wingbermuehle developed a new method for quickly drawing to the display, calling it ionfastcopy. It does exactly the same thing as GrBufCpy, but much faster. To use it, you need to do one of two things: port your program for ion, or copy the code for ionfastcopy into your program. The second method is better.
.define gbuf plotsscreen ;what ion.inc defines the screen buffer as ;code for ionfastcopy directly from ionf.z80 fastCopy: di ld a,$80 ; 7 out ($10),a ; 11 ld hl,gbuf-12-(-(12*64)+1) ; 10 ld a,$20 ; 7 ld c,a ; 4 inc hl ; 6 waste dec hl ; 6 waste fastCopyAgain: ld b,64 ; 7 inc c ; 4 ld de,-(12*64)+1 ; 10 out ($10),a ; 11 add hl,de ; 11 ld de,10 ; 10 fastCopyLoop: add hl,de ; 11 inc hl ; 6 waste inc hl ; 6 waste inc de ; 6 ld a,(hl) ; 7 out ($11),a ; 11 dec de ; 6 djnz fastCopyLoop ; 13/8 ld a,c ; 4 cp $2B+1 ; 7 jr nz,fastCopyAgain ; 10/1 ret ; 10
Don't worry if you don't understand that code. It involves directly drawing to the screen.
1. What is ASCII and how is it used?
2. What is the difference between GetKey and GetCSC?
3. Write a short program to display "HELLO WORLD" diagonally
4. Write a program that moves a dot around the screen when the user presses an arrow key. (hint: Use the _IPoint, or see sprites for a better method)
5. Identify the errors (if any)
;draw an 8*8 square to top left corner ld hl,picData ld de,0 bcall(_DisplayImage) ret picData: .db 1,8 ;an 8*8 square .db %11111111 .db %10000001 .db %10000001 .db %10000001 .db %10000001 .db %11111111
6. Identify the errors, if any.
; draw an 8*8 square ; ported for ion ld de, picdata ld hl,appbackupscreen ld bc, 8 ldir call ionfastcopy ret picData: ;an 8*8 square .db %11111111 .db %10000001 .db %10000001 .db %10000001 .db %10000001 .db %11111111
Check your answers here.
That's it for simple input and output. It's suitable for many applications; however, because we are working in assembly, there are advanced topics such as using hardware ports, sprites, and grey-scale that can potentially enhance your program.