Setting up the vector table properly is crucial for getting your Cortex-M1 application running correctly. The vector table tells the processor where to find the reset and interrupt handlers needed to respond to events. If the vector table is not configured properly, your program may crash or behave unpredictably. Here are some tips for debugging issues with the Cortex-M1 vector table configuration.
Verify the linker script
The main source of vector table configuration is in the linker script. Double check that the linker script defines the vector table region and sets the correct size. For Cortex-M1, the vector table needs to be at least 64 bytes to fit the mandatory exception vectors. Make sure the __vector_table symbol points to the start of this region.
Also verify that the reset handler symbol (Reset_Handler) is defined and points inside the vector table area. The processor will start executing code from the reset handler on boot, so it needs to be valid.
Use a vector table copy
Some toolchains copy the vector table contents to RAM on startup. This protects against accidental writes to flash that could overwrite the table. Make sure the copy operation is being done and the processor starts executing from the RAM copy address.
You can verify this by looking at the startup code and ensuring the copy loop is executed. Also check that the stack pointer gets initialized to an address above the RAM copy address.
Check alignment
The Cortex-M1 requires the vector table to be aligned on a 256 byte boundary. This alignment allows the processor to quickly compute the offset to each exception handler. If the linker script does not enforce alignment, the vector table start could be misaligned and certain exceptions would fail.
Examine the link map file and look at the start address of the vector table region. Make sure it is aligned to a 256 byte boundary as required by the Cortex-M1.
Match table size with handlers
The size of the vector table needs to match the number of handlers defined. Cortex-M1 requires at least 16 exception handlers. If the vector table is too small, the additional handlers will get truncated and cause a crash.
Count the number of exception handler functions defined in your code and make sure the vector table size matches. Having extra unused exception slots is fine, but the table must be large enough to fit all handlers.
Initialize unused handlers
Leaving exception handler slots empty in the vector table is asking for trouble. Any exceptions hitting those slots will fail or crash since no handler code is defined. Make sure to initialize all slots in the table, even for unused exceptions.
A simple handler that gets called for unhandled exceptions can just loop indefinitely. This is better than crashing if an unexpected exception occurs.
Check vector addresses
Verify that the individual exception handler addresses populated in the vector table match the associated function symbols. If the address stored does not point to the start of the handler function, that exception will jump to the wrong location.
Manually inspect the vector table definition in your linker script or assembly startup code. Make sure the address stored matches the label for each handler function.
Linker file issues
Problems with the linker script itself can lead to vector table errors. Using incorrect memory regions, alignment settings, or symbol mappings in the linker file would cause invalid configuration.
Carefully read over the memory and section definitions in the linker file. Double check for typos or incorrect mappings. Review the manufacturer’s documentation for the proper way to define vector tables.
Toolchain problems
Issues with the toolchain itself can sometimes manifest as vector table problems. If the compiler, assembler, or linker have bugs, it could throw off code generation and addresses.
Try building the same code with an alternate toolchain if available. For example, build with GCC instead of Keil MDK or vice versa. If the problem goes away, it points to a toolchain bug.
Stack collisions
If the stack and heap are defined incorrectly, they could collide with the vector table region. This would lead to very erratic issues and crashes.
Inspect the linker script and make sure the stack and heap are placed in valid memory regions that do not overlap the vector table. Having a guardian area between the stack and vector table can also prevent collisions.
Corrupted table contents
In some cases, the contents of the vector table itself could get corrupted at runtime after booting. This can happen if buggy code in the application accidentally writes over the table region.
Use watchpoints to monitor access to the vector table region. If you see writes that are unexpectedly modifying handler addresses, then trace back to find the offending code.
Enable debug exceptions
Debug exceptions like the Hard Fault handler give you visibility into crashes that would otherwise be opaque. Make sure to enable debug exceptions in your project configuration.
When the processor hits an issue like accessing an invalid address, the debug handlers let you halt execution and inspect the registers and call stack. This makes the root cause much easier to uncover.
Fault register reads
The Cortex-M1 fault registers provide key details about crashes. Make sure to have handlers read out and log values from registers like CFSR, HFSR, and MMFAR to get maximum visibility.
For example, the MMFAR register will contain the invalid memory address that triggered a fault, allowing you to pinpoint the line of code responsible.
Eliminate compiler optimization
Compiler optimizations can sometimes obfuscate code behavior and make debugging more difficult. Try disabling optimizations like dead code removal while debugging vector table problems.
With optimization disabled, the generated code will match more closely with your source files. This makes stepping through code and tracebacks more accurate.
Software breakpoints
Using software breakpoints is an easy way to pause execution and inspect code flow. Insert strategic breakpoints in your reset handler, fault handlers, and vector table copy routine.
When a breakpoint hits, you can get the call stack, local variables, and other insights into the issue using your debugger.
Examine handler arguments
The arguments passed to each exception handler can provide clues during debugging. For example, a handler getting passed the wrong exception number indicates issues with the vector table mapping.
Use debug print statements or breakpoints to record the arguments passed into each handler. Verify they match expected values for the handlers.
Check handler return addresses
On Cortex-M parts, the return address for an exception is typically set to point to the next instruction after the call. Make sure your handlers are returning properly.
A incorrect return could mean executing undefined code or looping back prematurely before the issue is resolved. Verify the link register is restored properly after handling.
Reproduce with interrupt injection
Being able to reproduce vector table issues is key for effective debugging. Use interrupt injection tools to simulate external interrupts and trigger exceptions.
This isolates code changes from the equation and lets you reliably induce the same exception over and over to corner bug causes.
Review compiler assembly output
Examining the assembly code generated by the compiler can provide low-level insights. You can verify that important symbols, addresses, and sections match expectations.
Make sure code related to the vector table like the copy loop and handlers aligns with the source code. Differences could indicate a compiler problem.
Triple check code changes
When vector table problems appear after code changes, carefully review every modification that was made. Don’t assume the issue is isolated.
Code changes may have impacted the toolchain configuration, memory layout, linker script, or other build process resulting in table corruption.
Use asserts liberally
Assert statements are great for validating assumptions during execution. Use them liberally to double check addresses, alignment, and handler arguments.
Any failed asserts precisely identify when and where an unexpected condition occurs. This points right to the source of vector table problems.
Conclusion
Configuring the vector table correctly is a crucial step in Cortex-M1 development. With these tips, developers can systematically track down issues related to vector table mapping, alignment, contents, and handler integration. Proper validation and debugging techniques will lead to robust behavior in response to exceptions and interrupts.