The Cortex-M0 is a 32-bit ARM processor designed for low-power embedded applications. It has a simplified architecture compared to higher performance Cortex-M processors, making it suitable for cost-sensitive and power-constrained devices. The Cortex-M0 contains 13 core registers that are fundamental to its operation.
Main Program Status Register
The Main Program Status Register (xPSR) contains information about the current processor state including the current instruction set state, overflow and carry flags, and exception number. This register can be read to check the status of the processor after an instruction executes. Some key bits in the xPSR register include:
- N, Z, C, V bits for negative, zero, carry, and overflow flags
- T bit indicates ARM or Thumb instruction set state
- ISR_NUMBER field to identify the interrupting exception number
The processor state like exception level and execution mode is derived from the IPSR and CONTROL registers along with xPSR.
Program Counter
The Program Counter (PC) contains the current program address. On reset, the PC is loaded with the reset vector address as specified in the vector table. It is updated during program execution as instructions are fetched. The PC can be read to determine the current position in the program. On taking an exception, the value of the PC at exception time is saved to the stack.
Link Register
The Link Register (LR) is used to store the return address when a function call occurs. On taking a exception, LR is used to store the address to return to after exception processing completes. LR gets updated automatically on a branch and link (BL) instruction or exception entry.
Stack Pointer
The Stack Pointer (SP) holds the address of the current top of stack. It is decremented when values are pushed onto the stack, and incremented when popped off. Stack operations like PUSH and POP manipulate SP. On exception entry, the current stack pointer is saved to the Link Register.
Control Register
The Control Register controls privilege level and stack access. Key fields include:
- PRIVILEGED_MODE – Indicates Thread or Handler mode
- SPSEL – Selects Main Stack Pointer or Process Stack Pointer
- nPRIV – Controls access to user or privileged stack
These fields determine the access level and stack pointer available to threads and exception handlers.
Fault Status Registers
The HardFault Status Register (HFSR) and Debug Fault Status Register (DFSR) provide information on the cause of faults and errors. On a fault or exception entry, these registers get populated with status like the cause of the fault. Key fields include:
- FORCED – Indicates a forced hard fault
- VECTBL – Fault on vector table read
- DEBUGEVT – Fault triggered in debug state
These registers are used for debugging and identifying faults.
Exception Number Register
The Exception Number Register (IPSR) identifies the exception source number from 0 to 240. This indicates if the exception is from the processor (like a data abort), or an external interrupt number. The IPSR allows the exception handler to identify the source and handle appropriately.
Control and State Registers
The CONTROL and Configurable Feature State registers control processor specific features like sleep on exit modes, stack alignment checks, and managing wake up interrupts. These provide implementation specific configuration options.
Memory Access Registers
The Memory Access Register (MPU_TYPE) indicates MPU support and regions if memory protection is implemented. The Memory Fault Address Register (MFAR) captures the faulting address on memory faults. And the BusFault Address Register (BFAR) captures faulting addresses for bus faults. These registers are used for debugging memory access faults.
Debug Halting Control and Status
The Debug Halting Control and Status Register (DHCSR) contains debug control bits like halt requests, step enables, and debug key access. The Debug Fault Status Register (DFSR) holds debug fault and watchpoint information. These registers integrate debugging support into the core.
Software Trigger Exception Register
The Software Trigger Interrupt Register (STIR) is written to generate processor exceptions and test exception handling. This provides a mechanism to inject exceptions and test exception processing.
In summary, the Cortex-M0 contains a set of core registers to manage the program execution state, track processor exceptions, configure features, and integrate debugging capabilities. These registers control the fundamental operations of the processor.
Applications
The Cortex-M0 core registers are utilized in various ways:
- The program counter and link register enable function calls and branches.
- The stack pointer implements a stack for local variables and passing parameters.
- Status registers track arithmetic flags and the state during exceptions.
- Exception number and fault status registers assist in debugging crashes.
- Control registers configure options like stack checking and sleep.
- Memory fault registers identify illegal memory accesses.
- Debug registers support single stepping and breakpoints.
- Exception injection with the software trigger tests exception handling code.
Here are some examples of how the core registers are used:
Function Calls
When calling a function like add() the link register gets loaded with the return address. The program counter gets updated to the function address. The stack pointer manages pushing parameters and allocating local variables. Registers make structured code possible.
Interrupts
On receiving an interrupt, the program counter and link register are automatically pushed to the stack. The IPSR indicates the interrupt number. The program counter gets updated to the interrupt handler. Stack pointer allocates handler context.
Exceptions
If an instruction faults, the fault status registers indicate the cause, like an invalid memory access. The fault address registers pinpoint the location. The stack saves context for diagnosing crashes.
Debugging
Debug registers like DHCSR enable stepping through code. Watchpoint registers track data accesses. Breakpoints are set by storing instruction addresses. Registers integrated debugging before debug hardware.
The versatile Cortex-M0 registers enable robust code development, reliable interrupt handling, and detailed diagnostics.
Initializing Core Registers
Here is sample code to initialize core registers on startup: void initialize_registers() { // Enable fault exceptions SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk; // Set control register to unprivileged mode CONTROL.nPRIV = 1; // Main stack pointer uses MSP CONTROL.SPSEL = 1; // Set vector table offset SCB->VTOR = 0x8000; // Initial program counter PC = resetHandler; // Initial stack pointer MSP = 0x2000; // Enable interrupts __enable_irq(); }
Key steps include:
- Enabling exceptions like usage faults in system control block.
- Configuring the control register for thread mode and main stack pointer.
- Setting the vector table offset for the interrupt vectors.
- Loading the program counter and main stack pointer.
- Enabling interrupts globally.
This initializes the Cortex-M0 in preparation for running C code and interrupt handlers. The core registers establish the foundation for application development.
Configuring MemoryRegions
Here is example code for setting up two memory regions for flash and RAM access: void configure_mpu() { // Setup region for flash MPU->RBAR = 0x00000000; MPU->RASR = MPU_RASR_ENABLE | MPU_RASR_SIZE_128KB | MPU_RASR_AP_READ; // Setup region for SRAM MPU->RBAR = 0x20000000; MPU->RASR = MPU_RASR_ENABLE | MPU_RASR_SIZE_16KB | MPU_RASR_AP_FULL; // Enable MPU MPU->CTRL = MPU_CTRL_ENABLE; }
This demonstrates using the MPU memory protection unit registers to:
- Configure a memory region for flash with read-only access.
- Configure a memory region for SRAM with read-write access.
- Enable the MPU to enforce memory permissions.
The MPU registers allow setting up protected memory regions for code and data.
Debugging Crashes
If a crash occurs, the fault status registers provide debugging information: void debug_fault(void) { // Check HFSR for forced hard fault if(SCB->HFSR & SCB_HFSR_FORCED_Msk) { puts(“Forced hard fault”); } // Check MFAR for memory manage fault address if(SCB->MFAR) { puts(“Memory fault address:”); printf(“0x%X\n”, SCB->MFAR); } // Check BFAR for bus fault address if(SCB->BFAR) { puts(“Bus fault address:”); printf(“0x%X\n”, SCB->BFAR); } }
This shows how to use the fault registers to detect issues like:
- Hard fault triggered by API call or assertion.
- Memory protection violation on illegal address.
- Bus error on bad memory access.
The fault registers provide clues to diagnose crashes without a debugger.
Conclusion
In summary, the Cortex-M0 core registers provide the foundation for embedded applications built with the processor. Key capabilities include:
- Program execution with program counter and link register.
- Stacks for local variables and context saving.
- Status flags tracking arithmetic operations.
- Interrupt and exception handling.
- Configuration options for stacks, exceptions, and debugging.
- Memory fault diagnostics.
- Debugging with breakpoints, watchpoints, and halting.
The registers control all aspects of program flow, exceptions, configuration, and debugging. Understanding the core registers is critical for those working with the Cortex-M0 processor in embedded systems.