On my NUCLEO-H7A3ZI-Q, I am trying to make the LED at port PB7 turn on using assembly. According to the STM32H7A3 reference manual, port B is mapped at address 0x50820400 (page 129):
The following code should write the value 0xc0 to the address 0x50820400, pointing into the first byte of GPIOB_MODER, which is rw:
.section .text
reset_handler:
nop
ldr r0, GPIO_ADDR
mov r1, #0xc0
strb r1, [r0]
done:
b done
.align 2
GPIO_ADDR: .word 0x58020400
.section .vectors
.word 0x20001ffe # Initial SP
.word reset_handler # Entrypoint
However, this does not work. Looking at the memory using STM32CubeProgrammer before and after the strb instruction gives the same value 0xFFFFFEBF at 0x58020400 before and after the write instruction.
The value 0xFFFFFEBF is the reset value of GPIOB_MODER, which makes sense. However, all other values in the memory mapped region are also 0xFFFFFEBF, whereas the documentation states the reset value of some other values should not be 0xFFFFFEBF. This might suggest that I have missed some type of initialization step, but I could not find anything in the manual that states something like that should be necessary, but the manual is ~3000 pages, so I might have missed something :)
You need to enable GPIO peripheral clock first. RCC register is used for that.
I would rather discourage you from learning STM32 uCs using assembler. It is the way to nowhere.
Start from the programming manual & reference manual where ARM uCs low level programming is described. Clocks, peripherals etc etc
I am newbie. I have difficulties with understanding memory ARM memory map.
I have found example of simple sorting algorithm
AREA ARM, CODE, READONLY
CODE32
PRESERVE8
EXPORT __sortc
; r0 = &arr[0]
; r1 = length
__sortc
stmfd sp!, {r2-r9, lr}
mov r4, r1 ; inner loop counter
mov r3, r4
sub r1, r1, #1
mov r9, r1 ; outer loop counter
outer_loop
mov r5, r0
mov r4, r3
inner_loop
ldr r6, [r5], #4
ldr r7, [r5]
cmp r7, r6
; swap without swp
strls r6, [r5]
strls r7, [r5, #-4]
subs r4, r4, #1
bne inner_loop
subs r9, r9, #1
bne outer_loop
ldmfd sp!, {r2-r9, pc}^
END
And this assembly should be called this way from C code
#define MAX_ELEMENTS 10
extern void __sortc(int *, int);
int main()
{
int arr[MAX_ELEMENTS] = {5, 4, 1, 3, 2, 12, 55, 64, 77, 10};
__sortc(arr, MAX_ELEMENTS);
return 0;
}
As far as I understand this code creates array of integers on the stack and calls _sortc function which implemented in assembly. This function takes this values from the stack and sorts them and put back on the stack. Am I right ?
I wonder how can I implement this example using only assembly.
For example defining array of integers
DCD 3, 7, 2, 8, 5, 7, 2, 6
BTW Where DCD declared variables are stored in the memory ??
How can I operate with values declared in this way ? Please explain how can I implement this using assembly only without any C code, even without stack, just with raw data.
I am writing for ARM7TDMI architecture
AREA ARM, CODE, READONLY - this marks start of section for code in the source.
With similar AREA myData, DATA, READWRITE you can start section where it's possible to define data like data1 DCD 1,2,3, this will compile as three words with values 1, 2, 3 in consecutive bytes, with label data1 pointing to the first byte of first word. (some AREA docs from google).
Where these will land in physical memory after loading executable depends on how the executable is linked (linker is using a script file which is helping him to decide which AREA to put where, and how to create symbol table for dynamic relocation done by the executable loader, by editing the linker script you can adjust where the code and data land, but normally you don't need to do that).
Also the linker script and assembler directives can affect size of available stack, and where it is mapped in physical memory.
So for your particular platform: google for memory mappings on web and check the linker script (for start just use linker option to produce .map file to see where the code and data are targeted to land).
So you can either declare that array in some data area, then to work with it, you load symbol data1 into register ("load address of data1"), and use that to fetch memory content from that address.
Or you can first put all the numbers into the stack (which is set probably to something reasonable by the OS loader of your executable), and operate in the code with the stack pointer to access the numbers in it.
You can even DCD some values into CODE area, so those words will end between the instructions in memory mapped as read-only by executable loader. You can read those data, but writing to them will likely cause crash. And of course you shouldn't execute them as instructions by accident (forgetting to put some ret/jump instruction ahead of DCD).
without stack
Well, this one is tricky, you have to be careful to not use any call/etc. and to have interrupts disabled, etc.. basically any thing what needs stack.
When people code a bootloader, usually they set up some temporary stack ASAP in first few instructions, so they can use basic stack functionality before setting up whole environment properly, or loading OS. A space for that temporary stack is often reserved somewhere in/after the code, or an unused memory space according to defined machine state after reset.
If you are down to the metal, without OS, usually all memory is writeable after reset, so you can then intermix code and data as you wish (just jumping around the data, not executing them by accident), without using AREA definitions.
But you should make your mind, whether you are creating application in user space of some OS (so you have things like stack and data areas well defined and you can use them for your convenience), or you are creating boot loader code which has to set it all up for itself (more difficult, so I would suggest at first going into user land of some OS, having C wrapper around with clib initialized is often handy too, so you can call things like printf from ASM for convenient output).
How can I operate with values declared in this way
It doesn't matter in machine code, which way the values were declared. All that matters is, if you have address of the memory, and if you know the structure, how the data are stored there. Then you can work with them in any way you want, using any instruction you want. So body of that asm example will not change, if you allocate the data in ASM, you will just pass the pointer as argument to it, like the C does.
edit: some example done blindly without testing, may need further syntax fixing to work for OP (or maybe there's even some bug and it will not work at all, let me know in comments if it did):
AREA myData, DATA, READWRITE
SortArray
DCD 5, 4, 1, 3, 2, 12, 55, 64, 77, 10
SortArrayEnd
AREA ARM, CODE, READONLY
CODE32
PRESERVE8
EXPORT __sortasmarray
__sortasmarray
; if "add r0, pc, #SortArray" fails (code too far in memory from array)
; then this looks like some heavy weight way of loading any address
; ldr r0, =SortArray
; ldr r1, =SortArrayEnd
add r0, pc, #SortArray ; address of array
; calculate array size from address of end
; (as I couldn't find now example of thing like "equ $-SortArray")
add r1, pc, #SortArrayEnd
sub r1, r1, r0
mov r1, r1, lsr #2
; do a direct jump instead of "bl", so __sortc returning
; to lr will actually return to called of this
b __sortc
; ... rest of your __sortc assembly without change
You can call it from C code as:
extern void __sortasmarray();
int main()
{
__sortasmarray();
return 0;
}
I used among others this Introducing ARM assembly language to refresh my ARM asm memory, but I'm still worried this may not work as is.
As you can see, I didn't change any thing in the __sortc. Because there's no difference in accessing stack memory, or "dcd" memory, it's the same computer memory. Once you have the address to particular word, you can ldr/str it's value with that address. The __sortc receives address of first word in array to sort in both cases, from there on it's just memory for it, without any context how that memory was defined in source, allocated, initialized, etc. As long as it's writeable, it's fine for __sortc.
So the only "dcd" related thing from me is loading array address, and the quick search for ARM examples shows it may be done in several ways, this add rX, pc, #label way is optimal, but does work only for +-4k range? There's also pseudo instruction ADR rX, #label doing this same thing, and maybe switching to other in case of range problem? For any range it looks like ldr rX, = label form is used, although I'm not sure if it's pseudo instruction or how it works, check some tutorials and disassembly the machine code to see how it was compiled.
It's up to you to learn all the ARM assembly peculiarities and how to load addresses of arrays, I don't need ARM ASM at the moment, so I didn't dig into those details.
And there should be some equ way to define length of array, instead of calculating it in code from end address, but I couldn't find any example, and I'm not going to read full Assembler docs to learn about all it's directives (in gas I think ArrayLength equ ((.-SortArray)/4) would work).
1. #define timers ((dual_timers *)0x03FF6000)
This is a memory map definition used in an ARM Microcontroller
where the structure definition is
2. struct dual_timers
{
special_register TMOD;
special_register TDATA0;
special_register TDATA1;
special_register TCNT0;
special_register TCNT1;
};
What the meaning of(dual_timers *)0x03FF6000) ?, is it type casting .
if it is typecasting please explain its influence in the code.
How would the compiler see the definition 'timers' after this?
This has been asked and answered countless times here.
First off the structure thing is a bad idea, not portable not reliable, even though it is used as often as it isnt in vendors code. Little time bombs waiting to go off and have you pay them for support perhaps.
Your define is just elementary C. It is a typecast, I have this address happens to be hardcoded, in C programming class we might have used the name of some other pointer and likely not the define
unsigned int *bob;
unsigned char *ted = (unsigned char *)bob;
(yet another programming trick you should never use). And you can spin that around as a define
#define ted (unsigned char *)bob
Or something to that effect. bob is just an address with a human readable name.
For this to work you need a volatile in there (which it isnt?) and they have yet another typedef somewhere that defines dual_timers so they dont have to keep typing volatile unsigned int or volatile uint32_t or volatile uint8_t or whatever size the registers are. The volatile is because you know but the compiler doesnt that you are pointing at hardware not ram, you need the compiler to perform all of the loads and stores and not optimize any out.
In addition you need the compiler to perform the right sized loads and stores, if it is a register that can only be accessed with 32 bit wide transactions, you need the compiler to implement this with the right instructions. And no matter what you do that is not a guarantee, this programming style can and if you are unlucky will fail for you. It is a very wide spread practice, but it is not foolproof. It and even worse than making pointers to absolute addresses is using structures across a compile domain, hardware is a separate compile domain from your code. You cannot guarantee no matter how many compiler specific directives you find, that that code will remain working as time goes on and compilers are upgraded or if god forbid you try to compile on some other computer. It may work 99.9999% of the time but that time that it fails is a massive failure that earthquake once in a zillion years that wipes out all of Tokyo. As you see in kernel drivers using an abstraction makes for portable code, in bare metal you can implement that abstraction in assembly language and guarantee the correct instruction is used. It can cost you some cycles, so you can create a define/typedef just like the one you are asking about for the abstraction, but your code is not forced into that and a complete re-write of your code is not required if you need to port that code or work around a chip errata, etc. the latter is my personal opinion and style based on decades of experience in bare metal programming.
The define is just an elementary C typedef nothing special or fancy just read it like any other C syntax to understand what it is doing. The struct is a way of applying offsets to that address, so if we assume that all of these registers are 32 bit then the "desire" is to have accesses to TMOD be at address 0x03FF6000+0x00, accesses to TDATA0 be at address 0x03FF6000+0x04, TDATA1 0x03FF6000+0x08 and so no. But again there is nothing here that insures that is actually going to happen nor does it insure that 32 bit loads or stores are used. A simple disassembly of the code will show these addresses being generated for these accesses.
I assume you tried using code like this to see what it did:
typedef volatile unsigned int special_register;
typedef struct
{
special_register TMOD;
special_register TDATA0;
special_register TDATA1;
special_register TCNT0;
special_register TCNT1;
} dual_timers;
#define timers ((dual_timers *)0x03FF6000)
unsigned int fun ( void )
{
timers->TMOD=5;
timers->TDATA0|=1;
timers->TCNT0=timers->TCNT1;
return(timers->TDATA1);
}
for arm as you mentioned producing
00000000 <fun>:
0: e3a02005 mov r2, #5
4: e59f301c ldr r3, [pc, #28] ; 28 <fun+0x28>
8: e5832000 str r2, [r3]
c: e5932004 ldr r2, [r3, #4]
10: e3822001 orr r2, r2, #1
14: e5832004 str r2, [r3, #4]
18: e5932010 ldr r2, [r3, #16]
1c: e583200c str r2, [r3, #12]
20: e5930008 ldr r0, [r3, #8]
24: e12fff1e bx lr
28: 03ff6000 mvnseq r6, #0
Yes it is type casting. It basically says that starting from address 0x03FF6000 you can consider that there is a dual_timers structure.
In this context, I guess that special_register is defined as something like volatile unsigned uint32_t.
This is a typical way of easily accessing the registers of a microncontroller. For accessing the register TDATA0 for example, in your code you will need to use timers->TDATA0
It means that there is a pointer to the structure dual_timers and the value of the pointer is 0x03FF6000, i.e. it is pointing to the structure located at 0x03FF6000.
The compiler (in fact preprocessor) sees the expression (dual_timers *)0x03FF6000) every time it looks at the word timers. For you it looks like timers->TDATA0 but for the compiler it looks like (dual_timers *)0x03FF6000)->TDATA0, take TDATA0 field of dual_timers structure located at 0x03FF6000.
I've been debugging this for many hours now.
My application is an embedded program running on the CC2650 ARM M3 processor using the TI RTOS.
This line of c generates an ARM hard_fault exception (LD - link register set to 0xFFFFFFFD):
leaseStartMessageForReplay = *leaseStartMessage;
The code simply dereferences the leaseStartMessage struct pointer and copies the full struct content (2 words) to the leaseStartMessageForReplay struct. (Thats the intension at least).
The actual assembly for that line looks like this:
The assembly seems correct: 1st line loads R0 with the address of leaseStartMessage. 2nd line loads R2 with the address of the leaseStartMessageForReplay. 3rd line load the two words located at address-R0 into R0 and R2. 4th line stores the two words in R0 and R2 at address-R1.
The hard_fault exception happens on the 3rd line. The registers R0, R1, R2 have these values just before executing the 3rd instruction:
As can be seen the two address pointers R0 and R1 are initialized and I have verified that they contain the correct addresses.
Any help on how to debug this would be greatly appreciated.
R0 isn't aligned to a 32 bit address, and LMDIA requires alignment.
Use memcpy() instead.
I'm currently working on a bootloader for an ARM Cortex M3.
I have two functions, one in C and one in assembly but when I attempt to call the assembly function my program hangs and generates some sort of fault.
The functions are as follows,
C:
extern void asmJump(void* Address) __attribute__((noreturn));
void load(void* Address)
{
asmJump(Address);
}
Assembly:
.section .text
.global asmJump
asmJump: # Accepts the address of the Vector Table
# as its first parameter (passed in r0)
ldr r2, [r0] # Move the stack pointer addr. to a temp register.
ldr r3, [r0, #4] # Move the reset vector addr. to a temp register.
mov sp, r2 # Set the stack pointer
bx r3 # Jump to the reset vector
And my problem is this:
The code prints "Hello" over serial and then calls load. The code that is loaded prints "Good Bye" and then resets the chip.
If I slowly step through the part where load calls asmJump everything works perfectly. However, when I let the code run my code experiences a 'memory fault'. I know that it is a memory fault because it causes a Hard Fault in some way (the Hard Fault handler's infinite while loop is executing when I pause after 4 or 5 seconds).
Has anyone experienced this issue before? If so, can you please let me know how to resolve it?
As you can see, I've tried to use the function attributes to fix the issue but have not managed to arrive at a solution yet. I'm hoping that someone can help me understand what the problem is in the first place.
Edit:
Thanks #JoeHass for your answer, and #MartinRosenau for your comment, I've since went on to find this SO answer that had a very thorough explanation of why I needed this label. It is a very long read but worth it.
I think you need to tell the assembler to use the unified syntax and explicitly declare your function to be a thumb function. The GNU assembler has directives for that:
.syntax unified
.section .text
.thumb_func
.global asmJump
asmJump:
The .syntax unified directive tells the assembler that you are using the modern syntax for assembly code. I think this is an unfortunate relic of some legacy syntax.
The .thumb_func directive tells the assembler that this function will be executed in thumb mode, so the value that is used for the symbol asmJump has its LSB set to one. When a Cortex-M executes a branch it checks the LSB of the target address to see if it is a one. If it is, then the target code is executed in thumb mode. Since that is the only mode supported by the Cortex-M, it will fault if the LSB of the target address is a zero.
Since you mention you have the debugger working, use it!
Look at the fault status registers to determine the fault source. Maybe it's not asmJump crashing but the code you're invoking.
If that is your all your code.. I suppose your change of SP called the segment error or something like that.
You should save your SP before changing it and restore it after the use of it.
ldr r6, =registerbackup
str sp, [r6]
#your code
...
ldr r6, =registerbackup
ldr sp, [r6]