basic print string function in assembly

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

basic print string function in assembly

Post by Wall_Axe »




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
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

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
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

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
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: basic print string function in assembly

Post by Wall_Axe »

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
:?
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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

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

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)

Code: Select all

    LD HL, #4830 ; example of a known address to print something at
    call draw_sprite8x8aKnowAddress
You can also unroll the loop if you want

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.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

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
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

Wall_Axe wrote: Fri Sep 29, 2023 8:09 pm Yeah I could've, the examples I saw returned to basic and it wasn't clear how I could set x and y coords.
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
22 is AT, then Y coord, then X coord.

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
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: basic print string function in assembly

Post by Wall_Axe »

ok thanks, previously I only ever saw examples where x and y were hardcoded next to the string
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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 ;)
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

Wall_Axe wrote: Fri Sep 29, 2023 8:37 pm ok thanks, previously I only ever saw examples where x and y were hardcoded next to the string
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.
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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.

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.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

ParadigmShifter wrote: Fri Sep 29, 2023 8:49 pm (and I'm not sure if it handles UDGs either).
it does. and will happily print BASIC tokens too. ;-)
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

ketmar wrote: Fri Sep 29, 2023 8:50 pm it does. and will happily print BASIC tokens too. ;-)
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.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

Code: Select all

  ld    a,220
  rst   #10
prints "BRIGHT".

Code: Select all

  ld    a,139
  rst   #10
prints one of 2x2 gfx chars.

Code: Select all

  ld    a,144
  rst   #10
prints first UDG char.
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

yeah, RST #10 is not a speed demon. but is costs only 1 byte! ;-)
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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.
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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

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
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.
Last edited by ParadigmShifter on Fri Sep 29, 2023 10:41 pm, edited 1 time in total.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

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

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. ;-)
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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/
Last edited by ParadigmShifter on Fri Sep 29, 2023 10:51 pm, edited 3 times in total.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: basic print string function in assembly

Post by ketmar »

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.
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. ;-)
Last edited by ketmar on Fri Sep 29, 2023 11:06 pm, edited 1 time in total.
User avatar
ParadigmShifter
Manic Miner
Posts: 671
Joined: Sat Sep 09, 2023 4:55 am

Re: basic print string function in assembly

Post by ParadigmShifter »

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).
Post Reply