Basic use of the arm-none-eabi toolchain or what happens before main
Introduction
Tutorials that help in developing software for microcontrollers focus mainly on the use of peripherals of the microcontroller itself, which is understandable, but unfortunately they rarely focus on the part of the program that executes before the main function.
The following tutorial aims to introduce this part of the program based on the GNU ARM toolchain and the stm32F334 microcontroller with the cortex-m4 core.
Tools used:
- GNU Arm Embedded Toolchain 7-2018-q2-update
https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads
- OpenOCD
- cygwin (with make)
- puTTs
- drivers required by STLINK
Install the above programs and add the path to the bin folder for the toolchain to the Path environment variable (for our convenience, otherwise you have to specify the relative path to the tools every time).
1. Startup
Where to start writing code? From searching for information on how the core manufacturer (in this case, ARM) envisioned the processor startup:
https://www.keil.com/pack/doc/CMSIS/Core/html/startup_s_pg.html
Our code starts at address 0x00000000 with the value of the stack pointer, and then the program starts at address 0x00000004 with the beginning of the vector table whose first element is the pointer for the "Reset_Handler" function.
It is possible to change the memory address from which our program runs. This is possible by physically interfering with the pins marked BOOT0 and nBOOT1. Interestingly, we can start the program from "System memory" where the "Embedded boot loader" is located, which is loaded during the production of the system and is not possible to modify.
More information about the Embedded boot loader can be found at the following link:
https://www.st.com/content/ccc/resource/techn...0/0c/CD00167594.pdf/files/CD00167594.pdf/jcr: content/translations/en.CD00167594.pdf
We can now proceed to writing our own startup.S code. What is this file? It is a file written in assembly language called assembler. The assembly language consists of two sets of instructions: ARM and thumb (thumb-2). You can read more about it in this thread:
https://stackoverflow.com/questions/28669905/...e-arm-thumb-and-thumb-2-instruction-encodings
In our case, we use the thumb-2 instruction set:
http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf
1. the .global directive makes the _start symbol visible to the linker (ld)
2. the .thumb_func directive tells the assembler (as) that the next symbol points to the thumb instructions
3. stack point address, whose value will be at address 0x08000000 flash. Currently, it will be set to the end of the SRAM memory, which can be read from the microcontroller documentation.
4. vector reset, which will be located at address 0x08000004 flash. The content of this address contains the address for the reset function:
5. Branch - jumping to the function with a label (address) without entering the address to lr (link register).
6. The code should never be here.
7. Infinite loop function.
2. Linker script
Now that we have our startup, we can start writing our linker script. The script below describes what code (and in what order) will go to each section and will provide us with information about the beginning and end of each section.
1. MEMORY allocates names to memory spaces.
2. Flash start and length. Information about the address in the documentation
3. Start of memory and Ram length. Again, information about the address in the documentation
4. section .text in this section is the executable code.
5. section start address. We can also set the section address manually via . = 0x80000;
6. Drop all text from objects into this section
7 and 8. Putting specific text from objects into this space. Keep in mind that swapping the order of the objects will cause the code to not work properly!
9. End of section address
10. Indication of the address to which the given section will fly.
11. The .rodata section contains all constant variables.
12. The .bss (block started by symbol) section contains all non-initialized static variables.
13. The .data section contains all initialized static variables. The entry "AT (__rodata_end__)" means that the section, although it will be located in SRAM, will be physically located in flash at the beginning and then it must be copied in cstartup to the ram.
3. cstartup
Now that we have our linker script, we can start writing our own crt0. This is a piece of code executed before the main function, which ensures that all static data is copied from non-volatile memory to volatile memory (in the case of static uninitialized variables, resetting them to zero).
So this is what our _cstartup looks like:
This is code written in C, but we can write it in asm without any problems.
Global variables starting with extern are variables containing information about the addresses of the beginning and end of the section, which will be described in detail in the linker section. The above code clears the .bss space where uninitialized static variables are located, when all initialized static variables are transferred from non-volatile memory to the .data section.
It is important to mention that if we want other sections to be in RAM as well (e.g. a section with code that is to be executed from RAM), they must be copied here as well.
4. Main
Once we have written all the elements related to the startup, we can proceed to writing our main function:
[syntax=c]#define RCCBASE 0x40021000
#define GPIOBBASE 0x48000400
static int wymuszenie_bss;
int wymuszenie_data = GPIOBBASE;
const int wymuszenie_rodata = 400000;
int main ( void )
{
unsigned int* ptr;
wymuszenie_bss = 0x40021000;
ptr = (unsigned int*)(wymuszenie_bss+0x14);
*ptr |= 1
Tutorials that help in developing software for microcontrollers focus mainly on the use of peripherals of the microcontroller itself, which is understandable, but unfortunately they rarely focus on the part of the program that executes before the main function.
The following tutorial aims to introduce this part of the program based on the GNU ARM toolchain and the stm32F334 microcontroller with the cortex-m4 core.
Tools used:
- GNU Arm Embedded Toolchain 7-2018-q2-update
https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads
- OpenOCD
- cygwin (with make)
- puTTs
- drivers required by STLINK
Install the above programs and add the path to the bin folder for the toolchain to the Path environment variable (for our convenience, otherwise you have to specify the relative path to the tools every time).
1. Startup
Where to start writing code? From searching for information on how the core manufacturer (in this case, ARM) envisioned the processor startup:
https://www.keil.com/pack/doc/CMSIS/Core/html/startup_s_pg.html
Our code starts at address 0x00000000 with the value of the stack pointer, and then the program starts at address 0x00000004 with the beginning of the vector table whose first element is the pointer for the "Reset_Handler" function.
It is possible to change the memory address from which our program runs. This is possible by physically interfering with the pins marked BOOT0 and nBOOT1. Interestingly, we can start the program from "System memory" where the "Embedded boot loader" is located, which is loaded during the production of the system and is not possible to modify.
More information about the Embedded boot loader can be found at the following link:
https://www.st.com/content/ccc/resource/techn...0/0c/CD00167594.pdf/files/CD00167594.pdf/jcr: content/translations/en.CD00167594.pdf
We can now proceed to writing our own startup.S code. What is this file? It is a file written in assembly language called assembler. The assembly language consists of two sets of instructions: ARM and thumb (thumb-2). You can read more about it in this thread:
https://stackoverflow.com/questions/28669905/...e-arm-thumb-and-thumb-2-instruction-encodings
In our case, we use the thumb-2 instruction set:
http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf
Code: text
1. the .global directive makes the _start symbol visible to the linker (ld)
2. the .thumb_func directive tells the assembler (as) that the next symbol points to the thumb instructions
3. stack point address, whose value will be at address 0x08000000 flash. Currently, it will be set to the end of the SRAM memory, which can be read from the microcontroller documentation.
4. vector reset, which will be located at address 0x08000004 flash. The content of this address contains the address for the reset function:
5. Branch - jumping to the function with a label (address) without entering the address to lr (link register).
6. The code should never be here.
7. Infinite loop function.
2. Linker script
Now that we have our startup, we can start writing our linker script. The script below describes what code (and in what order) will go to each section and will provide us with information about the beginning and end of each section.
Code: text
1. MEMORY allocates names to memory spaces.
2. Flash start and length. Information about the address in the documentation
3. Start of memory and Ram length. Again, information about the address in the documentation
4. section .text in this section is the executable code.
5. section start address. We can also set the section address manually via . = 0x80000;
6. Drop all text from objects into this section
7 and 8. Putting specific text from objects into this space. Keep in mind that swapping the order of the objects will cause the code to not work properly!
9. End of section address
10. Indication of the address to which the given section will fly.
11. The .rodata section contains all constant variables.
12. The .bss (block started by symbol) section contains all non-initialized static variables.
13. The .data section contains all initialized static variables. The entry "AT (__rodata_end__)" means that the section, although it will be located in SRAM, will be physically located in flash at the beginning and then it must be copied in cstartup to the ram.
3. cstartup
Now that we have our linker script, we can start writing our own crt0. This is a piece of code executed before the main function, which ensures that all static data is copied from non-volatile memory to volatile memory (in the case of static uninitialized variables, resetting them to zero).
So this is what our _cstartup looks like:
Code: text
This is code written in C, but we can write it in asm without any problems.
Global variables starting with extern are variables containing information about the addresses of the beginning and end of the section, which will be described in detail in the linker section. The above code clears the .bss space where uninitialized static variables are located, when all initialized static variables are transferred from non-volatile memory to the .data section.
It is important to mention that if we want other sections to be in RAM as well (e.g. a section with code that is to be executed from RAM), they must be copied here as well.
4. Main
Once we have written all the elements related to the startup, we can proceed to writing our main function:
[syntax=c]#define RCCBASE 0x40021000
#define GPIOBBASE 0x48000400
static int wymuszenie_bss;
int wymuszenie_data = GPIOBBASE;
const int wymuszenie_rodata = 400000;
int main ( void )
{
unsigned int* ptr;
wymuszenie_bss = 0x40021000;
ptr = (unsigned int*)(wymuszenie_bss+0x14);
*ptr |= 1
Comments
For those who are completely uninformed and trying to make their first project, it is worth adding that most of the above activities can be covered with a neat environment. There were some examples of... [Read more]
And this is an example of why I will stay with avrstudio and avr, because 8 bits is enough for me, and for larger calculations I will use some pi or orange, because unfortunately, but you did not convince... [Read more]
Raspberry pi is already ARM + GPU from brodcom ;) and this article was just inspired by writing bare metal under raspberry pi :) So I didn't even have to convince you :) [Read more]
It would be good when writing something like this to give some arguments or examples, because you only sow unnecessary confusion. Your statement makes absolutely no sense. What didn't convince you... [Read more]
Unfortunately, you are wrong, what you are writing about is optimization, which in the example I gave is not present at all (default value). The -g option for the compiler means: "Produce debugging information... [Read more]
Well, behind my back I felt that it was about debugging options, but I didn't check it out of momentum. After all, in System Worbench we have: https://obrazki.elektroda.pl/8017753000_1542662242_thumb.jpg... [Read more]
Now I've actually tested how the code will work with -O3 and interestingly the compiler wants to optimize the code by using memset and memcpy, which we don't have because we don't use standard... [Read more]
Could you elaborate a bit on the "soft reset" thread. The first time I met this term and I was interested in what it is about, how it is called and maybe where it is described, e.g. for STM32? [Read more]
In general, it turns out that I mixed up the information from cortex-M with cortex-A. In the case of cortex-M, it seems that there is always an internal pull of Vdd to GND and starting the uC again. ... [Read more]
You know, you didn't explain how it relates to STM32. How to implement it and why after this soft reset the program starts from the address 0x00000004 [Read more]
On the cortex-m it is possible to detect the source of the reset, but the code will always start from the address 0x00000000, even if the VTOR was previously set to a different value (even software reset... [Read more]
Ok thanks, I didn't notice the correction and still had what I read in my mind. :D [Read more]
When it comes to documenting this knowledge, the article is great. I used to dig with this type of settings myself, but those were the times when the IDE for ARM could be either bought or assembled by... [Read more]
After all, no one is forcing you to operate STMs in a low-level way. You might as well use CubeMX, some convenient IDE, and HAL libraries. Then you won't even need to know that something like a Makefile... [Read more]
It's not like you don't reference registers in cortex-m, it's just hidden under definitions that are more descriptive, but they're still registers. The libraries themselves are convenient,... [Read more]
It's clear. However, the entry point is lower, precisely because using these definitions is more intuitive than writing registers directly. I'm just arguing with the myth that you should start... [Read more]
A twin article for raspberry bare metal is already being prepared :) Cortex-A is definitely a different world than cortex-m, but it's worth getting interested in them because the documentation provided... [Read more]
2. the .thumb_func directive tells the assembler (as) that the next symbol points to the thumb instructions As far as I know, .thumb_func causes the function name symbol to have a value 1 greater than... [Read more]
https://sourceware.org/binutils/docs-2.15/as/ARM-Directives.html "This directive specifies that the following symbol is the name of a Thumb encoded function. This information is necessary in order to... [Read more]