2025-03-10 12:38 AM
I am working on a simple program on an STM32F3 (Cortex-M4) that calls an assembly function (assembly_main) from main() in a loop. However, assembly_main()executes only once and does not return back to main() as expected. The only way to make it work continuously is by forcing an unconditional branch, which is not what I want.
I suspect the issue is related to stack pointer (SP) corruption or an incorrect return address being stored while executing main(), which prevents assembly_main() from properly returning.
Any insights or debugging pointers would be greatly appreciated!
/* I do have an STM32F303RETX_FLASH.ld file which initialize with: Entry Point
ENTRY(Reset_Handler)*/
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x4000; /* required amount of stack */
// ... other lines of initializations device specific
// I have two simple codes; one is C as follows:
// C code just a looping call to blink led in assembly
extern void assembly_main(void);
__attribute__((noreturn)) int main(void) {
while (1) {
assembly_main();
}
}
@ the reset_handler and assembly are below
.section .vectors
.global Reset_Handler
Reset_Handler:
ldr r0, =_estack @ Set MSP to top of RAM
msr msp, r0
mov sp, r0 @ Explicitly set SP
ldr r0, =main @ Load address of main()
bx r0 @ Jump to main()
.syntax unified
.cpu cortex-m4
.thumb
.global assembly_main
.section .text
assembly_main: //init device and blink
@ Enable GPIOA Clock (RCC_AHBENR)
ldr r0, =0x40021014 @ RCC_AHBENR register address
ldr r1, [r0] @ Load current value
orr r1, r1, #(1 << 17) @ Enable GPIOA clock (Bit 17)
str r1, [r0] @ Store back
@ Configure PA5 as output (GPIOA_MODER)
ldr r0, =0x48000000 @ GPIOA_MODER register address
ldr r1, [r0] @ Load current value
bic r1, r1, #(3 << (5 * 2)) @ Clear bits for PA5
orr r1, r1, #(1 << (5 * 2)) @ Set PA5 as output (01)
str r1, [r0] @ Store back
loop:
@ Turn LED ON (GPIOA_ODR)
ldr r0, =0x48000014 @ GPIOA_ODR register address
ldr r1, [r0] @ Load current value
orr r1, r1, #(1 << 5) @ Set PA5 high
str r1, [r0] @ Store back
bl delay @ Call delay function
@ Turn LED OFF (GPIOA_ODR)
ldr r1, [r0] @ Load current value
bic r1, r1, #(1 << 5) @ Set PA5 low
str r1, [r0] @ Store back
bl delay @ Call delay function
bx lr @this should return to main but it will not!
delay:
ldr r2, =200000 @ Adjust delay for visibility
delay_loop:
subs r2, #1
bne delay_loop
bx lr
2025-03-10 2:30 AM
From your assembly function you call delay routine without saving the lr beforehand. Delay returns to your function, and your function returns to itself - to the instruction after the last delay call, so it's stuck in an infinite loop.
Add push {lr, r1} as the first instruction and replace bx lr at the end with pop {r1, pc}
2025-03-10 5:57 AM
Thank you for the reply! I somehow missed that and made that correction.
However, the problem persists. Even just to make sure, I unrolled the internal functions in the assembly (delay related) to make sure there are no stack related issues. It blinks only once as usual and will not return to main unless the following is replaced with pop{r1,pc}
ldr r0, =main
mov pc, r0 @explicit return to main :(
which i wouldn't prefer to have.
It is probably a stack issue, where I think lr gets corrupted or main.c vs main.s stack initialization differences??
I tried to make sure stack is well defined and quite large in the FLASH.id file as follows:
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x8000; /* required amount of stack */
/* Memories definition */
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 16K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
}
and the stack pointer in the Reset_Handler:
Reset_Handler:
ldr r0, =_estack @ Set MSP to top of RAM
msr msp, r0
mov sp, r0 @ Explicitly set SP
ldr r0, =main @ Load address of main()
bx r0 @ Jump to main()
The final code in assembly is:
Reset_Handler:
ldr r0, =_estack @ Set MSP to top of RAM
msr msp, r0
mov sp, r0 @ Explicitly set SP
ldr r0, =main @ Load address of main()
bx r0 @ Jump to main()
.syntax unified
.cpu cortex-m4
.thumb
.global assembly_main
.section .text
assembly_main:
push {r1, lr} @ Save return address (lr) and registers
@ Enable GPIOA Clock (RCC_AHBENR)
ldr r0, =0x40021014
ldr r1, [r0]
orr r1, r1, #(1 << 17)
str r1, [r0]
@ Configure PA5 as output (GPIOA_MODER)
ldr r0, =0x48000000
ldr r1, [r0]
bic r1, r1, #(3 << (5 * 2))
orr r1, r1, #(1 << (5 * 2))
str r1, [r0]
@ Turn LED ON (GPIOA_ODR)
ldr r0, =0x48000014
ldr r1, [r0]
orr r1, r1, #(1 << 5)
str r1, [r0]
ldr r2, =900000
delay_loop1: @unrolled
subs r2, #1
bne delay_loop1
@ Turn LED OFF (GPIOA_ODR)
ldr r1, [r0]
bic r1, r1, #(1 << 5)
str r1, [r0]
ldr r2, =900000
delay_loop2: @unrolled
subs r2, #1
bne delay_loop2
pop {r1, pc} @ Restore registers and return properly (not working)
I would appreciate if you can comment. Thank you.
2025-03-10 6:27 AM
Why do you think it's not working? What does it do when you step through the code at the assembly level?
The following function returns correctly for me:
assembly_main:
push {r1, lr}
pop {r1, pc}