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
Cool? Ranking DIY