z80:Input and Output

From Learn @ Cemetech
Jump to navigationJump to search

Existing Tutorials

Introduction

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 Characters

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 Strings

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 world 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.

PutMap

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)

vPutS

Again, vPutS is exactly the same as PutS except it's for small text.

   ld A,'A'
   bcall(_vPutS)

Text Flags

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

User Input

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.

GetKey

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.

GetCSC

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.

Direct Input

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.

Picture Data

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.

Inputs: HL: Points to image data

   ld HL,picData


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 %10000001
    .db %10000001
    .db %11111111


Flags

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)

GrBufCpy

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.

   bcall(_GrBufCpy)

ionfastcopy

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.

Review

Topic questions

1. What is ASCII and how is it used? 2. What is the difference between GetKey and GetCSC?

Code Fragments

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)

Programming Errors

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 %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 %10000001
    .db %10000001
    .db %11111111


Check your answers here.

Conclusion

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 grayscale that can potentially enhance your program.