basic print string function in assembly
basic print string function in assembly
are there any glaring things where you're thinking, it would have been easier to do it a different way?
I dont care about the unrolled loops or how fast it executes, just wondered if there is a way of doing things with fewer commands
Re: basic print string function in assembly
why don't use ROM routines for this? you will also have all standard control codes for free — color control, udg, etc.
Code: Select all
org #8000
; open main screen channel
ld a,2
call #1601
ld de,mymsg
call print
di
halt
print:
ld hl,0
push hl
jp #0C22
mymsg: defb "messag", "e"+#80
Re: basic print string function in assembly
or, if you know the length of your message:
Code: Select all
org #8000
ld a,2
call #1601
ld bc,msglen
ld de,mymsg
call #203c
di
halt
mymsg: defb "message"
msglen equ $-mymsg
Re: basic print string function in assembly
Yeah I could've, the examples I saw returned to basic and it wasn't clear how I could set x and y coords.
So I thought it would be better if I could see what's going on with everything
So I thought it would be better if I could see what's going on with everything
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
I used the ROM routine the other week to draw the 2x2 graphics characters stuff. It's bonkers how it does that (and really slow!) - it builds the graphics on demand in a scratch buffer for each character lol. In the end I had to write my own routine since drawing 9 graphics characters was taking nearly an entire frame.
But otherwise using the ROM is a good idea if you are starting out.
If you want to write your own code:
Don't use a loop to multiply something by 8, easiest way to do that is put it in HL and do
ADD HL, HL
ADD HL, HL
ADD HL, HL
You're also using a loop to skip to the correct character to print when you just want to add the current char to DE.
I only have my flash bells and whistles string drawing routine to hand which is quite long, handles some control characters and has a current row, current column variable, and does wrapping from one line to the next and scrolling, so I won't post that
I always use a 48 byte lookup table for screen row addresses.
Here's a bare bones "print an 8x8 graphic aligned to a character cell" routine. All graphics must be aligned to an address divisible by 8, and the row lookup table has to be aligned to a multiple of 256
If you want to unroll something your assembler probably has a DUP or a REPT directive to repeat a block of code, so you should probably use that rather than copy/paste code or typing it multiple times (also easier to modify how you did it later on).
EDIT: And if you know the address beforehand (i.e. it is a constant) you can put an entry point skipping the address calculation (see my edit to code)
You can also unroll the loop if you want
But otherwise using the ROM is a good idea if you are starting out.
If you want to write your own code:
Don't use a loop to multiply something by 8, easiest way to do that is put it in HL and do
ADD HL, HL
ADD HL, HL
ADD HL, HL
You're also using a loop to skip to the correct character to print when you just want to add the current char to DE.
I only have my flash bells and whistles string drawing routine to hand which is quite long, handles some control characters and has a current row, current column variable, and does wrapping from one line to the next and scrolling, so I won't post that
I always use a 48 byte lookup table for screen row addresses.
Here's a bare bones "print an 8x8 graphic aligned to a character cell" routine. All graphics must be aligned to an address divisible by 8, and the row lookup table has to be aligned to a multiple of 256
Code: Select all
; B - row
; C - column
; DE - ptr to sprite data. MUST BE ALIGN 8
draw_sprite8x8a:
ld h, tbl_rows/256 ; high byte of address of the screen rows table page, which is ALIGN 256
; add on twice the row
ld a, b
add b
ld l, a ; THIS REQUIRES tbl_rows TO BE 256 BYTE ALIGNED
ld a, (hl) ; first byte of value in table
inc l
ld h, (hl) ; second byte of table
add c ; add on the column
ld l, a
draw_sprite8x8aKnowAddress:
; loop counter
ld b, 8
.loop
ld a, (de) ; 8 pixels of data from gfx
inc e ; next line of data ; THIS REQUIRES DE WAS A MULTIPLE OF 8 ON ENTRY TO FUNCTION. If it might not be, have to use INC DE here
ld (hl), a ; draw to screen
inc h ; move down a line
djnz .loop
ret
ALIGN 256
tbl_rows dw #4000, #4020, #4040, #4060, #4080, #40A0, #40C0, #40E0
dw #4800, #4820, #4840, #4860, #4880, #48A0, #48C0, #48E0
dw #5000, #5020, #5040, #5060, #5080, #50A0, #50C0, #50E0
EDIT: And if you know the address beforehand (i.e. it is a constant) you can put an entry point skipping the address calculation (see my edit to code)
Code: Select all
LD HL, #4830 ; example of a known address to print something at
call draw_sprite8x8aKnowAddress
Code: Select all
; B - row
; C - column
; DE - ptr to sprite data. MUST BE ALIGN 8
; UNROLLED VERSION
draw_sprite8x8a:
ld h, tbl_rows/256 ; high byte of address of the screen rows table page, which is ALIGN 256
; add on twice the row
ld a, b
add b
ld l, a ; THIS REQUIRES tbl_rows TO BE 256 BYTE ALIGNED
ld a, (hl) ; first byte of value in table
inc l
ld h, (hl) ; second byte of table
add c ; add on the column
ld l, a
draw_sprite8x8aKnowAddress:
; loop counter
;ld b, 8
;.loop
REPT 7
ld a, (de) ; 8 pixels of data from gfx
inc e ; next line of data ; THIS REQUIRES DE WAS A MULTIPLE OF 8 ON ENTRY TO FUNCTION. If it might not be, have to use INC DE here
ld (hl), a ; draw to screen
inc h ; move down a line
ENDR
ld a, (de) ; 8 pixels of data from gfx
inc e ; next line of data ; THIS REQUIRES DE WAS A MULTIPLE OF 8 ON ENTRY TO FUNCTION. If it might not be, have to use INC DE here
ld (hl), a ; draw to screen
; don't need to inc h again on last line, saving a microjiffy
; djnz .loop
ret
Last edited by ParadigmShifter on Fri Sep 29, 2023 8:27 pm, edited 2 times in total.
Re: basic print string function in assembly
or, if you want to bypass ROM, quick-and-dirty code:
Code: Select all
org #8000
ld hl,mymsg
ld de,#0105
ld c,6
call PrintStrZ
di
halt
mymsg: defb "message!", 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; print string; last string byte must be 0
;; in:
;; HL: string address
;; D: y (char)
;; E: x (char)
;; C: attribute
;; out:
;; HL: next string address
;; AF: dead
PrintStrZ:
ld a,(hl)
inc hl
or a
ret z
call PrintChar
inc e
jr PrintStrZ
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; print char
;; in:
;; A: char code
;; D: y (char)
;; E: x (char)
;; C: attribute
;; out:
;; AF: dead
PrintChar:
; filter out unprintables
cp 32
ret c
cp 128
ret nc
; normal print
push hl
push de
push bc
push af
; calculate screen$ address
ld a,d
and #18
or #40
ld h,a
ld a,d
rrca
rrca
rrca
and #E0
or e
ld l,a
; print char
pop af
ex de,hl ; DE is scr$
; for custom font:
;; push de
;; ld de,font_address
;; sub a,32
;; add a,a
;; ld l,a
;; ld h,0
;; add hl,hl
;; add hl,hl
;; add hl,de
;; pop de
; calculate address in ROM font, fast
ld l,a
add hl,hl
ld h,15
add hl,hl
add hl,hl
; done char address calculation
ld b,8
PrintChar_lineloop:
ld a,(hl)
ld (de),a
inc hl
inc d
djnz PrintChar_lineloop
; calc attribute address
ld a,d
dec a
or #87
rra
rra
srl a
ld d,a
; set attr
pop bc
ld a,c
ld (de),a
pop de
pop hl
ret
Re: basic print string function in assembly
as we're printing with ROM, setting coords is as easy as use AT control code.
Code: Select all
mymsg: defb 22, 10, 5, "messag", "e"+#80
or, as a routine to be called before printing:
Code: Select all
org #8000
ld a,2
call #1601
ld b,10 ; y coord
ld c,2 ; x coord
call setxy
ld de,mymsg
call print
ld a,'!'
rst #10
di
halt
print:
ld hl,0
push hl
jp #0C22
setxy:
ld a,22
rst #10
ld a,b
rst #10
ld a,c
jp #10
mymsg: defb "messag", "e"+#80
Re: basic print string function in assembly
ok thanks, previously I only ever saw examples where x and y were hardcoded next to the string
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
If you are interested in the flash version which does a lot of stuff, check out this thread from another forum:
https://worldofspectrum.org/forums/disc ... amework/p1
Kind of abandoned that project after I got Covid
https://worldofspectrum.org/forums/disc ... amework/p1
Kind of abandoned that project after I got Covid
Re: basic print string function in assembly
also, beware that ROM priting routine destroys alternate register set, and it wants IY to point to system variables area (i.e. you'd better don't use IY in your code). it will also crash back in BASIC if you will specify invalid coord, bad control code, or something. and it will show the infamous "scroll?" message if your text will require scrolling.
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
Also note RST #10 does not handle the 2x2 block graphics (and I'm not sure if it handles UDGs either). I think it only handles character codes < 128
Method which does use RST #10, you can use the control characters like AT, INK, etc. though with this method.
Method which does use RST #10, you can use the control characters like AT, INK, etc. though with this method.
Code: Select all
ORG 32768
MAIN
ld a, 2 ; channel #2
call $1601 ; call ROM open channel routine. Channel #2 is the top screen (not the input area). Only need to do this once
ld hl, strhello
call puts
ret
puts:
ld a, (hl)
or a ; set Z flag if A is 0
ret z ; string is null terminated, exit when you see a 0 byte
; probs best to do this as well
push hl
rst 16 ; routine to print a char from A register
; and pop afterwards
pop hl
inc hl ; next char
jr puts
strhello db "Hello World!", 0 ; 0-terminated string
Last edited by ParadigmShifter on Fri Sep 29, 2023 10:21 pm, edited 2 times in total.
Re: basic print string function in assembly
it does. and will happily print BASIC tokens too. ;-)
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
It definitely doesn't Try it if you don't believe me :p I got burned by that the other week (which is when I discovered how bonkers and slow the routine to print the 2x2 graphics stuff is).
The ROM print routine at (not RST #10) does though.
EDIT: I was mistaken about this, oops
Last edited by ParadigmShifter on Fri Sep 29, 2023 9:26 pm, edited 1 time in total.
Re: basic print string function in assembly
Code: Select all
ld a,220
rst #10
Code: Select all
ld a,139
rst #10
Code: Select all
ld a,144
rst #10
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
Hmm ok, I tested again and you are correct, dunno what I was doing wrong the other week then
Printing the graphics characters is very very slow though, see
https://skoolkid.github.io/rom/asm/0B24.html
specifically the bit after
Graphic characters are constructed in an ad hoc manner in the calculator's memory area, i.e. mem-0 and mem-1.
Printing the graphics characters is very very slow though, see
https://skoolkid.github.io/rom/asm/0B24.html
specifically the bit after
Graphic characters are constructed in an ad hoc manner in the calculator's memory area, i.e. mem-0 and mem-1.
Re: basic print string function in assembly
yeah, RST #10 is not a speed demon. but is costs only 1 byte! ;-)
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
Yeah I think if speed is not an issue it's best to use the simplest version which does use RST #10 then.
If speed is an issue there's been several suggestions on how to do stuff in this thread.
General observations:
Don't use IX if you can help it.
Don't multiply by a constant value using a loop.
A lookup table is the fastest way to get screen addresses.
Use OR A instead of CP 0 (smaller and faster)
EDIT: The scroll? thing is annoying and having to use channel 0 instead of channel 2 to print in the bottom 2 lines is a PITA though.
If speed is an issue there's been several suggestions on how to do stuff in this thread.
General observations:
Don't use IX if you can help it.
Don't multiply by a constant value using a loop.
A lookup table is the fastest way to get screen addresses.
Use OR A instead of CP 0 (smaller and faster)
EDIT: The scroll? thing is annoying and having to use channel 0 instead of channel 2 to print in the bottom 2 lines is a PITA though.
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
I suspect RST #10 faffs with the HL register and does not restore it which was probably why I failed to draw the graphics characters with it.
Probs best to add a PUSH HL before RST #10 and POP HL afterwards then if you use my routine which uses RST #10 (RST 16 in the program I posted).
It probably?? works ok with characters < 128? Since I had no problems using those without saving HL on the stack...
EDIT: Edited my RST #10 code anyway with that change. Which is going to slow everything by 21 T-jiffies per character.
If you restore HL before you return to Basic you can probably use EXX instead of PUSH and POP HL. (Not tested though). Jiffies saved if that works: 13T
i.e. this might work. You only need to restore IY (system variables) and H'L' (calculator stack I think) if you use the ROM and return to Basic. You can't use IY either if you use the standard interrupt routine unless you DI before using IY, restore it again after you use it, and EI afterwards
I don't really use the ROM at all I would use it if I wanted to call the SAVE or LOAD routines though, that's about it.
Probs best to add a PUSH HL before RST #10 and POP HL afterwards then if you use my routine which uses RST #10 (RST 16 in the program I posted).
It probably?? works ok with characters < 128? Since I had no problems using those without saving HL on the stack...
EDIT: Edited my RST #10 code anyway with that change. Which is going to slow everything by 21 T-jiffies per character.
If you restore HL before you return to Basic you can probably use EXX instead of PUSH and POP HL. (Not tested though). Jiffies saved if that works: 13T
i.e. this might work. You only need to restore IY (system variables) and H'L' (calculator stack I think) if you use the ROM and return to Basic. You can't use IY either if you use the standard interrupt routine unless you DI before using IY, restore it again after you use it, and EI afterwards
Code: Select all
ORG 32768
RETURN_TO_BASIC EQU 1 ; save H'L' and IY if we return to Basic. Don't need to do this if you never return to Basic.
MAIN
IF RETURN_TO_BASIC
ld (stashed_iy), iy
exx
ld (stashed_hl_alt), hl
ENDIF
ld a, 2 ; channel #2
call $1601 ; call ROM open channel routine. Channel #2 is the top screen (not the input area). Only need to do this once
ld hl, strhello
call puts
IF RETURN_TO_BASIC
returntobasic:
ld iy, (stashed_iy)
ld hl, (stashed_hl_alt)
exx
ret
ENDIF
ret
puts:
ld a, (hl)
or a ; set Z flag if A is 0
ret z ; string is null terminated, exit when you see a 0 byte
; probably works ;)
exx ; push hl
rst 16 ; routine to print a char from A register
exx ; pop hl
inc hl ; next char
jr puts
strhello db "Hello World!", 0 ; 0-terminated string
dw stashed_iy 0
dw stashed_hl_alt 0
Last edited by ParadigmShifter on Fri Sep 29, 2023 10:41 pm, edited 1 time in total.
Re: basic print string function in assembly
actually, ROM printing routine saves the registers by EXX. this is what the RST #10 handler does literally as its first instruction (see #15F2 in ROM). you can prolly go directly to PRINT-OUT (#09F4), then you'll be able to store registers yourself, and avoid trashing the alternate set. (it will be also slightly faster.)ParadigmShifter wrote: ↑Fri Sep 29, 2023 10:20 pm If you restore HL before you return to Basic you can probably use EXX instead of PUSH and POP HL. (Not tested though). Jiffies saved if that works: 13T
p.s.: "The Complete Spectrum ROM Disassembly" book is very handy. it has some minor errors, but nothing really bad. that's what i usually consulting instead of looking into ROM with a debugger myself. ;-)
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
Well I'm not using the ROM So my "this might work" probably won't then I use EXX all the time lol. I believe the standard interrupt does not use EXX (never had a problem with it being enabled when I use EXX everywhere). EDIT: Although my latest code does seem to hang if I slow the processor down to ~42% speed so maybe it does. I'm going to use my own interrupt handler anyway when I merge it with the rest of my code (which uses a custom IM 2 handler).
I use the skoolkit disassembly it's better for searching if I need to see what the ROM is doing. I MIGHT use the ROM if I was using floating point numbers (I'd probably use fixed point though and borrow routines for those from the TI calculator site). Or if I wanted to modify a Basic program in memory for a utility say.
https://skoolkid.github.io/rom/
I use the skoolkit disassembly it's better for searching if I need to see what the ROM is doing. I MIGHT use the ROM if I was using floating point numbers (I'd probably use fixed point though and borrow routines for those from the TI calculator site). Or if I wanted to modify a Basic program in memory for a utility say.
https://skoolkid.github.io/rom/
Last edited by ParadigmShifter on Fri Sep 29, 2023 10:51 pm, edited 3 times in total.
Re: basic print string function in assembly
yeah, it is based on the book, but with fixed book bugs, i believe. but i somehow prefer the old way, it feels… more retro, i guess. ;-)ParadigmShifter wrote: ↑Fri Sep 29, 2023 10:42 pm I use the skoolkit disassembly it's better for searching if I need to see what the ROM is doing.
Last edited by ketmar on Fri Sep 29, 2023 11:06 pm, edited 1 time in total.
- ParadigmShifter
- Manic Miner
- Posts: 671
- Joined: Sat Sep 09, 2023 4:55 am
Re: basic print string function in assembly
I do agree though that if you do use the ROM you should really know exactly what it is doing and what registers you need to preserve before calling stuff (obviously).