So I'm finally beginning to make some of my own programs in z80 asm, and to translate my high-level program ideas into low-level code, but I'm still having some frustrating failures with projects that seem like they should be fairly simple. The following two programs will assemble/compile when I use the brass assembler and binpac8x linker, but they don't do what I want when I run them on a WabbitEmu emulator.

Code:
#include    "ti83plus.inc"
.org    $9D93
.db    t2ByteTok, tAsmCmp
   
;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 box
 .db %11111111
 .db %10000001
 .db %10000001
 .db %10000001
 .db %10000001
 .db %11111111

.end

This first program is pretty much taken straight from a tutorial. It should display an 8x8 box in the upper left corner of the screen. However, though it will assemble/compile as I said, when I try to call it with Asm() on my TI-83 plus emulator, I just get an Invalid error. I don't understand why I'm getting this error, since I've always associated it withing missing ".db tAsmCmp" in the code or something, but that's there, so...

Code:

#include "ti83plus.inc"
.org $9d93
.db t2ByteTok, tAsmCmp

forR:
forC:      
   bcall(_clrLCDfull)

   ld a, (row)   ;set location for text (row, col)
   ld (curRow),a
   ld a, (col)
   ld (curCol),a
   
   ld a, 'A'   ;display 'A'
   bcall(_putC)
   
   ld a,(col)   ;next Col
   inc a
   ld (col),a
   cp 15
   jp nz,forC

   ld a,(row)   ;next Row
   inc a
   ld (row),a
   cp 7
   jp nz,forR

row:
   .db 0
col:
   .db 0

End:
   ret
.end

This second program should display the letter 'A' and make it travel across the screen left to right, top-down. However, when I run it on the emulator, the letter A just keeps flashing left to right over and over again; or, sometimes it moves downward at the same time, but not nearly as quickly as it should. I assume that my error is somehow rooted in trying to make nested for loops out of assembly code, but I don't see what I did wrong. So yeah, any ideas on how to fix either of these programs?
First off, welcome to z80 assembly!

For your first program, i've never even heard of the _DisplayImage bcall. Actually, i have, but with someone else having trouble with it, too. It's almost certainly going to be too slow to use for any games or anything that needs even a little bit of speed. One possible problem might be that these routines often copy your sprite to what's called the graphbuffer, it contains a copy of what you want to draw to the LCD. To copy it from the graphbuffer (aka gbuf) to the LCD, you'll need to either call bcall(_GrBufCpy) or another routine (like ionFastCopy) which will copy the contents of the gbuf to the LCD.

I don't think that that's exactly your issue here, though. I would recommend looking up a different sprite routine and including it in your program. A common one is the one from ion, "ionPutSprite":
http://z80-heaven.wikidot.com/sprite-routines

You will need to copy it from the buffer to the LCD after drawing it though.

For your text routine, something dangerous i see is that after "jp nz,forR" you execute your data. The ret should come before "row:", otherwise you will be executing whatever instruction has the HEX value of your data.

Another problem is that you never reset col back to zero.

You could try something like this:

Code:
#include "ti83plus.inc"
.org $9d93
.db $BB,$6D

forR:
forC:     
   bcall(_ClrLCDFull)

   ld a, (row)   ;set location for text (row, col)
   ld (curRow),a
   ld a, (col)
   ld (curCol),a
   
   ld a, 'A'   ;display 'A'
   bcall(_PutC)

   ld hl,col   ;next Col
   inc (hl)
   ld a,(hl)
   cp 16
   jp nz,forC

   ld (hl),0   ;reset X cursor to 0

   inc hl      ;hl now points to next byte: row
   inc (hl)
   ld a,(hl)   ;next Row
   cp 8
   jp nz,forR
   ret

col:
   .db 0
row:
   .db 0

But really, col and row aren't necessary. It could be optimized even further:

Code:
   ld hl,$0000
   ld (curRow),hl
forLoop:
   bcall(_ClrLCDFull)
   ld a, 'A'   ;display 'A'
   bcall(_PutC)
   ld hl,curRow      ;first comes curRow
   ld a,(hl)      ;load the value of curRow into a
   cp 7            ;if it = 7, we're on the last line
    jr nz,forLoop
   inc hl         ;then the next byte in memory is curCol
   ld a,(hl)
   cp 15         ;quit when we reach column 15 of row 7
    ret z
   jr forLoop
...since _PutC updates curRow and curCol for you!
Ok, this raises some new questions:

1) In assembly, how are the homescreen pixels indexed, 0-15/0-7 or 1-16/1-8?
2) Why did you use hl to increment row/col rather than a? A is the register that gets used in cp anyway, so why not do all your incrementing with it? And how do row/col get incremented if you don't store the incremented values back into their respective addresses? I assume it all has something to do with hl "pointing to the next byte," but I'll need a little further explanation of that step
too haha.
3) What is the best place in a program to put the ret statement then, if not at the end? And what does it mean when you follow ret with a register name?
4) In your second program, why didn't you initialize curCol to 0 (or 1, see Q1)? Also, won't that code make the 'A' loop through all the rows before it ever changes the column, due to the first jr nz,forloop statement?
5) Finally, what is the difference between jp/jr, and when would you use one over the other?

Thanks again for responding so quickly/thoroughly though!
1) They should start from 0, so 0-15 (X) and 0-7 (Y)
2) Here i used hl to increment because it is quicker and smaller. And pay attention to the difference between inc hl (add 1 to hl) and inc (hl) (add 1 to the value stored at the address of hl, so if hl = $8000, the byte stored at $8000 will be incremented by one, hl still = $8000 afterwards).
ld hl,col = 3 bytes, 10 t-states
inc (hl) = 1 byte, 7 t-states
ld a,(hl) = 1 byte, 7 t-states
altogether, 5 bytes and 24 t-states

ld a,(col) = 3 bytes, 13 t-states
inc a = 1 byte, 4 t-states
ld (col),a = 3 bytes, 13 t-states
altogether, 7 bytes and 30 t-states

Working with registers only (without immediate data, ie ld a,e vs ld a,1) is faster and smaller. (hl) can be used everywhere registers b, c, d, e, h, and l can be used, but it's slightly slower (usually 3 t-states).

row and col get incremented because when inc (hl) increments the value stored at the address in hl, not hl itself. It's similar to the difference betwen ld hl,hello (store the address of hello into hl) and ld hl,(hello) (store the value -- 2 bytes -- stored at hello into hl).

3) There are several ways you can use ret. What ret does is take the last two bytes on the stack and load them into the PC (the program counter, it contains the address of the next instruction to run). When your program gets loaded, the value on the stack will return you to the TI-OS (assuming you're running the program from the homescreen). When you "call" a subroutine, it will return you to the next instruction after the call, eg.:

Code:
start:
    ld hl,0    ;hl = 0
    call add10toHL
    dec hl    ;hl = 9
    ret

add10toHL:
    add hl,10    ;hl = 10
    ret
Here, call will put the address of the next instruction (dec hl) on the stack and jump to the address of add10toHL. It will then add 10 to hl, ret(urn) to the dec hl instruction, and return one more time to the TI-OS homescreen. If you aren't using any calls, you will put ret wherever you want to quit your program. Remember, the program will execute one instruction after another unless you tell it to go somewhere else.

4) In the other program, i DO initialize curCol to zero. But to know this you have to realize that curRow and curCol are right next to each other in memory (curRow first, followed by curCol), and you have to know the difference between 8-bit and 16-bit registers. If you load an 8-bit value into curRow (really, the only 8-bit register that can do this is "a"), you will only fill the byte at curRow. However, if you load a 16-bit register (bc, de, or hl) into curRow, you will load the value of the LSB (least significant byte, the small value, here c, e, and l) into curRow, and the MSB (most significant byte: b, d, and h) into curRow+1, which just so happens to be curCol. You load the two bytes of HL into curRow (loading 0 into hl loads 0 into both h and l). If you find this confusing let me know, it's pretty important. Smile

5) jp is an absolute jump. It jumps to an absolute address (for example, $8000, $9D95, $4080, etc.). jr is a relative jump (JumpRelative). It is limited in range: you can jump up to 127 bytes forward or -128 bytes backward. jr is one byte smaller (-128->127 is the range of a signed 8-bit number, so this value fits in one byte) than jp, which takes a 2 byte value and can jump anywhere in RAM. Traditionally, people have used JP where speed is important (a standard jp instruction takes 10 t-states) and JR where size is important (jr takes 12 t-states). However, it's not necessarily always going to be faster, for example a jr takes 7 t-states when you don't jump (for example jr z,aaaa will take seven t-states if the z flag is set to nz).

The last program doesn't do anything to curRow/curCol because _PutC automatically updates them for you Wink

And if you're asking about the ret z, z isn't a register, it's a flag. The flags are set (or reset) based on the results of an operation. For example, the z flag is set when an operation = 0 (cp 10 essentially subtracts ten from a, so if a = 10, 10-10 = 0, the z flag is set. If a = 11, 11-10 = 1, so nz (not zero) is set). Other operations are c (carry), nc (not carry), p (positive), m (negative, or maybe "minus"), pe (parity even), po (parity odd). The ones you will use more often are the z flag (z/nz) and the carry flag (c/nc), p/m can be useful as well but in most cases the carry flag will be enough (especially since jr can't use the sign flags, only z/c). pe/po seem to be used a lot in optimizations, in normal usage i can't think of many situations where they're necessary to use. Flags are another topic in and of themselves, though.

Whew... if you've got any other questions or i didn't explain something very well, let me know!
Omg no you explain things very well, thank you! You have no idea how happy I am to have finally found a source like this Smile

But back to business lol...
1)I think I understand everything about your first program except one thing, when you "inc hl" to go from the col address to the row address, how did you know that these variables held adjacent locations in memory? Is it just because they're defined in that order? I guess my more general question here is: how do you know the exact addresses, or at least relative order, of variable locations in memory?
2) As for your second program, some things still confuse me. You explained that "ret z" refers to the z flag, not a z register, but what does that statement mean? Is it a conditional statement, like "return if z is set"? If so does that mean, you can make ret be contingent upon any flag?
3) So I know that _PutC automatically moves the cursor over after it displays a character; is that why you didn't need to explicitly increment curRow or curCol? Also, if _PutC puts a character in column 16 (the last column), what happens? Does it revert curCol back to 0 and increase curRow? If so, then I think I can already answer this last Q...
4) The flow of ur second program is still pretty new to me so let me see if I can reason it out... Every time u call _putC within the loop, it automatically increases curCol, reverting it to 0 and increasing curRow whenever curCol reaches 15 (see Q3 above). Thus, you check curRow's value during every iteration of the loop until it =7. However, there will still be 15 loop iterations in which curRow = 7, so that's why you also check if curCol = 15 afterward. If it does (i.e., if z is set, see Q2 above) then end the program, otherwise keep looping.

Do I have it all right?
1) Yes. In fact, you define it that way in the program, by having the col: label followed by one byte, then the row: label followed by one byte.
2) It means that the return instruction will only be executed if the zero flag is set. If not, it will be skipped. You can indeed make the ret conditional on other flags, like ret c (return if carry flag is set), ret nz and ret nc (the inverses of ret z and ret c), etc.
3) Yes, and yes.
4) Sounds all correct to me!
I think Kerm answered everything fine, but adding a little to #3:
When _PutC reaches column 16 (remember, columns are from 0-15) it will increase to the next row and reset curCol to 0. If you are already at the last row, i believe it will scroll the text on the screen up one row, resetting curCol to 0 but leaving curRow at the same row. I can't quite remember if that's how it works, but that's how the TI homescreen works so i think it that's how it is.

And for #2, ret supports (as well as call and jp) all the flags, jr however only supports z/nz and c/nc (no p/m or po/pe).
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement