MHarrop678
Posts: 51
Joined: Thu Nov 27, 2014 8:36 pm
Location: Ipswich, UK

Anybody know why it acts like this?

Sun Dec 07, 2014 4:41 pm

I have written 2 modules:
gpio.s

Code: Select all

/*
* This command loads the physical address of the GPIO region into r0.
*/


getGPIOAddress$:
	

mov pc,lr

/*
* mov pc,lr means putting the command in the link register 
* (Where we came from) into the next command to be executed.
* This is the equivalent of the return function in c
*/

/*
* Set GPIO 47 to Output mode by writing 0b001 to bits 21-23 (7 * 3)
* to GPFSEL4, which controls GPIOs 40 to 49.
* That is in the 4th word of the table at 0x20200010 or r0 + 0x10 (16 decimal)
*/

.globl setGPIOFunction
setGPIOFunction:
	ldr r0,=0x20200000
		mov r1,#1
	lsl r1,#21
	str r1,[r0,#16]
		mov r1,#1
	lsl r1,#15
	str r1,[r0,#32]
mov pc,lr

/*
* Turn GPIO 47 on by writing 1 to bit 15 of GPSET1. Each GPSETn location controls the output of up to 32 GPIOs.
* Writing a 1 to a GPSETn location sets that GPIO output (if it is in output mode) to a high state.
* Writing a 0 to a GPSETn location does nothing. To set a GPIO to output low you have to write a 1 to a GPCLRn.
*/

.globl turnLEDon
turnLEDon:
	mov r1,#1
	lsl r1,#15
	str r1,[r0,#32]

mov pc,lr

/*
* Turn GPIO 47 off by writing a 1 bit to GPCLR1 at address 44
*/

.globl turnLEDoff
turnLEDoff:
	mov r1,#1
	lsl r1,#15
	str r1,[r0,#44]

mov pc,lr
main.s

Code: Select all

.section .init
.globl _start
_start:

b main

.section .text
main:
mov sp,#0x8000

/*
* Normally we would read the current contents of the location first, so we don't change GPIOs 40-46 and 48,49
* when we write to it. But this is only a quick example program. That bit comes later
*/

bl setGPIOFunction

loop$:


b loop$

Now the set address, pin function and LED turn on part in gpio.s works in 'GetGPIOAddress$' with a 'bl getGPIOAddress$' call in 'setGPIOFunction', however the exact same code to set the address, pin function and turn the LED on, doesn't work in 'setGPIOFunction'.
Does anybody have any idea why?
Does anybody know why?

User avatar
rpdom
Posts: 14979
Joined: Sun May 06, 2012 5:17 am
Location: Chelmsford, Essex, UK

Re: Anybody know why it acts like this?

Sun Dec 07, 2014 8:39 pm

Code: Select all

getGPIOAddress$:
   

mov pc,lr
This bit of code doesn't do anything. It just returns. Is that right?

MHarrop678
Posts: 51
Joined: Thu Nov 27, 2014 8:36 pm
Location: Ipswich, UK

Re: Anybody know why it acts like this?

Sun Dec 07, 2014 9:18 pm

Yes, sorry the code doesn't illustrate my problem very well.

So the code to turn the LED on works within the scope of 'getGPIOAddress' but doesn't work within the scope of 'setGPIOFunction' and I have no idea why.
The code is exactly the same, so why would it work within one and not the other?

User avatar
DexOS
Posts: 876
Joined: Wed May 16, 2012 6:32 pm
Contact: Website

Re: Anybody know why it acts like this?

Mon Dec 08, 2014 3:49 pm

Try replacing

Code: Select all

mov pc,lr
With

Code: Select all

NameOfFunction:
stmfd  sp!,{ lr}
;some code here
ldmfd  sp!,{ pc}		
See if that helps
Batteries not included, Some assembly required.

MHarrop678
Posts: 51
Joined: Thu Nov 27, 2014 8:36 pm
Location: Ipswich, UK

Re: Anybody know why it acts like this?

Mon Dec 08, 2014 4:06 pm

How is that different from 'mov pc,lr' ?
How does it work?

User avatar
rpdom
Posts: 14979
Joined: Sun May 06, 2012 5:17 am
Location: Chelmsford, Essex, UK

Re: Anybody know why it acts like this?

Mon Dec 08, 2014 5:01 pm

It's the equivalent of

Code: Select all

    push lr
...
    pop pc
It keeps the return address safe even if you do another "bl" in the middle.

An example of bad code

Code: Select all

test1:
0000    mov r0,#1
0004    mov pc,lr

test2
0008    mov r0,#2
000C    bl test1
0010    mov pc,lr

main:
0014    bl test2
0018    #it won't ever get to here.
It won't work because the "bl test2" will set "lr" to the return address 0018, then go to test2. Then in test2 the "bl test1" will set "lr" to 0010. Then in test1 it will jump back to 0010 by moving lr to pc but lr now contains 0010 instead of 0018 and the code will keep running the same instruction at 0010: mov pc,lr and going back to 0010 forever.

MHarrop678
Posts: 51
Joined: Thu Nov 27, 2014 8:36 pm
Location: Ipswich, UK

Re: Anybody know why it acts like this?

Tue Dec 09, 2014 11:01 am

But that still doesn't explain why the LED Lights up after 2 branches rather than just 1?

dwelch67
Posts: 955
Joined: Sat May 26, 2012 5:32 pm

Re: Anybody know why it acts like this?

Tue Dec 09, 2014 3:51 pm

are you doing nested function calls? nested bl's when you say two branches do you mean two bl calls.

if not your problem or not now, then you will need to understand it...hopefully I am not repeating something someone said I think I read the answers here...if this was already answered or this is not the problem let me know I will remove it

Code: Select all

bl something
next_instruction
causes r14 to be loaded with the address of the next_instruction, it is our return address that is how we get back to where we started and mov pc,lr or the preferred bx lr. is how we return from a call, it puts that return address in the pc so that the pc runs from the instruction after are call, just like in C

Code: Select all

a = a+7;
myfun(a);
b=5;
after calling myfun() we want to return from myfun and run the stuff right after it. same goes in assembly.

Problem is there is only one r14. so what if you do this

Code: Select all

bl hello
mov r0,#5
...

hello:
  bl world
  mov pc,lr

world:
  bx lr
when we bl to hello lr holds the address of the mov r0,#5, good. now in hello as written we bl to world, so now lr, r14, gets the address of mov pc,lr, the address of mov r0,#5 is now lost we can never get back there btw. so then world does a bx,lr I used the different instruction to not confuse two mov pc,lr instructions. bx lr does the same thing but it also adds thumb/arm mode changes which mov pc,lr doesnt, but effectively a bx lr does a PC=LR...So world returns to execute the mov pc,lr, but when we called world, we modified lr to point at the mov pc,lr, so now we are stuck in a loop lr doesnt change it points at mov pc,lr and mov pc,lr changes the pc to point at mov pc,lr...

To fix all of this any time your function makes a call to another function (using the lr register) you need to preserve it.

Code: Select all

bl hello
mov r0,#5
...

hello:
  push {lr}
  bl world
  pop {lr}
  mov pc,lr

world:
  bx lr
so now the bl to hello, puts the address to mov r0,#5 in lr. hello then saves lr on the stack, bl world modifies lr to point at the pop {lr}, we go to world it does a bx lr which cause the pc to run at pop {lr} which puts the address of mov r0,#5 back in the lr, the mov pc,lr then causes the pc to point at mov r0,#5 and we keep going.

One shortcut to this which has to be used with extreme caution is saving an instruction and putting pc at the end of the pop list. Most arm cores I think this is okay from a thumb/arm mode perspective but I think at least one (maybe armv4t) does not let you mode change this way and you have to use bx lr, I have always used bx lr to be save, but ymmv.

not optimized, but I added few registers to the push/pop to illustrate the savings you can and will see this done this way with the lr at the end of the push and pc in the pop list. (position doesnt matter in a push/pop)

Code: Select all

bl hello
mov r0,#5
...

hello:
  push {r4,r5,r6,lr}
  bl world
  pop {r4,r5,r6,pc}

world:
  bx lr
also note that for arm mode push and pop are pseudo instructions that were not always there, older assemblers wont recognize them, they are real instructions in thumb mode.

The arm version is typically

Code: Select all

stmdb sp!,{r4,r5,r6,lr}
...
ldmia sp!,{r4,r5,r6,pc}
there is another ldmfd or something syntax that you might also see documented or used. the ia/db makes more sense to me, it is the same instruction and same encoding just different syntax. The db means decrement before the ia means increment after basically --sp and sp++, and I personally memorized ldmia, so if the ldm has ia then stm has db for them to match. Generic stm/ldm knowledge is needed down the road, using them for pushing and popping gets you used to them.

Another thing that trips people up recently is that since many of the arm busses are now 64 bits wide, the latest abi says to always push and pop in units of 64 not 32, so you will sometimes see a bogus register added to a list, say you need to preserve three registers in a function call r4,r5 and lr, a modern arm compiler that conforms to that will often do something like this

Code: Select all

push {r3,r4,r5,lr}
...
pop {r3,r4,r5,pc}
or
pop {r3,r4,r5,lr}

even if r3 doesnt need to be preserved, the reason again is the 64 bit bus, writing 4 words actually costs less than 3 from a performance perspective, each register is 32 bits wide so two registers is 64 bits wide, if you insure that you never push or pop an odd number of registers nor modify the sp in anything other than multiples of 64 bits then the stack remains 64 bit aligned (assuming the bootstrap starts it off that way).

anyway, if this is unrelated to this thread then I will try to remove it...

David

Return to “Bare metal, Assembly language”