STM32 Startup code, Bare metal - Part 3

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome to another embedded geek video on stem32 development in this part i'll explain the startup process of cortex-m microcontrollers in general and stm32 in particular this video is inspired by an excellent blog series from zero to main to which i have a link in the description below in the previous two videos we set up an open source tool chain and config figured visual studio code for developing and debugging stm32 applications so now we'll pick up where we left off so let's go to the github repository from the last video and clone this project as a starting point let's call it startup test and as you see we have a git repository here already so let's remove that and start from scratch then we'll open visual studio code in this directory since we changed the name of the project we also need to change the name for the output binary here in the make file let's call this startup test as well we also need to tell the debugger the new name of the output binary and that is configuring the launch configuration json file now we are ready to build the application so let's open up a new terminal and we use make to compile and unlink everything to a binary that we can see here it's called startup test here the size tool is used to display some information on the binary so let's run this again here we see the size of different parts of the application first we have the text part which contains the code and the constants then we have the data which is initialization values for variables and then we have bss this is uninitialized variables we can easily demonstrate this by adding a new variable to the main file here and let's make it a 32-bit integer and we can call the i and we initialize this to the value 1. to make sure it isn't optimized out let's use it as well then we can recompile again and as you see the data segment is increased by 4 bytes or a 32-bit inc then let's remove the initialization value for this one and leave it as uninitialized and we will see here that if we compile again we lose four bytes here but in the other hand we gained four byte in the bss segment let's go ahead and replace the assembly startup file from stem cube with a little bit more understandable c version so let's create c file with the same name then we have to edit the make file to make sure that we build the c file instead of the assembler code let's put the files side by side and examine what's really happening in the assembly code from the top first a number of constants are imported these are defined in the linker script and points to the memory address of certain sections start and end let's pull them in here we need to declare them as extern for the c compiler to know that they exist and are defined somewhere else since these are memory addresses in a 32-bit core these have the data type unsigned in 32 we also need to include the standard hint header for the compiler to recognize this data type then let's define the reset handler that would be the real entry point into this application this function will do all the preparations needed before jumping to your program's main function the name of this function is not really important since we will put a pointer to it down in the interrupt vector but gcc will complain and give you a warning if there is no function named reset handler in your application so let's just keep the name reset handler and avoid that warning the first thing that happens here is that we are loading the end of stack or top of stack memory address to the stack pointer and i don't really think this is necessary it's easily verified so if we just execute the program even before this instruction is executed we can see that the stack pointer here is loaded with the correct memory address the end of stack constant is defined in the linker script so if we go here and look in the linker script we can find it in the top and this is defined as the end of rom i'm not sure exactly why st have done it like this but it's usually a better idea to have the stack in beginner from that will lead to a much more predictable behavior in case you get stack overflow errors here in the linker script we also reserve some space in the beginning of flash for the interrupt vector here you can see that we put the interrupt vector in the section called isr vector then we also define the start of the initialization data segment and the start and end of the data segment and also the start and end of the bss segment and i will get to those in a minute the first thing done by the reset handler is to initialize all the static variables we will loop from the start of data to the end of data and copy the value from the start of the initialization data to the location then we will loop from the start of pss to the end of pss and fill that with zeros let's wait with this for a bit and look at what is done after the variables is initialized we will start by calling the system init function which is defined by st in the board support package and to get access to that one we need to include the header where this function is defined if we go to the device files in the board support package we can find this system header where system init is declared then let's call system init from our new reset handler then we will call libc init array which is a function defined in libc or a new lib in this case that we link in if we go to the make file we can see in the linker part that we include the lc here where we can find the libc init array to let the compiler know that this function exists we need to declare it as an extern and tell compiler not to care about where this is and that it will be linked in the final stage this function will loop through a list of all statically declared c plus plus objects and execute their constructors before entering your main function then finally let's call your main function which is where you usually enter your your own program and this of course also have to be declared at last if your main program would return for some reason we need to catch this with an endless while loop here in the end to make sure that we are not executing code outside of the function another way to solve this would be to perform a system reset or a similar action if your main function would return then let's go back to the variable initialization let's start by creating a pointer to the first four bytes of the initialization data we can handle this as you in 32 because these are aligned to 4-bit boundaries by the linker script so we can copy them for bytes at a time regardless of the data type of the actual data then let's create a target pointer of the same data type to the start of data address finally let's loop until the target pointer reaches the end of data address defined in the linker script in this loop we just need to copy the data from the target pointer to the source pointer and advance both of them one step or one four byte address this can be done by just using the plus plus operator the bss segment is even simpler because there we don't need any source data we just set the target pointer to zero so let's define a pointer here as well with the address of the start of pss segment and then we loop until we reach the end of the bss segment exactly the same as above in the loop we write 0 to the target pointer and also advance it one step exactly the same as above the next part of the starter file is the default handler this is where point all the interrupt functions that we are currently not using in our application the default handler is very simple this is just a function to catch any interrupts call that are currently not implemented so here we will put just an endless while loop the interrupt vector also points to a hard fault handler and it's usually a good idea to separate this from the default handler when debugging your program you will then know if your program halted because of a hard fault or if there was an unimplemented interrupt that stopped your program for now let's just hold here with an endless loop but here you could actually try to recover from any hard fault in some certain cases a hard fault could happen for example when you divide by zero or try to dereference a null pointer next step is to create the vector table and this is a vector of function pointers that we store at the first address in flash and that's where we defined the section called isr vector to tell jcc that we would like to put an object in a specific section we need to use an attribute and in this case we tell it just that the section is named dot isr vector to make sure that the vector ends up in flash instead of rom we also need to declare it as a const then this is a vector of function pointers taking a void and returning a void since the first entry in the vector table is the top of stack address we need to handle this a little bit special we need to type cast this into the same data type as the rest of the table so let's just explicitly say that the e stack variable is a function pointer as well even though it's not and the e-stack constant is also declared in the linker file so we need to import this as well into this file for the compiler to know that the constant actually exists the next entry in the vector table is the reset handler and the address of this is what will be loaded into the program counter on power on so this is where the processor will start executing all the remaining entries in the table is interrupt handlers and to be able to to enter them here we will first need to define them let's copy them in and define them all as functions this will be made as weak which means that the linker will use them if there is no other function with the same name in that case the function defined here will be overwritten including the device header is an easy way to get also get all the correct gcc headers included then we will find the definition of week as well let's clean this up and point them all to the default handler for now then let's fill out the rest of the vector table and here the order of these is extremely important since they are hardwired in the silicon to the corresponding interrupt and you can look at this table here in the data sheet of the processor and that will show you the order and the offset of all these entries in the table then we'll clean this up as well to make it nice and tidy to summarize the interrupt vector table is a vector of function pointers that tells the processor where to jump in case of an hardware interrupt these are weakly defined placeholder functions for hardware interrupts until we select to define them in our applications instead and overwrite the weekly defined version in this file the hard fault handler is where the processor will jump in case of a hard fault the default handler is where we direct unimplemented hardware interrupts the reset handler is the real entry point of your application it's everything that happens before we continue on to the main function all these constants are memory addresses defined by st in the linker script and here we import all them as externally defined constants si data is the start address of the initialization data the s data is the start of the data segment e data is the end of the data segment bss is the start of the bss segment and ebss is the end of the bss segment and finally e-stack is the end of the raw memory and the top of stack then we have some externally defined functions first the init array from libsy then we have the main function which is the main entry point to your application then within the reset handler we first initialized the data segment with the initialization data then we move on to the bss segment that is set to zero when everything is initialized we run the system init from the st board support package then we use libc to initialize all the static c-plus objects and run their constructors finally we jump to the main of your application and if main would return we catch this with an index loop let's try to compile this now we get an error that we have a multiple definition of the hard fault handler so this could be already defined in the by st in the files generated by cuba mx yep here we have the hard fault handler and the easiest will be just to remove the one we have in here in our startup file let's try to compile again and now it worked then we can start a debugging session and step through the code line by line we see that we are actually stepping through our own file here and not the original assembly file if we step forward a few cycles in the data initialization loop we can go to the watch here we can see that the data pointer exists in the address space of the raw memory and the data in the pointer also is in the address space of the flash memory let's jump ahead to the system init which for this cortex m0 actually is empty the leap c knit array is not relevant since we have now c plus plus objects and then we can continue on to the main part of our program that you may recognize from the previous video and here i forgot to restore the changes i made earlier so let's quickly just remove those and recompile the program and start a new debugging session where we jump directly to the main function here we will initialize the hardware abstraction layer we will initialize some peripherals then we'll wait for a button press on which we'll send the hello world message to the serial port let's bring in a serial console to make sure that everything works when i push the button now we send the hello world message every 100 milliseconds as long as the button is pressed this also tells us that our startup file actually works because this whole delay function uses the systick interrupts behind the scenes if we go to the cube mx generated interrupt definitions and look for the systick interrupt we can see here that we call the increased tick function in the hardware abstraction layer that was all for this video in the description below there will be a link to a github repository containing all the code thank you for watching and don't forget to subscribe
Info
Channel: EmbeddedGeek
Views: 12,509
Rating: undefined out of 5
Keywords: Embedded, STM32, CubeMX, GCC, Make, OpenOCD, Programming, Toolchain, Makefile, Windows, ARM, Cortex-M, Visual Studio, VSCode, Visual Studio Code, GIT, Tutorial, stm32cubeide, keil, debugging, stlink, nucleo, blue pill, Development
Id: 7stymN3eYw0
Channel Id: undefined
Length: 22min 42sec (1362 seconds)
Published: Fri Sep 25 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.