String and Array Addressing
Introduction
A common situation in embedded systems involves moving a pointer along a string or array to access a particular location. You might do this, for instance, to send string data to a serial communications data-out register. The example below illustrates the conceptual features in string/array addressing as implemented on the Freescale hcs12 microcontroller chip.
Imagine that we have the following situation: we have a string of characters 'hello' stored in memory beginning at address $2000, and we want to send this data out on the serial communications SCI1 port. To do this, we need to send each character of the string to the serial communications SCI "SCI1DRL" data register which is located at address $00D7. We want some simple, efficient code to move the characters from the string to the SCI1DRL data-out register.
We will use a pointer called "str_ptr" to point to the various characters of the string. We start by pointing "str_ptr" to the first character of the string, and we then send this character to the serial port. We then point "str_ptr" to the second character of the string, and send this to the serial port, etc. The essential code needed to do this is shown below:
;set up memory
ORG $2000 ; beginning of our memory is at addresss $2000
string dc.b "hello" ; our string of characters
str_ptr ds.w 1 ; our pointer to the string of characters
; str_ptr is a 16 bit address, so we need 2 bytes = 1 word of storage space
; our code (only essential parts are shown)
...
...
movw #string, str_ptr ; point to beginning of string (moving a WORD = 2 bytes)
; since we use the IMMEDIATE addressing mode, we must lead with a '#' symbol
...
...
loop:
jsr tx_char ; jump to our serial comm. transmit subroutine to output current character
inc str_ptr+1 ; move our pointer to the next character of the string
...
...
bra loop ; loop until entire string has been transmitted
...
...
; subroutine "tx_char" will move character pointed to by str_ptr to SCI1DRL for serial transmission
ldx str_ptr ; CONTENTS of str_ptr/str_ptr+1 is the ADDRESS we want to point to, which we put in X
movb 0,X,SCI1DRL ; move character at address (X) + 0 to SCI1DRL register for serial transmission
...
...
rts
To use C terminology, what we are really doing above is using a "pointer to a pointer". Our first pointer is "str_ptr" which in turn points to a second pointer, which is the pointer (address) of the current character we want to transmit. Just to recall, a pointer is a variable which holds an address. So, we must always differentiate between a pointer itself (which is an ADDRESS), and the CONTENTS of the pointer, which is data. In the Freescale manuals, an ADDRESS is usually referred with the notation M, and the CONTENTS of M are referred to in parentheses as (M). So, in our progam above, if M= $2000, then (M) = $68 (ASCII code for character 'h'). The notation is similar for registers. For instance, the register X is simply reffered to as X, whereas the CONTENTS of X are referred to as (X).
To help understand the above code, the following points may be helpful:
* From our Code Warrior Directives, remember that "string" is a synonym for address $2000, and "str_ptr" is a synonym for address $2005. So wherever we see the word "string" in our code, the assembler will actually replace it with the value $2000, and wherever we see "str_ptr" in our code, it will be replaced with the value $2005.
* When we load the 16-bit accumulator X using the "ldx str_ptr" command, we are creating the 16-bit address of the character we want to point to. We form the 16-bit address by using two successive memory locations, the high byte being at address $2005 (address of str_ptr) and the low byte being at address $2006 (address of str_ptr+1). The high byte at $2005 is initially $20, and the low byte is at address $2006 is initially $00. So the 16 bit address we initially store in X is $2000. We later change this address to $2001, then $2002, etc as noted just below.
* When we use the command "inc str_ptr+1" we are saying "add 1 to the CONTENTS located at address str_ptr+1". The address of "str_ptr+1" is $2006. The contents of address $2006 is first $00, then $01 after the first increment call, then $02 after the second increment call, etc. When combined with the high-byte $20 stored at address $2005, we get a 16-bit address $2000, then $2001, then $2002, etc. which we load into X as noted just below. So by using the command "inc str_ptr+1", we can move along our string one character at a time.
> Had we done something like "inc str_ptr", we would have changed the contents at the address "str_ptr" =$2005 from $20 initially, to $21, then to $22, etc. The low byte at address $2006 would have remained at $00 since we never change it. So, our 16 bit address that we load into X would have then been $2000, folowed by $2100, then $2200, etc. which is not what we wanted.
* When we do the "movb 0,X,SCI1DRL" command, we will move the CONTENTS of the ADDRESS in X to the SCI1DRL data register. Since the contents of X is the value $2000, we move the contents of address $2000, which is the character "h", to the serial port data register. The next go round, after we have done a "inc str_ptr+1" instruction, we will move the contents of address $2001 to the SCI1DRL data register, and so on.
> Note that we cannot use "movb X, SCI1DRL" since the hcs12 will interpret this to mean that we want to move the CONTENTS of X to SCI1DRL. That is, one the first go round we would move the value $2000 (=contents of X) to the SCI1DRL data register, which is not what we want. Remember, we want to move the CONTENTS of the ADDRESS $2000 to the SCI1DRL register, and we must use the "movb 0,X, SCI1DRL" command to do this.
* Confused? You're not alone. It takes a lot of mental work to follow the steps when using multiple levels of pointers.
The figures below may help to clarify how our pointers are being used and updated.
Fig. 1 - This figure shows how the str_ptr points to the first character of the string when our program first begins to run. When we load the 16-bit accumulator X using the "ldx str_ptr" command, we are loading the 16-bit value $2000 into X. Later, when we do the "movb 0,X,SCI1DRL" command, we will move the CONTENTS of the ADDRESS in X to the SCI1DRL data register. Since the contents of X is the value $2000, we move the contents of address $2000, which is the character "h", to the serial port data register.
Fig. 2 - This figure shows how the str_ptr points to the second character of the string after we incremented the string pointer. Note that when we write "inc str_ptr+1" we are saying to increment the CONTENTS of the memory location at address str_ptr+1. Since str_ptr is a synonym for address $2005, then str_ptr+1 refers to address $2006. The initial contents of address $2006 is $00, which is the low byte of the pointer address (the high byte is $20 at address $2005). After our first increment, the contents of address $2006 is now equal to $01. So our new string pointer points to address $2001, which contains the character "e". This is sent to the serial communications data-out register SCI1DRL.