0
votes

I'm writing an program in assembly for a TM4C123GX discovery board. I'm using Keil uVision and debugging with the simulator. I'm trying to use SysTick handler to toggle the value in GPIODATA register to have an external LED blink. TM4C uses an address mask to store bits to GPIODATA and I'm using a variable (PB5_MASK) to hold the value to mask. My problem is that

  1. When I try to read from that variable, I always get a 0 unless I store a value, despite it being initialized already (0xF0).
  2. When I get the address in main, I get 0x2000.0004. But when I get the address for the same variable in the SysTick_Handler, I get 0x2000.0000.

My questions are: Why is the variable (PB5_MASK) initialized as 0 despite given an initial value (0xF0)? And why does the same variable (PB5_MASK) have two addresses (0x2000.0000 & 0x2000.0004)?

my_Variables.s:

    AREA My_Variables, CODE, READWRITE

 PB5_MASK DCD 240;0x000000F0

    END

main.s:

    ;Include constants I define to main
    INCLUDE my_Constants.s
    ;Include variables I define to main
    INCLUDE my_Variables.s

    AREA |.text|, CODE, READONLY
    ;THUMB
    EXPORT __main
    ENTRY

    ;This  subroutine initializes GPIO
 GPIO_Init  PROC

    ;Push LR onto stack first
    PUSH {LR}
    ;write "high" to data register for port F pin 1 to turn on red LED. GPIODATA

    LDR r0, =AHB_PORTB
    LDR r1,[r0,#GPIODATAPB5]


    ;address for PB5_MASK here is 0x2000.0000, but...
    LDR r2, =PB5_MASK   ;get RAM address of PB5 mask (pointer)
    ;reads 0x0 unless I put in the value myself
    LDR r3,[r2] ;get the value of PB5 mask
    ORR r3, r3, #0xF0
    STR r3,[r2]

    ORR r1, r1, r3  ;Set PB5 to 'high'
    STR r1,[r0,#GPIODATAPB5]
    ;LDR r1,[r0,#GPIODATAPB5]

    ;Pop LR and return to __main
    POP {LR}
    BX LR

    ENDP

startup_rvmdk.s:

    ;includes constants I define to startup file
    INCLUDE my_Constants.s
    ;Include variables I define to startup file
    INCLUDE my_Variables.s

 SysTick_Handler    PROC

    EXPORT SysTick_Handler

    ;write "high" to data register for port F pin 1 to turn on red LED. GPIODATA
    LDR r0, =AHB_PORTB
    ;... address for PB5_MASK here is 0x2000.0004
    LDR r1, =PB5_MASK ;Get RAM address of PB5 mask value
    LDR r2,[r1] ;Grab value of PB5 mask
    EOR r2, r2, #0xF0   ;Toggle mas to toggle PB5
    STR r2,[r1] ;Store the toggled mask to properly toggle next time
    STR r2,[r0,#GPIODATAPB5]
    ;LDR r2,[r0,#GPIODATAPB5]

    ;return to __main
    BX LR

    ENDP

These are snippets. If the entire code is needed please let me know. Thank you!

Edit: Here is a link to a similar problem. I also had to put in the addresses being changed myself when debugging, but there isn't a solution to a variable having two addresses

ARM Assembly storing registers to memory

Edit: So the 2-addresses-for-the-same-variable problem was fixed by using IMPORT/EXPORT, but I'm still stuck with the uninitialized variable problem.

I'm having trouble understanding where to begin with bootloaders. All I want is for my variables to be initialized in RAM. I tried looking at examples that came with TivaWare and I see that Reset_Handler calls __main, which calls other subroutines like _main_after_scatter, etc. I can't find __main anywhere though? I initialially thought __main was where I was supposed to write my code (like main() ). Is __main a subroutine that just does it's own thing? If so, where do I write my code? Sorry for the multiple questions, but they're all related.

1
Are you sure you didn't just define it twice by INCLUDEing the same file twice? This would be like doing static int foo = 1234; in a C header file: each translation unit that includes that header will get their own copy of the variable. I don't know the keil tools, but can you use readelf or nm or equivalent to look at the symbol table and check for duplicates? - Peter Cordes
Thanks! I added in IMPORT and EXPORT directives like berendi said and they read a single address now (0x2000.0000). I'm using Windows so I can't use the nm command. I looked up an equivalent for Windows and found DUMPBIN, but apparently that's packaged in with Visual Studios, which I don't use. Is there an alternative to DUMPBIN? - Fua

1 Answers

1
votes

As Peter Cordes has noted in his comment, you are defining the variable twice.

Objects in an assembly module are local by default. In order to share a variable between two or more modules, you have to define it in exactly one compilation unit, mark the symbol with GLOBAL or EXPORT (they are the same), then use EXTERN or IMPORT in the other module(s) to make them visible there.

Regarding your other question, variables should be initialized at program startup.

This embedded system has no loader, the program is written directly into the onboard flash, and executed from there. Code and constant data are in the flash, variables are statically allocated in RAM, but there is nothing there that would fill in the initial values of the variables in RAM, unless your code does it.

Toolchain vendors supply a startup function, which does exactly that, copies initial values from flash to ram, zero out variables without an explicit initialization, do some more housekeeping, and transfer control to the main function of the user program. This startup function is called __main in the Keil toolchain, you should call it from the reset handler if you want your variables initialized.