Sorry if I left you hanging at that last tutorial. It was just getting a tad bit long to be one one page. We're now at "Now can I do what I want?" :)
Pay attention - this is where everything that you've read thus far comes together.
Here's a copy of the hello.z80 source:
.nolist #include "ti83plus.inc" .list .org userMem-2 .db t2ByteTok,tasmCmp ld a,$00 ld (curRow),a ld (curCol),a ld hl,hello_world B_CALL(_PutS) ret hello_world: .db "Hello world!",$00 .end
It puts "Hello world!" up at the upper-left corner of the homescreen.
The 'magic command' is PutS. It is a TI-OS-provided function that allows the programmer to print a string to the screen.
Where can you find out how PutS works? "Hmm, I wonder... ohh! It must be in one of those other documents!" Right on! :) Here is where the references come into play. I take a peek at the TI-83 Plus System Routines documentation, and I find this (had to edit a bit because copying from PDF to text is rarely a good thing):
PutS Type: Display Description: Displays a zero (0) terminated string residing in RAM at the current cursor position. This routine uses the large 5x7 font. Inputs: HL = pointer to start of string (curRow) = cursor row position, (0 – 7) (curCol) = cursor column position, (0 – 15) Outputs: Carry = 1 if entire string was displayed Carry = 0 if string did not fit in the display curRow and curCol are updated to the position after the last character displayed. Registers destroyed: HL
The above text was taken from a copyrighted TI document. TI holds all rights and claims, etc. :-/
Aha! So what hello.z80 does is load zero into register a and in turn, it is indirectly loaded into memory addresses curRow and curCol. That effectively sets the cursor to the upper-left hand corner of the homescreen.
Now, it also needs to know the "pointer to start of string," because that is how the PutS routine works. To count the memory bytes for every label would be utterly tedious and error-prone, so what an assembler does to a label in a source file is take it and calculate the address (i.e. a pointer) and sticks that in the program. hello_world
is a pointer that points to the 'H' in the string "Hello World!" It does NOT contain the entire string "Hello World!" That is how memory pointers work. Then, a B_CALL to PutS shows the string (the actual code to display the string is hidden inside the OS; that's why we B_CALL it).
Now what about the outputs of PutS? Let's see... the register hl is destroyed, but we won't use it again, so it doesn't matter much. There is also a carry flag output, but we don't care if it overflowed, because we already knew beforehand that the string would already fit in the display.
You might be wondering about how the label addresses are generated? It all has to do with the 5th (and maybe the 6th) line(s) of the program:
.org userMem-2 .db t2ByteTok,tasmCmp
The .org tells the assembler to start assembling at address userMem-2. Now why is that so? First off, look at what userMem is. Is it a ROM call? Where could you get that sort of information? Answer: ti83plus.inc (say hi! :)). userMem is an equate that stands in place of address $9D95. Why $9D95? Why not $0000 or $F00D or some other 'cooler' address? Because TI decided that all assembly programs will run at userMem. So you tell the assembler to originate all labels from userMem because that is how it works. "But wait! There's a -2 after userMem? What's up with that?" The answer lies on the next line. These are two bytes that tell the TI-OS that this is a squished assembly program, not a BASIC program or whatever. This is how TI thought would be the best way to implement program detection and that is how it works.
Here's another more "down-to-earth" explanation: userMem is where RAM-resident assembly programs are actually loaded, but these two bytes aren't actually copied when the program is moved to userMem (hence why they are "detection bytes"). So when you .org userMem-2 before these two bytes, the actual beginning of the program (which is two bytes later) will be at userMem where it's supposed to be (($9D95-2)+2 = $9D95
), and that is exactly what we want. :)
"Now what about the list stuff around the #include? What's that for?"
First off, I'll say that it is really a matter of preference and what works for you to include and use it. As to what it does, I'll leave it up to you to figure out. "What is it? Where could I get that sort of information?"
Eeep! I must've told you many things that you don't even know about - flags, strings, labels, indirection, B_CALLs, and so on and so forth. "AHH! Brain overload! Press any key to reboot!" Why did I not explain them here and now? Because you must figure out what they are; I'm just teaching you the concepts. You must read, consult, learn, practice, and absorb.
Well, to the uninitiated, the program does all we want it to do. However, you want to grow and learn new things (...right?), so enter the concept of optimization. Basically, you are looking for ways to do the same thing but more efficiently. For your first optimization, I'll teach you the technique of 'register-pair memory loading' (yay! :)).
The optimization is as follows:
ld hl,$0000 ld (curRow),hl
which would replace:
ld a,$00 ld (curRow),a ld (curCol),a
The former is faster and smaller, so naturally, we would use it. :) Now what does it do? Well, it does set curRow and curCol both to zero, but there is a catch. You need to be aware of a thing called 'little-endian' when you use this technique. What is little-endian you ask? Well... *cough* :)
Congratulations! This is all you need to know in order to program TI-83 Plus/TI-84 Plus assembly! That's it! The concepts are laid out on the table. It is now up to you to take what is useful and express who you really are.