6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Oct 05, 2024 10:28 am

All times are UTC




Post new topic Reply to topic  [ 26 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Variables in Assembler
PostPosted: Tue Dec 03, 2019 2:28 am 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
Hello,

I'm new to assembly, so I'm struggling with some of the basic concepts. I'm not new to programming, just assembly.

Anyway, I'm using ACME right now, and I've written a "Hello World" program that displays "Hello World" on a 20x4 LCD display hooked up to the VIA. To start with, I hard coded the string into the assembly program using

Code:
  *=$8100
.hello !text "Hello World",0,$FF


That put the ASCII codes for "Hello World" followed by a 0 and $FF at address $8100 in the ROM.

I then had some code that copied that to address $0300 in RAM:

Code:
COPY_TEXT:
   LDX #$00
COPY_LOOP:
   LDA $8100,X
   STA $0300,X
   BEQ COPY_DONE
   INX
   JMP COPY_LOOP
COPY_DONE:
   RTS


Finally, I had some code that put the "text" at $0300 out to the VIAs outputs in such a way that it would display it on the LCD.

Code:
OUT_TEXT:
   LDX #$00
OUTLOOP:   
   LDY $0300,X
   BEQ LOOP_DONE
   JSR DATA
   INX
   JMP OUTLOOP
LOOP_DONE:
   RTS


(the code at DATA would put the data on the VIA's outputs so that the LCD new it was getting data instead of a command).

The thought process was that eventually, I could put any text I wanted at $0300, and then call the OUT_TEXT subroutine, and the text would be displayed.

The problem I'm having is that I can't figure out how to have variable strings.

For example, say I want to put, "Some Variable = $12" on the LCD, I figured I could create a string with something like

Code:
.variable=$12
.string !text "Some Variable = ", #.variable,0


Then call some code to copy .string to $0300, then call the OUT_TEXT subroutine, and I'd have my text on my LCD.

But that second line of code hard codes the value into the ROM, and doesn't create anything in the memory that is dynamic.

Is there a way for me to create a dynamic string, or do I have to create a static string for the "Some Variable = " part, and when I need to set it into $0300, copy it out there, then copy the variable's value, then call OUT_TEXT?

I know this is super basic, and I'm sorry for that. I've read through the docs for ACME a few times looking for an answer to this, and haven't found one. I've searched the internet, and haven't found an answer there, either.

Thanks a bunch.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 5:42 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1948
Location: Sacramento, CA, USA
Welcome!

I think that you have already shown competence on many of the important concepts (at least enough to know how to ask the right questions), and that's a good thing. I feel that your next step (toward the answers you seek) might be to enter the realm of pointers, by designing a putstring subroutine which accepts a 16-bit address argument, say in registers A (high) and Y (low). This would provide you with more flexibility, and would be a great introduction to one of the 6502's signature features, the (zp),Y addressing mode. I am tempted to show you an example, but I'll hold off until you've had a chance to digest this.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 12:40 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
rickseiden wrote:
Is there a way for me to create a dynamic string, or do I have to create a static string for the "Some Variable = " part, and when I need to set it into $0300, copy it out there, then copy the variable's value, then call OUT_TEXT?

That's precisely what you need to do. You might define interpolated variables something like this:
Code:
varval:     .text   "thingie = "
            .db     $FE             ; marker for interpolation
                                    ; $FE = print 16-bit decimal value
            .dw     $02F0           ; location of value to interpolate
            .text   ".",$13,$10     ; follow with period, CR, LF
            .db     $00             ; marker for end of string

You then need to write a small interpreter that reads through this and, when it encounters the $FE, knows to read the next two bytes as an address, then read the value from that address, convert it to printable form, and put that in the output buffer. (This is basically how C's printf() or Python's str.format() work, if you think about it.)

(Those probably aren't ACME mnemonics, BTW, but the general idea should be clear. .db is "define byte" and .dw "define word," the latter placing the word into memory in little-endian order on a 6502.)

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 2:25 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
Thanks for replying.

cjs wrote:
(Those probably aren't ACME mnemonics, BTW, but the general idea should be clear.)


The general idea, I think is clear, but the actual implementation in ACME isn't, unfortunately. What compiler are you using there?


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 2:28 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
barrym95838 wrote:
Welcome!

I think that you have already shown competence on many of the important concepts (at least enough to know how to ask the right questions), and that's a good thing. I feel that your next step (toward the answers you seek) might be to enter the realm of pointers, by designing a putstring subroutine which accepts a 16-bit address argument, say in registers A (high) and Y (low). This would provide you with more flexibility, and would be a great introduction to one of the 6502's signature features, the (zp),Y addressing mode. I am tempted to show you an example, but I'll hold off until you've had a chance to digest this.


Thanks for your kind words. I'm familiar with zp,Y addressing, although I've never used it. I'm not sure how I could take two 8 bit values from the a and y registers and combine them into a 16 bit address, though. I'm assuming that I would use the 16 bit address to point to the location of my string that I want to output. But how would I actually construct the string itself in memory? Do I have to do it byte by byte using ASCII codes, or can I just do something like "This is my string" and then copy that into my memory location?

Rick


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 4:21 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
rickseiden wrote:
cjs wrote:
(Those probably aren't ACME mnemonics, BTW, but the general idea should be clear.)

The general idea, I think is clear, but the actual implementation in ACME isn't, unfortunately. What compiler are you using there?

I'm using the "I just made it up" assembler. :-) But the pseudo-ops are more or less along the lines of what many assemblers for many platforms use. (ACME, for whatever reason, is rather different.) It might assemble like this:

Code:
8000                   .org    $8000
8000 74 68 69  varval: .text   "thingie = "
8003 6e 67 69
8006 65 20 3d
8009 20
800A FE                .db     $FE             ; marker for interpolation
                                               ; $FE = print 16-bit decimal value
800B F0 20             .dw     $02F0           ; location of value to interpolate
800D 2E 13 10          .text   ".",$13,$10     ; follow with period, CR, LF
8010 00                .db     $00             ; marker for end of string

Hopefully looking at the ouput bytes at the left will make it clear exactly what these pseudo-ops deposit in memory. That's all there is to it; it's up to you to write code that reads those data, interprets them, and takes the desired actions based on them.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 5:45 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
cjs wrote:
I'm using the "I just made it up" assembler. :-)

I LOVE that assembler!

cjs wrote:
But the pseudo-ops are more or less along the lines of what many assemblers for many platforms use. (ACME, for whatever reason, is rather different.) It might assemble like this:

Code:
8000                   .org    $8000
8000 74 68 69  varval: .text   "thingie = "
8003 6e 67 69
8006 65 20 3d
8009 20
800A FE                .db     $FE             ; marker for interpolation
                                               ; $FE = print 16-bit decimal value
800B F0 20             .dw     $02F0           ; location of value to interpolate
800D 2E 13 10          .text   ".",$13,$10     ; follow with period, CR, LF
8010 00                .db     $00             ; marker for end of string

Hopefully looking at the ouput bytes at the left will make it clear exactly what these pseudo-ops deposit in memory. That's all there is to it; it's up to you to write code that reads those data, interprets them, and takes the desired actions based on them.


I wouldn't say that the code on the left makes it clear. But, I know where to go to see what those codes mean, and then, combined with the assembly you put there, I think it will be clear in a short amount of time.

I appreciate you spending a little extra time to help me out!

Thanks
Rick


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 5:56 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
I was expecting the hex codes at the address to be OP codes, but it looks like you're just storing bytes of information (or words in the case of .dw) in the ROM directly--hard coding the string if you will.

I was hoping for a way to assign a string. Like, I can store the value $AB in address $0345 in RAM by doing

Code:
  LDA #$AB
  STA $0345


I'm looking for a way to store a string that is created at run time in a given address the same way. I know I can't store a string in the accumulator, of course. So I'd have to load the string byte by byte into the accumulator and then store it into the address using indexed addressing.

Maybe I'm just too used to higher level languages and this lower level stuff isn't fitting in with what I'm used to.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 7:04 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
rickseiden wrote:
I was expecting the hex codes at the address to be OP codes, but it looks like you're just storing bytes of information (or words in the case of .dw) in the ROM directly--hard coding the string if you will.

Right. That's exactly what's happening. Just as when, in C, I do this:
Code:
static char *message = "Hello, world.";

In both cases the assembler or compiler assigns an address for the data, deposits the bytes at that address, and then binds a name—usable only at compile time—to that address. When code referencing that name is assembled or compiled, the name is replaced by the address.

Quote:
I was hoping for a way to assign a string. Like, I can store the value $AB in address $0345 in RAM by doing
Code:
  LDA #$AB
  STA $0345

Well, that's what happens at runtime. But when the assembler sees those, it simply generates the opcodes and their operands, those get dumped into a file, and when the data from that file get loaded into the memory of the microcomputer and the code is run, the store actually happens. Note that the $AB data is already stored in the machine code: if you look at the listing you'll see that LDA #$AB generated an immediate load opcode followed by that datum: A9 AB. All you're doing is loading that $AB from the location where it was assembled and then storing it in a different location.

Quote:
I'm looking for a way to store a string that is created at run time in a given address the same way.

So you need to write the assembly code that, when run, will do that.

Quote:
I know I can't store a string in the accumulator, of course. So I'd have to load the string byte by byte into the accumulator and then store it into the address using indexed addressing.

Yup, that's exactly what you do. Remember, everything you do is eventually just moving bytes around (or modifying them), and a string is just a sequence of bytes. This is true in any language, though higher-level languages usually provide a large library of functions to do this for you.

Quote:
Maybe I'm just too used to higher level languages and this lower level stuff isn't fitting in with what I'm used to.

Perhaps you could tell us what higher-level languages you use, and how you would write this in those, and that will help us provide explanations that draw on your experience with those languages.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 7:24 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
cjs wrote:

Quote:
I was hoping for a way to assign a string. Like, I can store the value $AB in address $0345 in RAM by doing
Code:
  LDA #$AB
  STA $0345

Well, that's what happens at runtime. But when the assembler sees those, it simply generates the opcodes and their operands, those get dumped into a file, and when the data from that file get loaded into the memory of the microcomputer and the code is run, the store actually happens. Note that the $AB data is already stored in the machine code: if you look at the listing you'll see that LDA #$AB generated an immediate load opcode followed by that datum: A9 AB. All you're doing is loading that $AB from the location where it was assembled and then storing it in a different location.
[/qoute]

That was a bad example on my part. I'm storing a static value in my example, and I don't want to do that with the string. A better example would be reading in the value of a register on a VIA an storing that into memory. The value you get from the VIA isn't known when the program is compiled/assembled, but you need to act on it somehow. In something like JavaScript you would store it in a variable. In assembly, all I know how to do is store it in a static location in RAM.

cjs wrote:
Perhaps you could tell us what higher-level languages you use, and how you would write this in those, and that will help us provide explanations that draw on your experience with those languages.


OK. Languages I've worked in (note, I'm not professional programmer by any stretch):
  • BASIC (of course)
  • C/C variations (For Arduino and not for Arduino)
  • Java
  • JavaScript

And a few others I haven't played with in years, like

  • Visual Basic
  • Delphi (Pascal)

What I'm trying to do, in a nutshell, is get the value from one of the registers on the VIA, and make a string out of it, and display it on an LCD screen.

So, say that the VIA's PA register is set to input, and when I query it, I get a value of $1F. I want to put something on the LCD like:

"VIA PA=$1F"

But a little more complicated than that. I would actually want:

"ROW 1 COLUMN F"

on the LCD.

I tested this out last night, and I can create a static string in memory for "ROW " and another for "COLUMN ", then use a loop to copy them to memory and insert the values I want shown. Like

Code:
.row !text "ROW " ; define static string in ROM
.col !text "COLUMN " ; define static string in ROM

  ldx #$00
COPY
  lda .row,x
  jsr draw_a ; puts that character on the screen
  inx
  bne COPY



and so on. That works, and I understand it. But I'm used to being able to say something like

Code:
  var outString="ROW " + value1 + "COLUMN " +value2;
  print outString;


I'm looking for a way to do that first line. Build a string that isn't static, but not have to spell it out character by character using ASCII codes.

Again, thank you for spending your time on this!


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 7:35 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
You're going to have to spell it out character by character with ASCII codes; that's how machine-language programming works. But it's not that hard; you've already figured out how to print out bytes from your strings in memory. You just have to pause in the middle and switch to printing out bytes from other sources. Subroutines, using JSR and RTS, can help with this.

Remember, too, that when you read $1F, to print that in human readable form you're going to need to convert it into two characters. The ASCII character code $1F is a control character, and will print nothing or something unreadable on the terminal; you need to turn that into the two ASCII characters $31 ('1') and $46 ('F') and print each one individually. You may have a bit of work ahead of you to learn how to do that, but there are plenty of examples around of that sort of thing.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 7:39 pm 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
cjs wrote:
You're going to have to spell it out character by character with ASCII codes; that's how machine-language programming works. But it's not that hard; you've already figured out how to print out bytes from your strings in memory. You just have to pause in the middle and switch to printing out bytes from other sources. Subroutines, using JSR and RTS, can help with this.


Well, that's not fun!

cjs wrote:
Remember, too, that when you read $1F, to print that in human readable form you're going to need to convert it into two characters. The ASCII character code $1F is a control character, and will print nothing or something unreadable on the terminal; you need to turn that into the two ASCII characters $31 ('1') and $46 ('F') and print each one individually. You may have a bit of work ahead of you to learn how to do that, but there are plenty of examples around of that sort of thing.


Yea. I'm not exactly looking forward to that part, to be honest. I know that for a single digit, you add #$30 to it, but that doesn't work beyond 0-9, so it won't be easy. Maybe, if I'm lucky, Google will be my friend there, and I won't have to rewrite that from scratch.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 7:59 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8521
Location: Southern California
In assembly language, you get control over every teensy detail. That's not to say it has to be unreadable or that the level of the language has to remain super low. After you get your feet wet, you can start venturing into macros, and do things like
Code:
       DISPLAY_IMM    "Press CONTINUE when ready"
       WAIT_FOR_KEY   CONT_KEY

or go further and mix variable data in with the constant portions, or even have fields where a cursor can be moved around and the user can edit those portions. You have full control of what your macros assemble; but once they're in place, you don't have to keep looking at the internal details every time, and the line that invokes the macro can be rather English-like. IOW, there's no need to despair over assembly language.

_________________
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: Tue Dec 03, 2019 9:13 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10947
Location: England
Many C compilers can also output the machine code they generate: it might help to compile a few fragments and study the code, if there's a simple CPU you're familiar with and a compiler for it. Matt Godbolt has an online demo of such a thing, with many compilers and many targets, at godbolt.org


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 03, 2019 9:23 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8411
Location: Midwestern USA
rickseiden wrote:
I'm familiar with zp,Y addressing, although I've never used it. I'm not sure how I could take two 8 bit values from the a and y registers and combine them into a 16 bit address, though.

Code:
         ldx #<stgaddr         ;string address LSB
         ldy #>stgaddr         ;string address MSB
         stx zpptr             ;ZP pointer LSB
         sty zpptr+1           ;ZP pointer MSB
         ldy #0                ;starting index
;
L0000010 lda (zpptr),y         ;get from string
         beq DONE              ;end of string
;
         jsr putchr            ;write to output
         iny
         bne L0000010          ;do next char
;
DONE     RTS                   ;exit

The above uses MOS Technology/WDC syntax (the assembly language standard for the 6502 family) and assumes the string is terminated with a null ($00). Maximum string length is 255 bytes and two locations on zero page are required for the string pointer. Also, it is assumed your "character out" subroutine preserves at least the .Y register.

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


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

All times are UTC


Who is online

Users browsing this forum: Google [Bot], Kannagi and 23 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: