Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I'll talk about the startup code that is, the code that runs even before the main function. Today, you will learn about the standard startup code that gets linked with your application from the IAR library. You will see how the startup code initializes the varius data sections and you will see these sections in the linker map file. Finally, you'll go back all the way to the beginning of the reset sequence, where you will encounter the vector table. As usual, let's get started with making a copy of the previous "lesson12" project and renaming it to "lesson13". If you are just joining the course, you can download the previous projects from state-machine.com/quickstart. Get inside the new "lesson13" directory and double-click on the workspace file to open the IAR toolset. If you don't have the IAR toolset, go back to "lesson0". The latest version published by IAR is 7.10, which I'm using in this lesson. So far in this course, you have been always starting your debugging with the main function. Today, you will explore the world before main. To do this, open the project Options dialog box and in the debugger section un-check the "Run to main" option. Also make sure that the TI Stellaris debug driver is selected, because for this lesson you will need use the real LaunchPad board, as opposed to the simulator. While you are here, make sure that the "Use flash loaders" option is selected. Finally, in the General Options revert from the Cortex-M0 experiment to the TM4C device actually used on your Tiva-C LaunchPad board. Please note that this device uses the hardware Floating Point Unit FVPv4. When you download your code to the board now, you can see that indeed you are not starting with main. Instead the fist label in the code you hit is __iar_program_start. Please also note that most registers are initialized to zero, except the stack pointer SP, which seems to be initialized to a reasonable value inside the RAM. You will need to understand how that happened later in the lesson, but for now, let's just quickly step through the code to get a sense what is going on. The first interesting instruction you should recognize is BL (branch with link) which is a function call. Here, the function being called is __iar_init_vfp, which initializes the hardware floating point unit (FPU). I don't want to go into the details of the FPU here, but let me only say that FPU needs to be initialized early in case any code downstream, such as main, wants to use it. The second interesting function call is to ?main. This is an illegal name for a C function, but I just want to remind you that we are here in the strange world before the C language, so the C rules for naming functions don't apply. This is an IAR specific startup code and they have chosen to call it ?main. Let's step inside this time. So, here you have another function call to __low_level_init. This function is intended to perform a customized initialization of your hardware that either must occur very early on, or can speed up the startup process. For example, if you plan to increase the CPU clock speed, it's better to do it sooner so that the rest of the startup code will execute faster. If you don't define your own __low_level_init, as you certainly don't at this point, an empty, library version is used. As you can see by the test of the R0 register, __low_level_init returns a value which determines whether to go straight to calling main or to perform a data initialization. The library version returns a non-zero value, so the function __iar_data_init3 is called. I skip over this function right now, because at this point your program does not have all the interesting data sections yet. I will step through the data initialization in the next debug session, after you modify the code such that all data sections will be present. Here I just want to finish this run by showing you that finally the startup code ends up calling the main function. OK, so let's modify the code such that it has all the data sections. But first, I need to clarify the term "data section" for you. Well, perhaps the best way to explain it is to simply show you the various program sections. For this, you need to enable generation of the so called map file by the linker. Please open the project options dialog box, go to the linker section, and the list tab. Here, please check the "generate linker map file" option Re-build the code by pressing F7. So, let's take a look at the generated linker map file. You can find it conveniently in the Output folder of your project. At first glance you might think that a linker map file is a cryptic, illegible, machine-level gibberish, and it certainly is. But for an embedded programmer this is also a real treasure trove of invaluable information and I highly recommend that you not only generate the map file for all your projects, but also that you learn how to read and use the information. For example, you should always know how big your program is in terms of code space in ROM and data space in ROM and RAM. To find out, you scroll to the "Module Summary" section. Here you have all this information broken down by type of data, such as read-only code, read-only data, and read-write data, as well as the object module, such as delay.o and main.o. At the bottom, you find the totals, which are currently 470 bytes of read-only code, 18 bytes of read-only data and 1060 bytes of read-write data. The largest contributor to the read-write data is the 1024 bytes generated by the linker for the stack. However, for now, the most interesting part of the map file is "Placement Summary", which lists all the program sections. For the linker, a program section is simply a contiguous chunk of memory that has a symbolic name. For example, the area between addresses 0 and 0x40 is named .intvec section. The following sections are all named .text and are for the code. Finally the .rodata section is for read-only data. All of these sections are in the ROM address range. Below that, you find several .bss sections, which hold uninitialized data that need to be set to zero during the system startup. And finally, at the very bottom, you find the CSTACK section, which holds the stack and is left uninitialized during the startup. If you wonder about the names, such as .text or .bss, for code and unitialized data, respectively, these are all historical names coming from some ancient assembly language. For example, BSS used to mean Block Started by Symbol or some such, which today has a completely different meaning. But anyway, the point is that your program doesn't have the initialized data section yet, that is, a data section that requires a specific initialization to hard-coded values during the startup. So, in the next step of this lesson, you will add some data initialization and then check again how this will change your map file. In the previous lessons of this course you have already seen that it is possible to give an initial value to a variable in its definition. The syntax is to follow the variable name by an equals sign and an expression, followed by a semicolon. For example, signed 16-bit integer x is initialized to -1, while unsigned 32-bit integer y is initialized to a binary or of #-defined constants LED_RED and LED_GREEN. You can also initialize more complex variables, such as whole arrays, in which case you need to enclose the initial values in braces and separate by commas. For example, here is an initialization of a signed 16-bit array sqr of four elements. When you provide an array initializer, you might omit the size, and the C compiler will infer the size from the initializer. Also, the initializer might have fewer elements than the specified array size, in which case the missing elements will be initialized to zero. However, the initializer cannot have more elements than the array size and you get a compilation error when you try. Initialization of structures is similar to initialization of arrays. Again, you enclose the initial values for the members in braces and separate them by commas. For example, here is an initialization of the p1 Point structure. For a structure of structures, such as Window, you simply nest the initializers of member structures. For instance here is an initialization of the Window instance w that contains two Point instances; Now, going back to the map file, perhaps you have noticed that it has changed. First, a new "Initializer bytes" section has been added in the ROM address range. Second, two new .data sections have been created in the RAM address range at the same time as two .bss sections have disappeared. And third, the combined size of the two new .data sections is 0xC byes, which is exactly the same as the size of the new "Initializer bytes" section in ROM. Shown graphically, the fact of your adding some initialization of variables caused the following changes. The linekr has inserted a new .data section in RAM for the initialized data and at the same time, the linker has created a matching "Initializer bytes" section in ROM of exactly the same size as the .data sections. The implication for the startup code is that it needs to copy the "Initializer bytes" section from ROM to the .data sections in RAM. Please note that the startup code does not initialize the data in the way you have specified it in the code, so it does not copy two bytes here and four bytes there to individual variables. Instead, the linker has specifically reordered the variables so that all initialized data can be initialized in a single block copy from "Initializer bytes" section to the .data sections. So, let's have another look at the startup code, now that you have both initialized data in the .data section and the uninitialized data in the .bss section. First, I set the memory view to the RAM region and I fill the memory with 0xFFs so that you can clearly see when the bytes are changed to zero or some other values. Next, quickly step through the startup code until you reach the __iar_data_init3 call. This time, you will step into this function to see how it initializes the data. This label indicates that the first step is to zero-initialize the data. This STR instruction is the core of the data clearing algorithm. The instruction stores the value in R3 to the address in R2. As you can see R3 is zero and R2 is exactly the address of the first .bss section in RAM. As you can see in the memory view, the STR instruction writes zeros to a 4-byte word at the beginning of the first .bss section in RAM. But I'd also like to take this opportunity to explain a new addressing mode used in this STR instruction. Please note the #4 constant after the square bracket. This offset caused incrementing the base register R2 by 4 bytes, as you can see in the register view. You should not confuse this addressing mode with an STR instruction that has a constant inside the bracket, as that one is for adding offset to the base temporarily before storing the data. The new addressing mode with incrementing the base is specifically suitable for tight loops. So, as you step through the code, you can see how the code loops around the STR instruction and each pass clears the next word in .bss section. After clearing .bss, the startup code proceeds to copying the data into the .data section. The actual copying is done by the LDR/STR instruction pair that use the addressing mode I've just explained for the .bss section. The R2 base register of the LDR instruction points to the source of the data, which is the beginning of the "Initializer bytes" section in ROM. The R3 base register of the STR instruction points to the target of the data, which is the beginning of the .data section in RAM. As you can see, again the code loops around and initializes the whole .data section. Eventually, the startup completes and the main function is called. In summary, what you've just seen is a standard-compliant startup code provided in the IAR library. By the time main() is called, the C standard requires all initialized variables to have their initial values and all uninitialized variables to be set to zero. However, startup code from other vendors might not comply with the C standard. Specifically, you might encounter startup code that does not clear your uninitialized variables in the .bss section. For example, the startup for Texas Instruments DSPs routinely does not comply in this respect. The point is that I highly recommend that you test your startup code, by methods I've just shown you. If you find out that your .bss sections are not cleared, you might need to explicitly initialize all previously uninitialized variables to zero. But this is not optimal, because you essentially convert the .bss sections to .data sections, which require a matching "Initializer bytes" section in ROM. In other words, you take space in ROM for a bunch of zeros. All right, so now that you have a fairly good overview of the standard C initialization sequence, it's time to go all the way to the beginning of the reset process. This part of the startup sequence is processor-specific, so what will follow will be specific for the ARM Cortex-M processor on your Tiva LaunchPad board. Let's start another debug session to answer two basic questions. First, you want to find out how the stack pointer SP got its initial value and second, how the program counter PC ended up at the function __iar_program_start. The clues to answer these two question are at the address 0, which is the start of ROM. When you look at this address in the disassembly window, you can see the CSTACK$$LIMIT at address zero and __iar_program_start at address 0x4. Please note that these are not machine instructions. These are simply words in memory. The code instructions start later, with the main function. So, here is the answer to your questions. The ARM Cortex-M processor is hardwired such that after reset it copies the bits from address 0 to the SP register, and the all bits except the least-significant-bit from address 0x4 to the PC. The least-significant-bit of any value loaded to the PC must be one, because this bit indicates the Thumb mode of the processor, which is the only one supported by Cortex-M. This explains why PC is 0x218, while the value at address 0x4 is 0x219. I talked about it already in previous lessons. But I'd like to point out something even more significant. What you have just discovered at address zero is the so called Vector Table. The Vector Table is described in the Datasheet of your microcontroller, where you can read what you already know, that it contains the reset value of the stack pointer SP and the start address of the PC. But the Vector Table contains more than that. It contains all the exception and interrupt vectors that your processor can handle. Of course, I need to explain what these are in the upcoming lessons, but I hope that you are excited about getting so close to the fascinating subject of interrupts. The actual layout of the Vector Table is shown on the next page. This picture is drawn in a traditional way, that is, with the address zero at the bottom and high addresses at top. This happens to be upside-down compared to the vector table in your disassembly view. So let me flip the Vector Table layout and try to match it with the view in your debugger. As you can see, the Vector Table from the datasheet now matches your view in the debugger quite nicely. All the exception vectors defined in the datasheet are initialized to BusFault_Handler, and the vectors marked as "reserved" are zero. However, the Vector Table from the datasheet is evidently much longer than the one in your disassembly view. Specifically, the vectors labeled IRQ0, IRQ1, etc. are not present in the disassembly view. This is because the vector table provided by the IAR library is generic. It contains only the standard exception vectors that are defined at the beginning of the table and are common to all Comrtex-M microcontrollers. But the IAR table does not contain interrupt vectors that are specific to a given microcontroller, such as IRQ0, IRQ1, etc., so it cannot really handle any interrupts. For that, you need to replace the generic IAR Vector Table with the specific one that will match exactly the layout defined in the Datasheet of your specific microcontroller. Before you leave this debug session, though, let's examine the BusFault_Handler code, which from the vector table should be at the address 0x1db. The actual location of this code is 0x1da, but by now you should know why the address must be an odd number, while the actual code is at an even address. So here you have also the answer why all exception vectors in the IAR vector table appear to be set to BusFault_Handler. Apparently, the IAR startup code defines all exception handlers, such as Bus-Fault, Debug-Monitor, Hard-Fault, Memory-Manager, and Non-Maskable-Interrupt, but they all point to the same piece of code. Knowing only this common address, the disassembler could not distinguish among the various exception handlers, and chose to show only the BusFault_Hanlder, because this one is the first in the alphabetical order. Finally, the IAR code associated with all these exception handlers turns out to be a single branch instruction, which jumps to itself. This means that an occurrence of any of the exceptions ends up tying the CPU in a tight endless loop. This is good for debugging, because when you break into such code, you will find it looping inside an exception handler. Hover, such a primitive policy is unacceptable for a production code, because the device will appear completely locked and unresponsive. This is known as denial of service. This concludes the lesson about the standard startup code. In the next lesson, you will learn how to replace the generic vector table with a real one that can handle all interrupts available in your microcontroller. You will also write the exception handlers that can be used in a production-quality code. And finally, you will start building the board support package for your LaunchPad board. All of this will set the stage for learning about interrupts. If you like this channel, please subscribe to stay tuned. You can also visit state-machine.com/quickstart for the class notes and project file downloads. --- Course web-page: http://www.state-machine.com/quickstart YouTube playlist of the course: http://www.youtube.com/playlist?list=PLPW8O6W-1chwyTzI3BHwBLbGQoPFxPAPM