Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I'll finish the subject of the startup code. Today, you will learn how to properly initialize the vector table with the correct stack pointer and all interrupts available in your Tiva-C microcontroller. You will also see how to write and test the exception handlers, so that they actually do what they are supposed to. As usual, let's get started with making a copy of the previous "lesson14" project and renaming it to "lesson15". If you are just joining the course, you can download the previous projects from state-machine.com/quickstart. Get inside the new "lesson15" 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". To quickly summarize what happened so far: at the end of the last lesson you have added a new file, named startup_tm4c.c to your project, in which you have defined the vector table as a constant array of integers. The focus of the previous lesson was to make sure that this new vector table from startup_tm4c.o was linked instead the generic one from the IAR library, and that it was located in the section ".intvec" at address zero. But, the new vector table was not initialized properly yet. So the first order of business today will be to provide the right initialization. To remind you quickly how the vector table should look like, let me open the relevant Section in the TM4C Datasheet. Please remember that most pictures showing memory layouts in datasheets are drawn with low addresses at the bottom and high at the top, which is upside down compared to how you initialize variables in C or how you view them in the debugger. So, with this in mind, you can see that the very first value in the vector table should be the initial value of the Stack Pointer at address 0, and the next is the address of the Reset handler at address 4. So, going back to your startup file, your first task will be to initialize the stack pointer correctly. The challenge here is not to break the compatibility with the standard IAR linker-script editor, where, as you might remember from the last lesson, you can set the CSTACK size. To get an idea how to initialize the stack pointer in a way that's compatible with the IAR toolset, let's step back and load the workspace from lesson 13, which still used the default startup code from the IAR library. When you load that older code to the LaunchPad board and look at address zero in disassembly, you can see that the first entry of the vector table is CSTACK$$Limit. This is an interesting observation, because it indicates that the symbol CSTACK$$Limit is known to the linker. So, let's reload our most recent workspace and try to use the symbol CSTACK$$Limit to initialize the stack pointer. But before hacking the code, let's read some IAR Help. Click on Help/Contents menu and choose the "IAR C language extensions" section. When you read it, you will find out that the IAR linker generates symbols section-name$$Base and section-name$$Limit for every section defined in the program. The linker places the symbols at addresses corresponding to the base and limit of the section, respectively. The way it works for the CSTACK section is shown in the following picture. The linker creates symbols CSTACK$$Base at the beginning of CSTACK and CSTACK$$Limit at the end of the CSTACK section in memory. Please note that on the ARM processor the stack grows from high RAM to low RAM, so the initial Stack Pointer needs to be set to the address of CSTACK$$Limit So, finally, back to the code, let's just try to initialize the stack pointer entry in the vector table to the address of the CSTACK$$Limit symbol. When you compile the code by pressing F7, you get a compiler error that the symbol CSTACK$$Limit is undefined. This error should actually make sense, because the section symbols are created by the linker after the compilation, so the C compiler upstream the build process does not know about them. So, you need to somehow inform the compiler about the existing CSATCK$$Limit symbol. In C, you do this by a declaration of the symbol as a variable. So far in this video course, you have only used variable declarations that were definitions at the same time, like so: int CSTACK$$Limit; But, this won't do in this case, because a variable definition introduces a new variable, which will cover the symbol defined by the linker. Instead, all you want is to simply introduce the symbol to the compiler, without creating it and allocating storage for it. In C, this can be achieved by means of a variable declaration that is not a definition. C provides the special keyword "extern", which you place in front of the variable definition. Note that a declaration still needs to specify the type of the variable, such "int" here, but in this particular case the type does not matter, as you are only interested in the address of the variable. When you try to compile now, you see that the symbol is recognized, but the compiler now complains that a pointer to int can't be used to initialize entity of type int, which is the type of the __vector_table. The simple solution is to apply an explicit cast to int, after which the compiler is happy, and also the program links correctly, meaning that the linker could also successfully resolve the CSTACK$$Limit symbol. I'm sure you are curious whether your stack pointer initialization really worked. So, let's load the code into the LauchPad board. Well, as you can see, the disassembly view shows CSTACK$$Limit at address zero, as expected, and the value in the SP register is 0x20000410, which looks like the top of stack. Indeed, when you inspect the linker map file, you can see that the linker created symbol CSTACK$$Limit is allocated at the address 0x20000410, which is exactly what you saw in the SP register. To prove beyond any doubt that you have not broken the compatibility with the linker script editor, let's change the stack size through the IDE. As you close the dialog box, the stack size changes in the linker script as well. Now, rebuild the project and verify that the address of CSTACK$$Limit has increased to 0x20000428. Finally, load the code again to the LaunchPad board and check that the new value in the SP register is exactly 0x20000428. OK, you are done with the sack now, so let's move on to initialization of the exception and interrupt handlers that follow in the vector table, according the to TivaC datasheet. The first handler to initialize is Reset, which is located immediately after the stack. The meaning of the Reset handler is that it is the initial value copied to the PC register when the microcontroller comes out of reset, so this is the address in ROM where the ARM Cortex-M processor starts executing code. Just like with the stack pointer, let's first look at the default initialization of the Reset handler from the standard IAR library. So, here is the disassembly view of the standard vector table, which you used back in lesson 13. As you can see, the reset handler was set to __iar_program_start, which is the startup code you learned about in lesson 13. This startup code provided everything you needed, so let's reuse it in your custom vector table as well. But how can you obtain an address of a function, such as __iar_program_start in C? I mean, so far you've only learned how to call functions, but you have never had a need to access the address of a function before. It turns out that C allows you to take an address of a function, just like it allows you to take an address of a variable. For example, you can take an address of __iar_program_start and use it to initialize your vector table, like this: Again, similarly as with the CSTACK$$Limit symbol, you need to declare the symbol __iar_program_start as a function, by providing its prototype. When you attempt to compile the code now, you get an error that the funny looking type (void (*)(void) can't be used to initialize an int. This is again similar to what happened with the CSTACK$$Limit symbol, except now the pointer type is more complex, because it is a pointer to a function that takes void arguments and returns a void rather than a simple pointer to an int. I don't want to go into details how you can declare such pointers to functions as independent variables, because it is not needed to understand the code at hand. I will leave pointers to functions for another lesson. For now, to get rid of the error, I will apply an explicit cast to int, just as I did with CSTACK$$Limit. By the way, an address of a function can be obtained in C even whey you don't use the ampersand in front of the function name. This is allowed, because a function name not followed by parentheses can't be misconstrued as a call to the function. As you can see the code without the ampersand compiles just fine, but I personally don't like this style and would not recommend it. Unfortunately, a lot of code out there, including vector tables from various silicon vendors, dont use ampersands, so you definitely need to know about such possibility. I simply consider the notation with ampersand superior, because it is very clear that you mean an address of a function. Needless to say, I will use ampersands in the rest of the vector table. But before moving on, I'm sure you are curious whether the Reset handler initialization actually worked. So, when you load the code to your board, you can see that the program is stopped at __iar_program_start at address 0x1b8, which is the value in the PC. Also, in the vector table at address zero, you can see both CSTACK$$LImit and __iar_program_start. Finally, when you run the code the LED blinks, so the code still works. OK, let's now move on the the other entries that follow Reset in the vector table. Specifically, I mean here entries with negative IRQ numbers, such as NMI, Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, SVCall, PendSV and SysTick. These entries in the vector table are common to all ARM Cortex-M processors and are for handling exceptions. For example, in lesson 10 you encountered a stack overflow. When the SP register dropped below the start of RAM, the processor entered the HardFault exception, which you could see this in the debugger. You can read more about the exceptions and which errors they handle later in the datasheet in the section "Fault Handling". Please note that the standard exceptions are arranged in the vector table not contiguously, but rather there are gaps in the table marked as "Reserved". This is very important, because you need to preserve this exact layout in your custom vector table. Back to code, you initialize the standard exceptions in exactly the same way as the Reset exception, but you pay attention to all the reserved slots, which are typically initialized to zero. As far as the prototypes of the exception handlers are concerned, you don't need to declare them yourself, because they are all provided for you in the tm4c CMSIS header file. The names of exception and interrupt handlers are not arbitrary, but rather they are part of the the CMSIS standard. Using standard names is important when you want to use the startup code together with other components, such as Real-Time Operating Systems or other 3rd party software. OK, so, include the tm4c CMSIS header in your startup code. Now, unlike the __iar_program_start Reset handler, which was taken from the standard IAR library, you actually need to write the code for the other exception handlers, such as HardFault_Handler. The standard way to code an exception handler is to use an endless loop that ties up the CPU when the corresponding exception, such as HardFault, is taken. This is convenient for debugging, because when you break into such code, you find it spinning inside the endless loop. Unfortunately, most people leave such code in the final product. I'm sure you have experienced devices that seemed to "freeze" and could not be used, not even turned off, until the battery has been pulled out and inserted back in. This is a classic example of "denial of service" due to incorrectly coded exception handlers. So, instead of an endless loop, I typically call a function named assert_failed() that provides a common handler for all sorts of errors, including assertions that I hope to talk about in the future lessons. I use assert_failed, because it is also used in many code libraries for ARM and it is already prototyped in the tm4c_cmsis header file. Please note that assert_failed is only suitable for situations when you encounter an unrecoverable error and you don't want to continue. In that case the purpose of assert_failed is to perform some damage control and, most commonly, to reset the machine. The other purpose of assert_failed is to report the error location and to record it in an error log, if possible. For this purpose the function takes two arguments: a pointer to a constant file-name string, and the line number at which the call occurred within the file. This information will allow you to easily locate the error in the code. Inside the HardFault handler, the function is called with the fault name, and the __LINE__ symbol, which is the standard preprocessor macro that resolves to the line number in which it appears. You can code all other fault handlers the same way. However, the vector table contains also exceptions that are not faults, such as SVC_Handler, DebugMon_Hanlder, PendSV_Handler, and SysTick_Handler. Instead of treated them as faults, what you ideally want is to provide an option for these handlers to be defined somewhere else in your program, but only if needed. If not used, you would like to provide a default implementation, which will indicate a fault of using an undefined handler. It might seem like you can't have a cake and eat it too, but the IAR compiler provides a very convenient way of providing the so called weak alias for a function. For example, here are the weak aliases for the non-fault exception handlers from your vector table. The weak alias means that if a symbol remains undefined till the end of the linking process, the provided alias will be used. For instance, if SVC_Handler is not used, it will be replaced with Unused_Handler. However, if the symbol is defined in the project, the weak alias is ignored and the linker does not report multiply defined symbols. Of course, the alias itself must be defined, so here is the definition of the Unused_Handler. And finally, you also need the prototype of the Unused_Handler at the top of the file. When you try to build now, you can see that the startup_tm4c actually compiles cleanly, but the linker reports that assert_failed is undefined. This is to be expected, because you obviously need to add that function to your project, but the question is in which file. I propose that you create a new file for the so called Board Support Package, that you save it as bsp.c, and you add it to the project. As the file is specific to you board, it needs to include the tm4c_cmsis header file. You will put here the board-specific stuff, like your error and assertion handling policy. You will also put here your specific interrupt handlers, so the file will come in really handy in the future. Regarding the definition of the assert_failed function, any damage control will really depend on your project, so you need to revisit this function after you carefully design your error recovery strategy. In the end, however, you will typically need to reset the system, for which the CMSIS standard provides a useful function called NVIC_SystemReset. As you can see, the project build succeeds with no errors and no warnings. As usual after every incremental step, you need to check how the code performs on the target. As you can see in the disassembly view, the vector table at address zero matches the initialization in your startup_tm4c. Specifically, the fault handlers and the reserved vectors match exactly. The non-fault handlers, all shown as DebugMon_Handler in the disassembly, because they are all aliased to the same address of Unused_Handler, and the IAR debugger displays all of them using the first name in the alphabetical order, which happens to be DebugMon. And finally, you want of course to run the code to see if the LED still blinks. And so it does. The next interesting test is to check the aliasing. You can do this by providing your own implementation of one of the non-fault handlers, for example SysTick_Handler, which you expect to be used instead of the alias. The code builds cleanly. And when you look in the disassebly, you see your SysTick_Handler instead of the DebugMon_Handler alias. Again, when you run the code, the LED still blinks. I'm afraid that at this point, you might think that you have tested your new stuff and you can move on to adding all other interrupt vectors. But not so fast. You have tested neither any of the fault handlers nor your assert_fault function implementation. So, let's try to exercise these parts of your code. But this seems easier said than done. I mean faults simply don't happen in your primitive Blinky program, so you would have to wait forever for something very unlikely, such as a cosmic ray hitting just the right part of the silicon. The answer is that you need to make it happen at your will. Scientifically, this is called "fault injection". So here is what you could do: set a breakpoint at the beginning of the delay function and run the code. When the breakpoint is hit, just before the stack push instruction, change the SP register to 0x2 followed by all zeros, which is the start of your RAM. Now very carefully single step through the code. Oops, as the SP drops below the start of RAM, the program jumps to HardFault_Handler. So far, this is exactly what you would expect. But wait a minute. The first instruction of HardFault_Hanler is another push to the stack, so when you step again, you don't move forward. Even if you run the program at full speed and break into it, you find it at the beginning of HardFault_Handler. Well, I really hope that you know why. Yes, you are right. The PUSH instruction at the beginning of HardFault causes another fault, which re-enters HardFault. This is obviously not good, because your assert_failed function is not even called. Instead, you have an implicit endless loop and denial of service--something that you precisely wanted to avoid. How do you fix it? Well, you obviously don't want to access the stack in your fault handlers or in assert_failed, because the stack might be corrupted. As described in the IAR help, IAR provides a C language extension called __stackless, which tells the compiler not to use the stack for a given function. A function designated as stackless violates the calling convention such that it is impossible to return from it. But remember that you don't want to return from your fault handlers or from assert_failed anyway. Instead, all you want to do is to perform some damage control, record the error, and reset. So, the __stackless keyword is exactly right for all the fault handlers and the assert_failed function. With this change, let's build and run the code again. First, check that the LED is still blinking and then stop the program. As before set a breakpoint at the beginning of the delay function and run the code. After the breakpoint is hit, change the SP register to 0x2 followed by all zeros, which is the start of your RAM. Now very carefully single step through the code. As the last time, the stack overflow that you have just created caused the HardFault exception. But this time around, there is no stack push instruction and the code proceeds to calling the assert_failed function. Finally, assert_failed does not access the stack either, so it also successfully calls the NVIC_SystemReset, which succeeds because you find yourself at __iar_program_start that is your Reset handler. All right, so your code works and no longer seems to suffer from the "denial of service" problem. Unfortunately, you can't say that of the most startup code examples distributed by the various silicon vendors. That startup code is perhaps sufficient for debugging but is inadequate for deployment in products. So, while my goal here is to give you startup code that you can take all the way to production, I'd like to make a larger point as well. I want you to develop a habit of exercising all parts of your code, especially parts that are executed infrequently, like your fault handlers. To test infrequent events, you don't need to wait until they occur naturally. Instead, use the "fault injection" method to intentionally cause errors at will. And lastly, I hope you start to see the benefits of having a single common error handler like assert_failed. One obvious advantage is that a single breakpoint at this function will catch all faults, errors, and assertions during debugging. I'm running out of time for this lesson, but perhaps in the future I will show you how to hard-code a breakpoint in ARM Cortex-M. For now I highly recommend that you always set a regular breakpoint in assert_failed. But back to your custom vector table, it has all the standard exceptions, but you still need to add all the interrupt handlers that your processor supports. I'm talking here about the entries marked as IRQ (interrupt request) in the datasheet. When you go a couple of pages up, you will see the complete list of these interrupts. Please note that this list also contains some Reserved entries, which you mustn't forget to preserve the exact layout of the table. Adding the actual interrupt handlers to the vector table is tedious, but does not introduce any new tricks that you should know about, so I simply copy the list that I prepared in advance. Please note that all the prototypes are provided int the tm4c_cmsis header file. The final touch is to alias all the interrupt handlers to the Unused_Handler. Finally, one last system build shows that the code compiles and links cleanly. This concludes this third and last lesson about the startup code. In the next lesson, I will introduce 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.