6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Apr 27, 2024 7:55 am

All times are UTC




Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: storing text for output
PostPosted: Wed Aug 24, 2022 6:26 am 
Offline
User avatar

Joined: Wed Jul 27, 2022 6:14 am
Posts: 54
My SBC is working and now I come to the second interesting part, the programming.
I have connected an LCD and can output texts there. Welcome message, status message...
But what is the best way to store this texts in ROM?
My first idea was to simply place texts where they are needed
Code:
message_w6502sbc: .asciiz "W6502SBC Welcome"

first variant: subroutine
Code:
do_strout:
   stx TEMP_VEC
   sta TEMP_VEC+1
   ldy #0
strprint:
   lda (TEMP_VEC),y
   beq strreturn
   jsr do_chrout
   iny
   jmp strprint
strreturn:
rts

call with
Code:
lda #>message_w6502sbc
ldx #<message_w6502sbc
jsr do_strout


This can even be shortend with a macro, like
Code:
.macro msg_out(msg)
   lda #>msg
   ldx #<msg
   jsr do_strout
.endmacro

called with
Code:
msg_out(message_w6502sbc)


second variant
Text in a block with an address array.
Code:
.org $FD00
messages:
msg_w6502sbc.equ 0
msg_ready.equ 3
.word message_w6502sbc
.word message_ready
message_w6502sbc: .asciiz "W6502SBC Welcome"
message_ready: .asciiz "W6502SBC ready:"

Access via the index and a TempVector in the ZP

Code:
do_msgout:
asl ; *2
tay ; into the y register
lda messages,y ; load lo address of msg
sta TEMP_VEC ; into the lo st vector
iny ; increase y
lda messages,y ; load hi address of msg
sta TEMP_VEC+1 ; into the hi str vector
ldy #0 ; y = 0
jmp strprint ; output string, second part of do_strout

call with
Code:
lda #msg_w6502sbc
jsr do_msgout


This is a slower variant, but the calls are very short. You even don't need a macro for it.

Is variant 2 worth it? It saves one instruction when called, but costs 6 instructions more when executed.
I haven't considered backing up and restoring the registers yet.
I tend to prefer the first solution.
What do you mean?
How do you do this?
Is there perhaps a better way?

_________________
don't count on me, i'm engineer (Animotion)
my arduino pages: http://rcarduino.de


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 7:18 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10793
Location: England
There's more than one way to do it!

I think the version I'm most familiar with is a terminated string inline, with a subroutine call ahead of it which will use the stacked return address as the string pointer.

Much depends on whether you're saving time, space, or effort.


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 8:00 am 
Offline
User avatar

Joined: Wed Jul 27, 2022 6:14 am
Posts: 54
BigEd wrote:
There's more than one way to do it!

I think the version I'm most familiar with is a terminated string inline, with a subroutine call ahead of it which will use the stacked return address as the string pointer.

Much depends on whether you're saving time, space, or effort.

Ok, another way to do it.
You take the return address from stack, adding 2 (or starting index register with 2) to output the string, than pushing the address back to stack and do a rts !?!

_________________
don't count on me, i'm engineer (Animotion)
my arduino pages: http://rcarduino.de


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 8:12 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
BigEd wrote:
Much depends on whether you're saving time, space, or effort
and whether you're using the string in more than one place. If it's only in one place, one of my favorites is to have the macro assemble the JSR followed by the string right there, with no preceding loading of an address. (Even if you do use it in more than one place, I suppose you could give the data a label to use elsewhere with the method that loads the data into registers before using a different subroutine.)

The following is from the "Inlining subroutine data" page of my 6502 treatise on stacks (stacks plural, not just the page-1 hardware stack):

    http://6502.org/source/io/primm.htm on 6502.org shows three ways to do a print-immediate subroutine with an example usage of:

    Code:
            JSR   PRIMM
            BYTE  "This will be printed!", $00


    You could go further and make a macro that puts it all in one line, defined as:

    Code:
    PRINT:  MACRO  STR
            JSR    PRIMM
            BYTE   STR, 0
            ENDM
     ;------------------


    and then use this way in an assembly-language program:

    Code:
            PRINT  "This will be printed!"


_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 12:37 pm 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 565
I prefer the text output function to be a pointer to a null terminated string for two reasons.

Some strings are generated programmatically, so they can't be stored within the code. So you pass a pointer to a text buffer instead. This is especially true for messages that echo back erroneous input.

If your program is ever localized to another language it's easier if all the text is in a single messages file. Otherwise it's likely some block of text will be overlooked.

Note that you can still pass the parameter inline, but it's a pointer rather than the text itself.


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 12:48 pm 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 690
Location: North Tejas
willie68 wrote:
BigEd wrote:
There's more than one way to do it!

I think the version I'm most familiar with is a terminated string inline, with a subroutine call ahead of it which will use the stacked return address as the string pointer.

Much depends on whether you're saving time, space, or effort.

Ok, another way to do it.
You take the return address from stack, adding 2 (or starting index register with 2) to output the string, than pushing the address back to stack and do a rts !?!

Where did you get the two?

The obvious wrong answer is zero, but because the 6502 pushes one less than the address of the "next instruction" onto the stack, you need to add one. The RTS instruction adds one to the popped value.


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 2:47 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 387
Location: Minnesota
If you're not too concerned about time, you could store all your messages in a single block, one after another. You'd need some way to indicate where each message ended, such as a zero byte following each or (if sticking to ASCII) setting the high bit of the last character.

To use the message block you'd set the X-register to the number of the message you wanted to output. Search from the start of the block, counting down each time you reached the end of a message (X- instead of Y- so you can use (indirect),Y to scan the block, incrementing if necessary beyond 256 characters in the block). When X hits zero, you've reached the message you want to output (with (indirect),Y conveniently pointing right at it).


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 6:48 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
teamtempest's idea (of high bit set on last character) is one I was forgetting about, probably because I often use the IBM437 character set for the specials, to get things like ±½°C.

The two other common ways are:

  1. a null-terminated string, which puts a 00 byte after the string to signal the end, for example 48 45 4C 4C 4F 00 for "HELLO"
  2. a counted string, which uses a leading count byte, for example 05 48 45 4C 4C 4F for "HELLO"

Method B is somewhat less common than method A, but still common. Each has its advantages. I've used both, in different projects.

The original question was about putting the strings in ROM; so they're not going to be changing length.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 6:54 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10793
Location: England
For extra fun, because counting down can be a tiny bit more efficient, the strings can be stored backwards!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 9:45 pm 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1373
I've been the same basic string out routine for decades... and as usual (for me) I use CMOS instructions and addressing modes constantly.

My simple routine uses a message number in the A reg. It might not be as efficient as some routines, but I do use some of these messages from multiple routines in my Monitor.

The messages are all null terminated and there's an index table for the messages which contains the memory address of each starting message.

Code:
;PROMPT routine: Send indexed text string to terminal. Index is contained in A Reg.
; String buffer address is stored in variable PROMPTL/PROMPTH.
PROMPT          ASL     A               ;Multiply by two for msg table index
                TAX                     ;Xfer to X Reg - index
                LDA     MSG_TABLE,X     ;Get low byte address
                LDY     MSG_TABLE+1,X   ;Get high byte address
;
;PROMPTR routine: takes message address in Y/A and prints via PROMPT2 routine
PROMPTR         STA     PROMPTL         ;Store low byte
                STY     PROMPTH         ;Store high byte
;
;PROMPT2 routine: prints message at address (PROMPTL) till null character found
PROMPT2         LDA     (PROMPTL)       ;Get string data (5)
                BEQ     HINEXIT         ;If null character, exit (borrowed RTS) (2/3)
                JSR     B_CHROUT        ;Send character to terminal (6)
                INC     PROMPTL         ;Increment low byte index (5)
                BNE     PROMPT2         ;Loop back for next character (2/3)
                INC     PROMPTH         ;Increment high byte index (5)
                BRA     PROMPT2         ;Loop back and continue printing (3)
;
HINEXIT         RTS                     ;And return to caller


Of course, this does limit you to 128 message numbers, but the messages can exceed 256 characters.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 24, 2022 10:04 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8144
Location: Midwestern USA
floobydust wrote:
I've been the same basic string out routine for decades...

My "print string" sub in the POC firmware is similar, except I don’t use a lookup table and the string address is pushed to the stack before the call. Within the sub, direct page is pointed to the stack, which means bank-agnostic, long addressing can be used to access the string. The actual call is through the BIOS API, which is responsible for setting the environment, as well as cleaning up the stack before returning to the caller.

Code:
04670  ;scprint: PRINT NULL-TERMINATED CHARACTER STRING ON CONSOLE
04671  ;
04672  ;   ———————————————————————————————————————————————————————————————————————
04673  ;   Synopsis: This service prints a null-terminate character string on the
04674  ;             console.  The maximum permissible string length is 32,767
04675  ;             bytes, not including the terminator.
04676  ;
04677  ;             This service will block if the console channel's transmitter
04678  ;             queue (TxQ) is full.
04679  ;   ———————————————————————————————————————————————————————————————————————
04680  ;   Invocation example: rep #%00010000         ;16-bit index registers
04681  ;                       ldx !#string & $ffff   ;string address LSW
04682  ;                       ldy !#string >> 16     ;string address MSW
04683  ;                       cop #kscprint
04684  ;                       bcs error
04685  ;
04686  ;   Exit registers: .A: entry value or error code¹
04687  ;                   .B: entry value
04688  ;                   .X: entry value
04689  ;                   .Y: entry value
04690  ;                   DB: entry value
04691  ;                   DP: entry value
04692  ;                   PB: entry value
04693  ;                   SR: NVmxDIZC
04694  ;                       ||||||||
04695  ;                       |||||||+———> 0: okay
04696  ;                       |||||||      1: error¹
04697  ;                       +++++++————> entry value
04698  ;
04699  ;   Notes: 1) Error codes returned:
04700  ;
04701  ;             E_SIOCH:  subsystem not ready.
04702  ;             E_STRLEN: string is too long.
04703  ;   ———————————————————————————————————————————————————————————————————————
04704  ;
04705  00D614  3B            scprint  tsc                   ;make API stack frame...
04706  00D615  5B                     tcd                   ;local direct page
04707  00D616  A0 00                  ldy #0
04708  00D618  C2 10                  rep #m_setx
04709  ;
04710  00D61A  B7 08         .main    lda [api_ptr],y       ;get from string
04711  00D61C  F0 0B                  beq .done             ;end
04712  ;
04713  00D61E  02 13                  cop #kscput           ;write to console
04714  00D620  B0 06                  bcs .err010
04715  ;
04716  00D622  C8                     iny
04717  00D623  10 F5                  bpl .main             ;get next
04718  ;
04719  00D625  A9 33                  lda #e_strlen         ;string too long
04720  ;
04721  00D627  38            .err     sec
04722  ;
04723  00D628  60            .err010  rts
04724  ;
04725  00D629  18            .done    clc                   ;no
04726  00D62A  60                     rts

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Aug 25, 2022 12:14 am 
Offline
User avatar

Joined: Fri Dec 12, 2008 10:40 pm
Posts: 1000
Location: Canada
GARTHWILSON wrote:
The original question was about putting the strings in ROM; so they're not going to be changing length.[/color]

True, but if you want to use the same code to print the string it will either have to know how long the string is or where it ends.

This has been my solution for limited messages. I use $FF as a terminator here, but you could use $00 too,

Usage:
SNDCHR is whatever routine you use to send a character to wherever it has to go,
All messages need to be in a single page - maximum of 256 characters including terminator characters. There is a simple way around this.
Offset of required message is put in X before calling the routine.

This example prints the "Boot (C/W)?" prompt.

Code:
PRMPT    ldx #$27                   ; Offset of prompt in message block
         jsr SNDMSG                 ; Go print the prompt


;
; Print a message.
; This sub expects the message offset from MBLK in X.
;
SNDMSG   lda MBLK,X                 ; Get a character from the message block
         cmp #$FF                   ; Look for end of message marker
         beq EXSM                   ; Finish up if it is
         jsr SNDCHR                 ; Otherwise send the character
         inx                        ; Increment the pointer
         jmp SNDMSG                 ; Go get next character
EXSM     rts 

;
; The message block. It terminates each message with a $FF.
;
MBLK     dc  C'OMEGA MICRO SYSTEMS'
         dc  H'0D0A0A'
         dc  C'OMS-02 TBC v0.1.2'
         dc  H'0D0A0A'
         dc  C'Boot (C/W)? '
         dc  H'07FF'

   

_________________
Bill


Top
 Profile  
Reply with quote  
PostPosted: Thu Aug 25, 2022 1:10 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
BillO wrote:
GARTHWILSON wrote:
Martin_H wrote:
Some strings are generated programmatically, so they can't be stored within the code.

The original question was about putting the strings in ROM; so they're not going to be changing length.

True, but if you want to use the same code to print the string it will either have to know how long the string is or where it ends.

Again true; but that's needed in all cases anyway, including if the data immediately follows the JSR in the code and the subroutine needs to know when it gets to the last byte, not just to stop, but also to adjust the return address on the stack correctly.

Several ideas are presented here. Teamtempest's was another one I hadn't thought of. To make it a little faster for longer strings, you could have it be a list of counted strings instead of null-terminated ones, so each string's length byte would lead you to the next string, without having to examine every character for a termination byte. Either way, the list is not limited to one page.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Aug 25, 2022 4:21 am 
Offline
User avatar

Joined: Fri Dec 12, 2008 10:40 pm
Posts: 1000
Location: Canada
GARTHWILSON wrote:
.. so each string's length byte would lead you to the next string, without having to examine every character for a termination byte. Either way, the list is not limited to one page.

"Sokath, his eyes uncovered"

Thanks Garth. Makes sense.

_________________
Bill


Top
 Profile  
Reply with quote  
PostPosted: Thu Aug 25, 2022 7:39 am 
Offline
User avatar

Joined: Wed Jul 27, 2022 6:14 am
Posts: 54
As I'm coming from pascal, length byte and than the string is something i know.
At the moment i'll use null terminated strings in one place, because RetroAssembler will do this automatically when using .asciiz Directive.

Having all messages in an area, with a index array was the second option i started with.

As i like the C64 i always have a look into the dissassbeld commodore kernal. (Yes they call it kernal...)
C64 Kernal ROM (BASIC Part) has nearly the same method. They mark the end of a string with the 8'th bit set. (as teamtempest also noted)
First all messages are at one block, $A19E, and than there is a address table for all te messages, $A328
In x register there is the message number, to show up they call $A437.

https://www.pagetable.com/c64ref/c64disasm/

"Many roads lead to Rome"...

_________________
don't count on me, i'm engineer (Animotion)
my arduino pages: http://rcarduino.de


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 26 guests


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 post attachments in this forum

Search for:
Jump to: