The Learn Z80 in 28 Days tutorial starts off with this code:


Code:

.nolist
#include "ti83plus.inc"
#define    ProgStart    $9D95
.list
.org    ProgStart - 2
    .db    t2ByteTok, tAsmCmp
    b_call(_ClrLCDFull)
    ld    hl, 0
    ld    (PenCol), hl
    ld    hl, msg
    b_call(_PutS)            ; Display the text
    b_call(_NewLine)
    ret

msg:
    .db "Hello world!", 0
.end
.end


I've tested it on jsTIfied with a TI-83 ROM and, as expected, it works perfectly. I have absolutely no idea how to make it work for the 84CSE, though (which is probably why I'm reading through the tutorial Razz) beyond replacing #include "ti83plus.inc" with #include "ti84pcse.inc"- and even if I do that, it's still read by the calculator/DCSE as a BASIC program.

I'm completely at a loss for what to do here, and Google isn't helping much. Could somebody who knows how please change the header so that it's correctly read by the CSE as an ASM program and explain why those changes have to be made? Would be much appreciated (:
Sure, of course! There a few differences that you should know of:
1) Most of the romcalls and saferam areas have changed
2) The LCD is completely different. (Technically it still uses the same ports though Razz)

Now, in order to get your code working, you are going to want to change your code to this:

Code:
.nolist            ; Not part of the executable
.binarymode ti8x      ; Brass Directive
#include "ti84pcse.inc"   ; New include file      
.list             ; Begin executable

   .org UserMem-2
   .db tExtTok,tAsm84CCmp

   bcall(_maybe_ClrScrnFull)    ; This is just how it is defined in the equate file for me. :P
   bcall(_HomeUp)       ; To the top of the display
   ld hl,msg         ; Load address of string into hl
   bcall(_PutS)       ; Print the string located at hl
   
   ret             ; Return from program
   
msg:
   .db "Hello World!",0      ; String
.end


Note that the major things that have changed are just the first two lines of you main code, the .org and .db. This tells the calculator that it is a CSE program, and the .org part tells your code where it is being executed from. It's a little bit different than that, but that is the gist of it.

Also, keep in mind that this is a sample program. Don't become too concerned with output and formatting; just keep steadily moving along. It's not too important because you can pretty much do whatever you want in assembly once you advance. Smile

I would also highly recommend you use the Brass assembler, as it supports many different and useful directives, and makes assembling for the CSE a tad easier as well.

One thing to keep in mind with with z80 assembly is that it isn't the funnest language to play around with when you begin. Be prepared for the long haul. Hope this helps, and good luck! Smile
MateoConLechuga wrote:
Sure, of course! There a few differences that you should know of:
1) Most of the romcalls and saferam areas have changed
2) The LCD is completely different. (Technically it still uses the same ports though Razz)

Now, in order to get your code working, you are going to want to change your code to this:

Code:
.nolist            ; Not part of the executable
.binarymode ti8x      ; Brass Directive
#include "ti84pcse.inc"   ; New include file      
.list             ; Begin executable

   .org UserMem-2
   .db tExtTok,tAsm84CCmp

   bcall(_maybe_ClrScrnFull)    ; This is just how it is defined in the equate file for me. :P
   bcall(_HomeUp)       ; To the top of the display
   ld hl,msg         ; Load address of string into hl
   bcall(_PutS)       ; Print the string located at hl
   
   ret             ; Return from program
   
msg:
   .db "Hello World!",0      ; String
.end


Note that the major things that have changed are just the first two lines of you main code, the .org and .db. This tells the calculator that it is a CSE program, and the .org part tells your code where it is being executed from. It's a little bit different than that, but that is the gist of it.

Also, keep in mind that this is a sample program. Don't become too concerned with output and formatting; just keep steadily moving along. It's not too important because you can pretty much do whatever you want in assembly once you advance. Smile

I would also highly recommend you use the Brass assembler, as it supports many different and useful directives, and makes assembling for the CSE a tad easier as well.

One thing to keep in mind with with z80 assembly is that it isn't the funnest language to play around with when you begin. Be prepared for the long haul. Hope this helps, and good luck! Smile


Thank you so much! I've been using the DCSE SDK, so Brass was a given already Razz


So I can have

Code:

.nolist
.binarymode ti8x
#include "ti84pcse.inc"
.list

   .org UserMem-2
   .db tExtTok,tAsm84CCmp

   ; program here

.end

as a template as opposed to the one given in the tutorial?
(also, _maybe_ClrScrnFull compiled with an error; _ClrScrnFull worked fine though)
Yep, that should work just fine. Smile I must have either an older version or maybe just a different one. Razz Time to go update!
Would this be the correct way to multiply a number by four? I can't test it at the moment.


Code:

LD a,3
LD c,a
LD a,2
MultLoop:
    LD b,a
    LD a,c
    add a,a
    LD c,a
    LD a,b
    sub a
    jr nz,MultLoop
ld a,c
The simplest way is just to add a value to itself twice. Assuming you only want to multiply A by 4:
Code:
    add a,a
    add a,a

Bitshifting might be faster, I don't know offhand. It does have the advantage of not requiring the input value be in A (or HL if you're doing it with a 16-bit value).

Code:
    sla a
    sla a



You didn't say what the inputs are assumed to be, so I'm not entirely sure what you're doing, but it can be optimized a lot. Your code appears to be taking C * 2A, so I shuffled things around a bit to do it more efficiently, now computing A * 2B.

Code:
    ld a,3
    ld b,2
MultLoop:
    add a,a
    djnz MultLoop
   



Misc thoughts:

Code:
LD a,3
LD c,a
You can load a literal directly into C.
Code:
    sub a
    jr nz,MultLoop
This will always fall through because you're subtracting A from itself. You probably wanted dec a instead.
Oh. Duh. |:

I was originally trying to multiply by five, but I realized that the loop made that impossible so I changed 'five' to 'four' and submitted the post.

I'd forgotten that djnz decremented B, and I did mean dec. Thanks!
Tari wrote:
Bitshifting might be faster, I don't know offhand. It does have the advantage of not requiring the input value be in A (or HL if you're doing it with a 16-bit value).

Except for the rotates that always use A as their argument (RRA, RRCA, &c.), all of the shift/rotate instructions are two or more bytes, and therefore slower than the 1-byte add instructions, even on the eZ80.
Btw, to multiply by a fixed number you can often use tricks to avoid writing a multiplication routine. For example, if you want to multiply a by five, you can do this:

Code:
    ld b,a
    add a,a ;*2
    add a,a ;*4
    add a,b ;*5

Sometimes you have to be a little creative, but there's generally a fast way to multiply by a fixed number by adding a number to itself or using a multiple of itself.

x2:

Code:
    add a,a


x3:

Code:
    ld b,a
    add a,a
    add a,b


x4:

Code:
    add a,a
    add a,a


x5:

Code:
    ld b,a
    add a,a
    add a,a
    add a,b


x6:

Code:
    ld b,a
    add a,a
    add a,b
    add a,a

etc.
So... this is going to suck. Please ignore my questionable use of registers+the stack |:

ANyway, this is supposed to display a character (currently 'x') in small font at a point in the screen, and move it around depending on what keys are pressed. When run, though, the x stays in place at wherever hl and bc were initialized to, and while key input does work, the arrow keys' jumps don't seem to do anything. I'm out of ideas as to how to fix it... help?


Code:

   bcall(_maybe_ClrScrnFull)
   ld hl, 15
   ld bc, 30
   ld de, 5
   ld a, 'x'
   jp    Display

KeyLoop:
   bcall(_GetCSC)
   cp    skUp ; up arrow
   jp    Z, Up
   cp    skDown ; down arrow
   jp    Z, Down
   cp    skLeft
   jp    Z, Left
   cp    skRight
   jp    Z, Right
   cp    skClear ; clear to terminate
   ret   Z
   jp    KeyLoop    ; If none of the above keys were pressed, get input again

Up:
   sbc hl, de ; de is used for penRow; this should move the pen upwards
   jp     Display

Down:
   add hl, de ; should move the pen down
   jp Display    ; Display new value of B.

Right:
   push hl ; preserve hl
   push bc  ; put bc
   pop hl   ; into hl
   
   add hl, de ; hl represents penCol; this should move the pen right
   
   push hl  ; put hl
   pop bc   ; into bc
   pop hl  ; restore hl
   jp Display

Left:
   push hl ;preserve hl
   push bc
   pop hl
   
   sbc hl, de ; move the pen left
   
   push hl
   pop bc
   pop hl ;restore hl

Display:
   ld    (penCol), hl
   ld    (penRow), bc
   bcall(_VPutMap)
   jp     KeyLoop    ; get another key
Those B_CALL's are destroying the contents of the registers you're using. TI's documentation says that B_CALL(_GetCSC) destroys AF and HL and that B_CALL(_VPutMap) destroys all but BC and HL. So you should push/pop the registers you need to save around each of them.
In addition, please note that you are writing BC to penRow, which is a 1-byte memory location. penCol is 2 bytes. It won't cause too many issues, but bear that in mind. Smile
Of course, can't believe I didn't think of that! It works perfectly now Smile
Noted, mateo-- I'll fix that at some point.

So my code now (moves a cursor around) is:

Code:

bcall(_maybe_ClrScrnFull)
ld hl, 0
ld bc, 0
jp    Display 

KeyLoop:
   push hl
   push bc
   bcall(_GetKey)
   pop bc
   pop hl
   ld de, 12
    cp    kUp       ; If the up arrow key was pressed.
    jp    Z, Up
    cp    kDown     ; If the down arrow key was pressed.
    jp    Z, Down
   ld de, 8
   cp    kLeft
   jp    Z, Left
   cp    kRight
   jp    Z, Right
    cp    kClear    ; If the CLEAR key was pressed.
    ret   Z
    jp    KeyLoop    ; If none of the above keys were pressed, get input again

Up:
    sbc hl, de ; move pen up
    jp     Display

Down:
   add hl, de ; move pen down
    jp Display

Right:
   push hl ; preserve hl
   push bc  ; put bc
   pop hl   ; into hl
   
    add hl, de ; move pen right
   
   push hl  ; put hl
   pop bc   ; into bc
   pop hl  ; restore hl
   jp Display

Left:
   push hl ;preserve hl
   push bc
   pop hl
   
   sbc hl, de ; move the pen left
   
   push hl
   pop bc
   pop hl ;restore hl
   jp Display

Display:
   ld a, $F1 ; checkered cursor thing
   ld    (penCol), bc
   ld    (penRow), hl
   push hl \ push bc
   bcall(_VPutMap)
   ld HL, (OldHLVal)
   ld bc, (OldBCVal)
   ld (penCol), bc
   ld (penRow), hl
   ld a, $06 ; space
   bcall(_VPutMap) ; draw space at old cursor position
   pop bc \ pop hl
   ld (OldHLVal), hl
   ld (OldBCVal), bc
    jp     KeyLoop    ; get another key

OldHLVal:
   .db 0
OldBCVal:
   .db 0


Two questions:
- What would be the best way to check for user input beyond the arrow keys? I'm assuming an array/lut with the ascii codes of the characters I want stored at their key values?
- As you'll notice, the code I have draws a space to erase. If input of other characters is allowed, though, this wouldn't be desirable; instead, I'd want to check the character under the cursor and output it at OldHLVal, OldBCVal when the cursor updates. Should I use an array of the screen with character values at their respective positions, or is there a better way to do it?

Edit: ...am I setting the program up wrong? I'm pretty sure I copied it directly from Mateo at the top of this page (CSE);

Code:
.nolist
#include ti84pcse.inc
.list
   .org UserMem - 2
   .db tExtTok,tAsm84CCmp


...but I decided to look through the memory management page today to see if I should delete any programs/whatever and I found that since yesterday, running this program has corrupted Y1, Y2 and L2, as Y2 and L2 were taking up huge amounts of space, and when I tried deleting Y1 the calc reset. When it turned back on, I tried running the program from DCSE, which promptly froze. Mateo, are you sure this is what the program is supposed to start with?

Edit 2: Nope, never mind; the corruption is caused by one of two things (not sure yet): Either a) when the cursor goes offscreen, or b) if I quit using 2nd-on (which happens along with the cursor going offscreen, since it makes the LCD go into panic mode sometimes).

Edit 3: as mateo said, it's actually probably because I'm storing two bytes (hl) to penRow.
Well, it definitely could be that, but I notice that you are overwriting the data that is outside of your program, which kind of surprises me that it isn't causing a crash.

This line:

Code:

OldHLVal:
   .db 0
OldBCVal:
   .db 0


Should be more like:

Code:

OldHLVal:
   .dw 0
OldBCVal:
   .dw 0


because these are 2-byte values. Hope this helps! Smile
The data after the program is just RAM, it might corrupt other variables in RAM though.

I'd suggest a bit of reorganizing to avoid all those push/pops, for example (note: untested code):

Code:
   bcall(_maybe_ClrScrnFull)
   ld hl, 0
   ld c, 0
   jp    Display 

KeyLoop:
; Get rid of these push/pops
;   push hl
;   push bc
   bcall(_GetKey)
;   pop bc
;   pop hl

   ld de, 0
   ld c,0
; We'll use calls to return back here afterwards, just make sure not to touch 'a' !!
   cp    kUp       ; If the up arrow key was pressed.
    call    Z, Up
   cp    kDown     ; If the down arrow key was pressed.
    call    Z, Down
   cp    kLeft
    call    Z, Left
   cp    kRight
    call    Z, Right
   cp    kClear    ; If the CLEAR key was pressed.
    ret   Z
; Moved display here so that it runs after all keys have been tested
Display:
   ld hl,penRow
   ld a,(hl)         ; a = penRow
   add a,c        ; penRow + offset
   ld (hl),a        ; store the new penRow value back to penRow

   ld hl,(penCol)
   add hl,de
   ld (penCol),hl     ; penCol is two bytes, so we use a 2-byte register

   push hl
   push af
      ld a, $F1 ; checkered cursor thing
      bcall(_VPutMap)
      ld hl,(OldPenCol)
      ld a,(OldPenRow)
      ld (penCol), hl
      ld (penRow), a   ; remember, penRow is 1 byte, not 2!

      ld a, $06 ; space
      bcall(_VPutMap) ; draw space at old cursor position
   pop af \ pop hl
   ld (OldPenCol), hl
   ld (OldPenRow), a
   jp     KeyLoop    ; get another key


Up:
   ld a,-12    ; a is the value we will add to penRow
   ret          ; now we ret instead of jump

Down:
   ld a,12
   ret

Right:
   ld e,8      ; de is the value to add to penCol
   ret

Left:
   ld e,-8
   ret

OldPenCol:
   .dw 0
OldPenRow:    ; penRow is only one byte
   .db 0
If I wanted to initialize an array to all 0s at a certain saferam area, how would I do that? I first thought of using .fill but that inserts data directly into the program, which isn't what I want... although I could use .fill, then ld (ArrayBase), Array then use some sort of SMC to delete the variable from the program. Still, that would be annoyingly complicated and wouldn't work well with DCSE's writeback feature.

I could use a loop (the array is large enough that it wouldn't make any sense to unravel the loop):

Code:

ld (ArrayBase), 0
ld b, ArrayLength
ArrayLoop:
    ld (ArrayBase + B), 0
    djnz ArrayLoop

but is there an even easier way that I'm missing?

Code:
    ld (ArrayBase + B), 0
isn't even a legal instruction.

Easy:

Code:
    ld hl, base
    ld b, len
loop:
    ld (hl), 0
    inc hl
    djnz loop


Clever:

Code:
    ld hl, base
    ld de, base + 1
    ld bc, len - 1
    ld (hl), 0
    ldir


Cleverer: elided because I can't be bothered to work out the details, but you can (ab)use the stack pointer to push a bunch of zeroes.
Oh, so is IX the only way I can use an offset with indirection? Or is the problem that I'm trying to offset by the value of a register? Regardless, thanks for that! And that second way definitely is clever.
M. I. Wright wrote:
Oh, so is IX the only way I can use an offset with indirection? Or is the problem that I'm trying to offset by the value of a register? Regardless, thanks for that! And that second way definitely is clever.
You can use ix and iy for offset indirection (but you shouldn't use iy unless you push/pop it and disable interrupts, because the OS uses iy for flags). You can use just about any 16-bit register pair for indirection without an offset.
Also, if you just want to set memory to 0, just do this:


Code:

ld hl,base
ld bc,len
bcall(_MemClear) ; call _MemClear on the CE


Or to set a block of memory to the value in A:


Code:

ld a,val
ld hl,base
ld bc,len
bcall(_MemSet) ; call _MemSet on the CE


It works exactly the same way as the second solution Tari posted, but I just find it easier on the eyes to read. Of course; don't do this when you need speed though.
  
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