APPENDIX G
Trouble Shooting Chapter 11 of this book covered various techniques in locating problems in program code. In this section, we will summarize the most common mistakes and problems that software developers might encounter when developing software for the CortexÒ-M0 and Cortex-M0+ microcontrollers.
G.1 Program Does Not Run/Start There can be many different possible reasons including dysfunctional hardware. A number of possibly software caused are listed here.
G.1.1 Vector Table Missing or Vector Table in Wrong Place Depending on the tool chain, you might need to create a vector table. If you do have a vector table in the project, make sure it is a vector table which is suitable for CortexÒ-M0 or Cortex-M0+ processors (e.g., vector table code for ARM7TDMIÔ cannot be used). It is also possible for the vector table to be removed accidentally during the link stage, or being placed into the wrong address location. For example, some of the Cortex-M0+ microcontroller has boot ROM at address 0x0, and user flash in a different address. It means that the linker settings might need to be adjusted to ensure that the vector table is located at the start of the user flash location rather than address 0x0. To help debugging problems related to vector table, an easy way is to generate a disassembled listing of the compiled image or a linker report to see if the vector table is present, and if it is correctly placed at the start of the memory.
G.1.2 Incorrect C Startup Code Being Used Besides from compiler options, make sure that the linker options are correctly specified as well. For example, if you are creating your own linker scripts for gcc tool chains. Otherwise a linker might pull in incorrect C startup code. For example, it might end up using startup code for another ARMÒ processor, which contains instructions not supported by the Cortex-M0/Cortex-M0+ processor, or unintentionally used startup code for a debug environment with semihosting, which might contain breakpoint instruction (BKPT). This can cause an unexpected HardFault or other software exceptions. 715
716 Appendix G
G.1.3 Incorrect Values in Reset Vector Make sure the reset vector is really pointing to the intended reset handler. For example, some code examples on the internet might not be based on CMSIS and use _start()/ __main() instead of Reset_Handler() as reset vector in the vector table, hence skipping the SystemInit function. Also, you should check the initial stack point value in the vector table is pointing to a valid memory location, and all the exception vectors in the vector table have the LSB set to 1 to indicate Thumb code.
G.1.4 Program Image Not Programmed in Flash Correctly Most flash programming tools automatically verify the flash memory after programming. If not, after the program image is programmed into the flash, you might need to double check if the flash memory has been updated correctly. In some cases, you might need to erase the flash first and then program the program image.
G.1.5 Incorrect Tool Chain Configurations Some other tool chain configurations can also cause problems with the startup sequence. For example, memory map settings, CPU options, endianness settings, etc.
G.1.6 Incorrect Stack Pointer Initialization Value This involves two parts. Firstly, the initial Stack Pointer (SP) value (the first word on the vector table) needs to point to a valid memory address. Secondly, the C startup code might have a separate stack setup step. Try getting the processor to halt at the startup sequence, and single step through it to make sure the SP is not changed to point to an invalid address value.
G.1.7 Incorrect Endian Setting Most ARM microcontrollers are using little endian, but there is a chance that someday you switch to use an ARM Cortex-M0 or Cortex-M0+ microcontroller in big endian configuration. If this is the case, make sure the C compiler options, assembler options, and linker options are set up correctly to support big endian mode. The CMSIS package contains a range of precompiled libraries, including some libraries compiled for big endian systems. Therefore it is possible to pick up incorrect library in a project and it is important to check if the right library is used.
Trouble Shooting 717
G.2 Program Started, but Enter HardFault G.2.1 Overview In summary, when debugging HardFaults on CortexÒ-M0/Cortex-M0+ processors, several pieces of information are very useful: • • • •
Extract the stacked Program Counter (PC) (see Chapter 11, Section 11.3, Analyze a fault) Check the T bit in the stacked Program Status Register (xPSR) Check the Internal Program Status Register (IPSR) in the stacked xPSR Generate a disassembled listing of the complete program image
If the SP is pointing to an invalid memory location, then you would not be able to extract the stack frame. In these occasions, you can: •
• •
•
•
Check if you have allocated enough stack space. Various tool chains have different way to provide the stack usage of the application code. In any case, stack usage analysis is something you should do anyway, even the program did not crash. Do not forget that exception handlers also need stack spaces, and for each extra level of nested ISR (interrupt service routine), you need an additional level of stack space for the stack frame as well as the ISR code. If MTB trace is available, use instruction trace to identify potential problems that can corrupt stack pointers between last know correct state and point of failure. Add a few function calls in various places in your program to check for stack leaks. CMSIS-CORE provides some functions to help accessing SP value (e.g., __get_MSP()), and you can use those functions to add stack checking code (e.g., in some part of the program, the value of Main Stack Pointer (MSP) should be the same everything when a function is called unless the function is used in more than one placesde.g. appears on multiple paths of the application’s call tree). If you are not using an RTOS, you can use the banked SP feature to separate the stack used by threads and handlers. In this way you can also add stack checking in the ISR with lowest priority level. Higher-priority level ISRs cannot use this trick because the SP value can be different if there was a lower-priority ISR running. If you are using an RTOS, some of the RTOS (including KeilÒ RTX) has optional stack checking feature.
If the SP is pointing to a valid location, then you should be able to extract some useful information from the stack frame. • • • •
With the stacked PC and the disassembled program image, you can often locate the instruction that triggered the HardFault. If the T bit in the stacked xPSR is 0, something is trying to switch the processor into ARMÒ state. If the T bit in the stacked xPSR is 0 and the stacked PC is pointing to the beginning of an ISR, check the vector table (all LSB of exception vectors should be set to 1). If the stacked IPSR (inside xPSR) is indicating an ISR is running, and the stacked PC is not inside the address range of the ISR code, then you likely to have a stack corruption in that ISR. Look out for data array accesses with unbounded index.
718 Appendix G If the stacked PC is pointing to a memory access instruction, usually you can debug the load/store issue based on the register contents (see Sections G.2.2eG.2.5). Other potential causes for HardFault are listed in Sections G.2.6eG.2.9.
G.2.2 Invalid Memory Access One of the most common problems is accidentally accessing to invalid memory region. Usually you can trace the faulting memory access instruction following the instructions in Chapter 11. Using the method described there you can locate the program code which caused the fault.
G.2.3 Unaligned Data Access If you directly manipulate a pointer, or if you have assembly code, you could generate code that attempts to carry out an unaligned access. If the faulting instruction is a memory access instruction, check if the address value used for the transfer is aligned or not.
G.2.4 Memory Access Permission (Cortex-M0+ Processor Only) The Cortex-M0+ processor has privileged and unprivileged execution states. Some of the components like Nested Vectored Interrupt Controller (NVIC) can only be accessed in privileged state (see Chapter 7, Section 7.8). Also, if the Memory Protection Unit is available, additional memory access permission rules can be set up. If there is a violation of the access permission, then the HardFault exception would be triggered.
G.2.5 Bus Slave Return Error Some peripherals might return an error response if it has not been initialized or if the clock to the peripheral is disabled. In some less common cases a peripheral might only be able to accept 32-bit transfers and return error responses for byte or half-word transfers.
G.2.6 Stack Corruption in Exception Handler If the program crashes after an interrupt handler execution, it might be a stack frame corruption problem. Since local variables can be stored on the stack memory, if a data array is defined inside an exception handler and the array index being used exceeds the array size, the stack frame of the exception could become corrupted. As a result the program could crash when exiting the exception.
Trouble Shooting 719
G.2.7 Program Crash at Some C Functions Please check if you have reserved sufficient stack space and heap space. By default, the heap space defined in some of the default startup code in KeilÒ MDK-ARM is 0 bytes. You will need to modify this if you are using some of the C functions like malloc, etc. Another possible reason for this problem is an incorrect C library function being pulled in by the linker. The linker can normally output verbosely to show the user what library functions were pulled in, which is something a user should check under such circumstances.
G.2.8 Accidental Trying to Switch to ARM State After a HardFault is entered, if the T bit in the stacked xPSR is 0, the fault was triggered by switching to ARM state. This can be caused by reasons such as an invalid function pointer value, LSB of vector in vector table not being set to 1, corruption of the stack frame during exception, or even an incorrect linker setting which ends up causing an incorrect C library being used.
G.2.9 SVC Executed at Incorrect Priority Level If the SVC (SuperVisor Call) instruction is executed inside an SVCall exception handler, or any other exception handlers that have same or higher priority than the SVCall exception, it will trigger a fault. If an SVC instruction is used in a Non-Maskable Interrupt handler or the HardFault handler, it will result in a lock up.
G.3 Sleep Problems G.3.1 Execution of WFE Does Not Enter Sleep Execution of a Wait-for-Event (WFE) instruction does not always result in entering of sleep mode. If a past event has occurred, the internal event latch inside the CortexÒ-M processor will be set. In this situation, execution of a WFE instruction will clear the event latch and continue to the next instruction. Therefore a WFE instruction is usually used in a conditional idle loop so that it can be executed again if sleep did not occur in the first WFE execution.
G.3.2 Sleep-on-Exit Triggers Sleep Too Early If you enable the Sleep-on-Exit feature too early during the initialization stage of a program, the processor will enter sleep mode as soon as the first exception handler is completed.
720 Appendix G
G.3.3 SEVONPEND Does Not Work for Interrupt Which Is Already in Pending State The SEVONPEND (Send Event on Pending) feature generates an event when an idle interrupt changes into pending state if the feature is enabled. The event can be used to wake up the Cortex-M processor if it has been entering sleep mode by a WFE instruction. However, if the pending status of the interrupt was already set to 1 before entering sleep, a new interrupt request arrive during sleep will not trigger an event. In this case the CortexM processor will not be woken up.
G.3.4 Processor Cannot Wake Up because Sleep Mode Might Disable Some Clocks Depending on the microcontroller you are using and the chosen sleep mode, the peripherals or the processor clock might be stopped and you might not be able to wake up the processor unless some special wake-up signal is used. Please refer to documentation from microcontroller vendors for details.
G.3.5 Race Condition Sometimes we need to pass software flags from interrupt handlers to thread level codes. However, the following code has a race condition: volatile int
irq_flag=0;
while (1){ if (irq_flag==0) { __WFI(); // enter sleep } else { process_a(); // Execute if IRQ_Handler had executed } } void IRQ_Handler(void){ irq_flag=1; return; }
If the IRQ takes place after the “irq_flag” checking and before the Wait-for-Interrupt (WFI), the process will enter sleep and will not execute “process_a()”. To solve this problem, the WFE instruction should be used. The execution of IRQ_Handler causes the internal event latch to set. As a result, the next execution of WFE will only cause the event latch to be cleared and will not enter sleep.
Trouble Shooting 721 If a microcontroller with Cortex-M3 r2p0 or earlier versions is used for the same operation, an __SEV() instruction needed to be included inside the “IRQ_Handler”. This is due to errata in the processor design that the event latch is not set correctly in interrupt event. Therefore the code should be changed as follows: volatile
int irq_flag=0;
while (1){ if (irq_flag==0) { __WFE(); // enter sleep if event latch is 0 } else { process_a(); // Execute if IRQ_Handler had executed } }
void IRQ_Handler(void){ irq_flag=1; __SEV(); // required for Cortex-M3 r2p0 or earlier versions return; }
G.4 Interrupt Problem G.4.1 Extra Interrupt Handler Executed In some microcontrollers, the peripherals are connected to a peripheral bus running at a different speed from the processor system bus, and the data transfer through the bus bridge might have a delay (depending on the design of the bus bridge). If the interrupt request of the peripheral is cleared at the end of an ISR and the exception is exited immediately, the interrupt signal connected to the processor might still be high when the exception exit takes place. This results in another execution of the same exception handler. To solve the problem, you can clear the interrupt request earlier in the ISR, or add an extra access to the peripheral after clearing the interrupt request. In most cases these arrangements can solve this problem.
G.4.2 Additional SysTick Handler Execution If you set up the SysTick timer for a single shot arrangement with a short delay, a second SysTick interrupt event could be generated during the execution of the SysTick handler. In such case, besides from disabling the SysTick interrupt generation, you should also clear the SysTick interrupt pending status before exiting the SysTick handler. Otherwise the SysTick handler will be entered again.
722 Appendix G
G.4.3 Disabling of Interrupt within Interrupt Handler If you are porting application code from an ARM7TDMIÔ microcontroller, you might need to update some interrupt handlers if they disable the interrupts during interrupt handling to ensure that the interrupts are re-enabled before exception exit. In ARM7TDMI re-enabling of interrupts can be done at the same time as exception return because the I-bit in CPSR is restored during the process. In the Cortex-M processors, re-enabling of interrupt (clearing of PRIMASK) has to be done separately.
G.4.4 Incorrect Interrupt Return Instructions If you are porting software from ARM7TDMI, make sure that all interrupt handlers are updated to remove wrapper code (needed for nested interrupt support in ARM7), and make sure the correct instruction is used for exception return. In the Cortex-M0 or Cortex-M0+ processor, exception return must be carried out using BX or POP instructions.
G.4.5 Exception Priority Setup Values Although the Exception/Interrupt priority level registers contains 8 bit for the priority level of each exception or interrupt, only the top 2 bits are implemented. As a result, the priority level values can only be 0x00, 0x40, 0x80, and 0xC0. If you are using NVIC functions from CMSIS compliant device driver libraries, the priority setup function “NVIC_SetPriority()” automatically shift the values 0e3 to the implemented bits.
G.5 Other Issues G.5.1 Incorrect SVC Parameter Passing Method Unlike traditional ARMÒ processors, the parameters pass on to the SVC exception and the return value from SVC handler must be transferred using exception stack frame. Otherwise the parameter could get corrupted. Please refer to Chapter 10 (Section 10.7.1) for details.
G.5.2 Debug Connection Affect by I/O Setting, or Low-Power Modes If you change the I/O settings of pins that are used for a debug connection, you might be unable to debug your application or update the flash because the debug connection is affected by the I/O usage configuration changes. Similarly, low-power features might also disable debugger connections. In some microcontroller products there is a special boot mode that allows you to disable the execution of your program during boot up. Chapter 19 covered the recovery method you can use on NXP LPC111x.
Trouble Shooting 723
G.5.3 Debug Protocol Selection/Configuration Some CortexÒ-M0/M0+ microcontrollers use serial-wire debug protocol and some others use JTAG debug protocol. If incorrect debug protocol is selected in the configuration of a debug environment, the debugger will not be able to connect to the microcontrollers. In some combination of debug tools and development boards it is necessary to specify the use of System Reset Request (SYSRESETREQ) as debugger reset control method.
G.5.4 Using Event Output as Pulse I/O Some Cortex-M microcontrollers allow an I/O pin to be configured as event output. When the SEV instruction is executed, a single cycle pulse is generated from the processor and this can be useful for external latch control. When a sequence of multiple pulses is required, additional instructions need to be placed between the SEV instructions. Otherwise the pulses could be merged. For example, the following sequence might result in one pulse (of two clock cycles) or two pulses (of one cycle) depending on the memory wait state of the system: __SEV(); // First pulse __SEV(); // Second pulse, could be merged with first pulse By changing the code to: __SEV(); // First pulse __NOP(); // Gap between the two pulses. __SEV(); // Second pulse If the C compiler you use can optimize away NOPs, __ISB() could be used instead.
G.5.5 Device Specific Requirements in Vector Table or Code Placement In some cases (for example, the Freescale KL25Z128 device used in the Freescale Freedom board FRDM-KL25Z), addition flash protection configuration data also need to be placed in the memory right after the vector table. If you are creating your own vector table/startup code, be very careful of such situations.
G.6 Other Possible Pitfalls in Programming G.6.1 Interrupt Priority Levels When migrating a software project from a CortexÒ-M3/Cortex-M4 microcontroller product to a Cortex-M0/Cortex-M0+ product, the use of different interrupt priority levels
724 Appendix G needs to be handled carefully. The ARMv6-M architecture only supports four programmable priority levels and does not support subpriority, whereas ARMv7-M architecture supports minimum eight priority levels and priority grouping. As a result, the arrangement of the priority levels needs to be reviewed and modified if needed. For example, a project for a Cortex-M3 microcontroller device using CMSIS-CORE priority level control function might have the following: NVIC_SetPriority(TIMER0_IRQn, NVIC_SetPriority(UART0_IRQn,
0x4); 0x3);
// //
lower priority Higher priority
Assumed 3-bit of priority level register is implemented on the Cortex-M3 device, the CMSIS function automatically shifts the value by 5 bits and the priority level at hardware level becomes: • •
Timer 0: Level 0x80 UART 0: Level 0x60
If the C source code is ported to Cortex-M0 or Cortex-M0+ microcontroller without adjustment, the shifting of priority level will result in the removal of the MSB of the timer 0 priority level because the function shifts the values left by 6 bits: • •
Timer 0: Level 0x00 // Now timer 0 become highest priority UART 0: Level 0xC0
This ends up with the Timer interrupt having higher priority than the UART interrupt. So it is important to review the priority level carefully.
G.6.2 Stack Overflow When Using Both Main and Process Stacks A range of topics related to stack overflow and various techniques to detect stack errors are covered in Chapter 23, Section 23.3. In some applications, two stack regions are defined: one for the main stack and one for the process stack, and it is important to ensure that these stack regions do not overlap by accident. If it is possible for the two stack regions to overlap, such error can be quite hard to debug because it depends on the timing of interrupt events: •
•
If the process stack is above (higher address than) the main stack, the stack corruption occurs when interrupts/exception takes place when the thread code exceeds its allocated stack usage. If there is no interrupt/exception event at the time, there might not be any error. If the main stack is above (higher address than) the process stack, the stack corruption occurs when there is a worst case stack usage in the nested exception/interrupt combination, which could be very rare.
As a result, careful analysis of the stack requirements is essential for projects that require high reliability.
Trouble Shooting 725
G.6.3 Data Alignment Unlike ARMv7-M architecture, the ARMv6-M does not support unaligned data transfers. So some codes ported from Cortex-M3 or Cortex-M4 processors that utilized unaligned transfers will need modifications when being used of the Cortex-M0 or Cortex-M0+ processors. For example, a “packed” data structure could contain unaligned data. __packed struct foo { char a; short b; // Unaligned char c, d, e; int f; // Unaligned short g; }foo_var;
When compiling a code like this, the compiler is aware of the unalignment of the data, and can use multiple memory accesses to handle the unaligned data at a cost of slightly more instructions and longer execution time. However, if we assign a data pointer to an element inside the structure, effectively casting away __packed attribute, the result is unpredictable (except for character type which is always aligned) and can lead to a HardFault when the processor tries to access the data. int * x; int y; x = foo_var.f; // Pointer points to unaligned address y = x; // HardFault trigger when data is read
Another common misunderstanding is the starting address of character or short int arrays. For example, char short int
a[4]; // a 4 byte character array b[2]; // a 4 byte short integer array
Although the sizes of these arrays are 4 bytes, the starting addresses of these data are not necessary aligned to word boundary. So casting a 32-bit data point to these arrays could also result in HardFault exceptions.
726 Appendix G
G.6.4 Missing Volatile Keyword In addition to peripheral registers, data variables that are shared by thread code and exception handlers should be declared with volatile keywords.
G.6.5 Function Pointers In some cases, function pointers could contain hard coded address (e.g., when accessing features in firmware preloaded on the chip). In such cases, we need to ensure that the LSB of the function pointer address is set to 1 to indicate thumb state. Otherwise it implies an attempt to switch the Cortex-M processor into ARMÒ state, which is not supported.
G.6.6 Read-Modify-Write Sometimes we need to perform Read-Modify-Write sequence. For example, setting a bit in a GPIO output port could be written as follows: GPIOA->OD j¼ (1<< 6); // Set bit 6 of Output Data (OD) While it looks simple, we need to consider the corner case where an interrupt took place between reading and writing of the GPIO register. If an ISR could change another bit in the GPIO output, we could end up with a conflict and lost the data value change in the ISR as we write the data back. Most of the GPIO peripherals in microcontroller have additional features to allow individual bits to be updated without affecting others. If such feature is not available, we might need to disable all interrupts in the Read-Modify-Write sequence. But there is another potential pitfall we need to be careful of (see Section G.6.7).
G.6.7 Interrupt Disable The __enable_irq() and __disable_irq() functions allow us to enable and disable interrupts easily. However, consider the following function: void func_X(void) { . __disable_irq(); GPIOA->OD j= (1<< 6); // Set __enable_irq(); . }
bit 6
of Output Data (OD)
Trouble Shooting 727 The function works fine on its own. However, if we use this function in another function that also changes the PRIMASK register. void func_Y(void) { . __disable_irq(); . // time critical processing func_X(); . // more time critical processing __enable_irq(); . }
After the func_X is executed, the PRIMASK register is cleared, and therefore the second half of func_Y is running with interrupt enabled, which is unintentional. As a result, we should declare an additional function to help managing the PRIMASK: int enter_critical_region(void) { int old_primask; old_primask = __get_PRIMASK(); __disable_irq(); return (old_primask); }
With this additional helper function, we can rewrite the code as: void func_X(void) { int old_primask; . old_primask = enter_critical_region(); GPIOA->OD j= (1<< 6); // Set bit 6 of Output Data (OD) __set_PRIMASK(old_primask); . }
void func_Y(void) { int old_primask; . old_primask = enter_critical_region(); . // time critical processing Continued
728 Appendix G func_X(); . // more time critical processing __set_PRIMASK(old_primask); . }
G.6.8 SystemInit Function The SystemInit function is typically executed before the execution of C startup code. As a result, global and static variables are not initialized when SystemInit is executed. Also, data variables assigned in the SystemInit function would be lost when the C startup code initialize the memory.
G.6.9 Breakpoints and Inline Not exactly a programming issue (more a debug issue), but worth pointing out that compilers can inline a function in certain optimization level, and at the same time, leave a copy of the same function in the code image (unless it is specified as static __inline/ static inline). As a result, when you set a breakpoint in the function X which is called by a different function Y, the code in function Y could get executed without hitting the breakpoint. It is possible to disable inlining of functions using additional compilation options. For example, in ARM Compiler (applicable to KeilÒ MDK-ARMÔ and ARM DS-5Ô ), --no_inline and --no_autoinline command line options are available for this purpose. When a function is declared as static inline, the C compiler will not create a copy of the function. However, static inline function cannot be used when the function is referenced by another program file in the project.