Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I'll finally explain how ARM Cortex-M handles interrupts and why interrupt handlers can be regular C functions on this CPU. Specifically, you will see how the designers of the chip have solved the problem with saving all the right CPU registers and returning from interrupt functions. As usual, let's get started with making a copy of the previous "lesson17" project and renaming it to "lesson18". If you are just joining the course, you can download the previous projects from state-machine.com/quickstart. Get inside the new "lesson18" 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: in the last lesson you saw how interrupts work on MSP430, which is simpler than ARM Cortex-M and handles interrupts in a way that is much more typical for embedded CPUs. Based on MSP430, you learned that interrupts differ from regular functions in the two important ways: First, interrupts return with a different machine instruction than regular functions. Specifically, regular functions like LED_toggle return with the RET instruction on MSP430, while interrupts, like Timer0_Handler return with RETI instruction. The instructions must be different, because they pop different registers from the stack. Second, interrupts must save more registers than regular functions. For example, Timer0_Handler saves registers R12-R15, while regular function Timer0_Function with the exact same body does not need to save any registers. So, the question now is, how ARM Cortex-M addresses these two requirements, so that it can indeed use regular functions as interrupt handlers. As in the previous lesson, you will answer these questions experimentally. To do this, you need to trigger the SysTick interrupt on ARM Cortex-M from the debugger, so that you can precisely control when the interrupt occurs. The method you used on MSP430, was to set the counter register of the timer in the debugger to the value one-less than the limit. The timer then reached the limit on the next clock cycle, which triggered the interrupt. Unfortunately, you cannot use this method with the SysTick timer in ARM Cortex-M, because the STCURRENT register is write-clear, meaning that writing to it with any value clears the register without triggering an interrupt. But not all hope is lost. As described in the TivaC datasheet, ARM Cortex-M offers another, even more direct way, which is to manually set the interrupt pending bit in the Interrupt Control and State register. Specifically, bit 26 in this register sets the SysTick interrupt to the pending state. OK, so let's use this information to generate the SysTick interrupt at will. Load the code to the TivaC LaunchPad board and set a breakpoint at the top of the while(1) loop. Run the code. When the breakpoint is hit, move the breakpoint to the very next LDR.N instruction and also, set another breakpoint in your SysTick_Handler. Finally, in the Register panel, select the System Control Block and expand the ICSR section. Go to the PENDSTSET bit and set it to 1. By the way, Cortex-M provides an interrupt pending bit for every interrupt source, so you can trigger every interrupt in the system by this method. Most of these pending bits reside inside the Nested Vectored Interrupt Controller (NVIC). But going back to your code, you've just set up the following experiment: You are stopped at the MOVS instruction. By writing to the PENDSTSET register, you arranged for the Interrupt Line to go high on the next clock cycle. You have also placed two breakpoints at the two possible paths through your code: One is at the very next LDR.N instruction. This breakpoint would be hit if the interrupt would not preempt after the MOVS instruction and the program would execute as usual. Your other breakpoint is inside the SysTick_Handler. This breakpoint would be hit only when the interrupt would fire right after the MOVS instruction thus preempting the normal program flow at this exact point. To experimentally settle this question, you cannot single step, because it disables checking for interrupts after each instruction. Instead, you need to let the program run free. And the answer is... that the SysTick interrupt has fired. At this point you have a verified method to trigger your interrupt at a machine instruction of your choosing. You will use it in a minute to take a closer look at the interrupt stack frame and the interrupt return process, just like you did in the previous lesson for MSP430. But before this, please go back to the project options and set the Floating Point Unit (FPU) to None. The FPU is a complex peripheral for speeding up floating-point computations, but unfortunately, it adds complications to the interrupt processing, which you don't want to deal with at this time. All right, so after re-building your project without the FPU, you can finally take a closer look at the details of ARM Cortex-M interrupt handling. Let's set the breakpoints and trigger the SysTick interrupt exactly as before. But this time, let's also setup the memory view to watch the stack content. Remember that the stack grows down on ARM, so I scroll the memory view to place current SP at the bottom. Now, when you run the program and hit the breakpoint inside the SysTick interrupt, you can see that the SP dropped to by 8 stack entries. When you look into the datasheet of your TivaC MCU, in the section about "Exception Entry and Return" you can see the interrupt stack frame without FPU. Remembering that datasheets show memory from high to low, you need to turn the picture upside down to align it with the memory view in your debugger. When you do this, you can now identify which registers are saved on the stack. For example, you can identify the saved PC, that is, the address to return to after the interrupt. In fact, when you scroll up the disassembly window, you can see that the return address is the LDR.N instruction inside the while (1) loop. But, more importantly, let me mark for you all the registers saved in the interrupt stack frame, with the hope that you might recognize this particular group. Well, back in lesson 9, you learned about the ARM Application Procedure Call Standard AAPCS. AAPCS was a convention that specified, among others, which registers must be preserved by a function call. I mark these registers for you again, so that you can see that this group precisely complements the registers saved in the interrupt stack frame. So, here you have the answer to today's first issue: Cortex-M interrupt entry complements the ARM Procedure Call Standard, so that's why a regular C function can be used as an interrupt handler. Please note that this elevates the AAPCS from merely a calling convention that in principle could be different for each compiler, to a hard rule that must be implemented the same way by all compilers. OK, so now let's step through the interrupt function and take a look at how it returns. Well, the function returns in a completely standard way via the BX LR instruction, because after all, this is just a regular C function. But wait a minute, the value in the LR register is 0xFFFFFFF9, which is negative 7 in two's complement. This is NOT a valid address in the code space. So what the hell is it? Well, this is an ARM-special. When this special value is loaded into the PC, the Cortex-M hardware treats this as a return from interrupt. That's why when you execute the BX LR instruction, you can see that the SP goes back to 0x3F8 and that the contents of registers is restored to the pre-interrupt state. The PC jumps back to your while(1) loop, just after your original breakpoint, which is exactly the point of preemption. So, here you have the answer to today's second question. A standard return from a function works also as a special return from an interrupt, because the LR is loaded with a special value upon the interrupt entry. Anther way of looking at this is, is that what other processors, such as MSP430, achieve by a special instruction (interrupt-return "iret"), ARM Cortex-M achieves by using special data--content of the LR register in this case. The ARM solution based on data is actually more flexible and extensible than a special instruction. In fact, ARM provides several variants of interrupt returns, which are all summarized in the Datasheet. I'm not going to discuss in detail all the options, which you can simply read about. But let me only quickly explain the terminology used in this table. Handler mode is the distinct processor state when it handles an exception, such as an interrupt or a fault. Thread mode is when it executes regular code, such as your while(1) loop inside your main function. Floating-point state means here that FPU is activated and that interrupts use the FPU stack frame as opposed to the regular stack frame. I'm going to show you the FPU stack frame in a couple of minutes. MSP stands for the Main Stack Pointer, while PSP stands for Process Stack Pointer. The datasheet makes this distinction, because your ARM CPU has actually two stack pointers, SP_main and SP_process, but only one of them is visible as SP, depending on the internal state of the CPU. This concept is called register banking and is another of the "ARM-specials". At this point I only mention banking of the SP register to explain the terminology, but the concept will be important when I'll talk about real-time operating system (RTOS), which I plan to do in one of the future lessons. But to finish with the basic interrupt stack frame, let me only explain the optional "aligner" word. The purpose of this optional stack entry is to align the interrupt stack frame at an address that is a multiple of 8 bytes. The reason the hardware needs SP to be aligned is to perform highly optimized, block transfers of registers to and from the stack. In fact, interrupt entry and exit take only 12 clock cycles each, which is very fast, when you consider that 8 registers are pushed or popped from the stack. To show you when stack alignment will be necessary, let's run the program again, but intentionally misalign the SP in the debugger. As before, when the breakpoint in your while(1) loop is hit, you trigger the SysTick interrupt. You also setup the memory view to watch the stack in the same way as before. But this time, to better see what has changed on the stack, you additionally pre-fill the unused stack above the current value of SP with some easy to identify garbage, such as 0xDEADBEEF. And finally, you misalign the stack by subtracting 4 bytes from the SP. I you wish, you can check that the value 0x3F4 is not divisible by 8. After you hit the breakpoint in the SysTick interrupt, you can see that the 8-register interrupt stack frame has been pushed on the stack, but, interestingly, one stack entry at address 0x3F0 has been skipped. This is the "aligner" word. Upon the interrupt return, the whole 9-word stack frame is removed, because the SP goes back to the original, mis-aligned value 0x3F4. Please note that in practice stack misalignment should never happen. This is because the eight byte stack alignment is a requirement of the AAPCS and compilers make sure that the stack is always aligned. Still, the stack alignment concept will be important when I'll talk about the real-time operating system (RTOS) in one of the future lessons. As a final subject of this lesson, let me show you the impact of the FPU on the interrupt stack frame. To do this, please open the project options dialog box and re-enable the FPU. Re-build the project and setup the interrupt entry experiment exactly as before. Note that right before the interrupt, the SP is 0x3F8. When the SysTick interrupt in entered this time, please note that the SP drops all the way to 0x390, which is 26 4-byte words. This is because now the CPU uses the stack frame with floating-point storage, which is more than 4 times bigger than the regular exception. Also, please note that the LR is now set to 0xFFFFFFE9 instead of 0xFFFFFFF9, which is the special FPU-type interrupt return utilizing the much bigger FPU stack frame. The moral from this is that you need to size the stack significantly bigger if you use the FPU. There is also an additional price to pay in longer interrupt entry and exit time. This concludes this lesson about interrupt entry and exit on ARM Cortex-M. In the next lesson I will talk about race conditions, which is a concept that you absolutely need to understand to effectively work with 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.