The ARM Cortex-M1 processor provides a simple and efficient way to access peripheral registers through memory mapped I/O. This allows developers to read and write to peripherals as if they were normal memory locations. The key advantage of this approach is that standard load and store instructions can be used without any special peripheral access instructions.
Memory Map
The Cortex-M1 memory map reserves regions of the processor’s address space for peripherals. Each peripheral is assigned a block of addresses, with each address corresponding to a register in that peripheral. For example, a UART peripheral may have a base address of 0x4000_0000. Writing to 0x4000_0000 may map to the UART’s data register, 0x4000_0004 to the baud rate register, and so on.
The memory mapped registers can be accessed through normal load and store instructions like LDR, STR, LDMB, STMB, etc. For example: LDR R0, =0x4000_0000 ; Load UART data register STR R1, [R0] ; Store value to UART data register
This removes the need for specialized instructions to talk to peripherals. From the programmer’s view, the peripherals look just like regular memory.
Accessing Memory Mapped Registers
Here are some ways peripheral registers can be accessed on the Cortex-M1 using standard ARM instructions:
- LDR – To load a register value into a processor register
- STR – To store a processor register value into a peripheral register
- LDM – To load multiple peripheral registers into processor registers
- STM – To store multiple processor registers into peripheral registers
For example: // Read UART status register into R0 LDR R0, =0x4000_0008 // Write R1 to UART data register STR R1, =0x4000_0000
Any instruction that does a load from memory can be used to read a peripheral register. Any instruction that does a store to memory can be used to write to a peripheral register.
Reserved Regions vs Overlays
The memory map on Cortex-M1 has regions reserved for core peripherals like the NVIC, SysTick, etc. There are also open regions that can be used for memory mapped peripherals.
If multiple peripherals need to be mapped, their address regions can overlay each other using a peripheral specific bus matrix. For example, three peripherals may all have a base address of 0x4000_0000 in their own view. The bus matrix maps this to different locations like 0x4000_0000, 0x4100_0000 and 0x4200_0000.
This allows each peripheral to behave as if it owns that memory region while actually being mapped to different locations.
Accessing Reserved Regions
While core peripherals like the NVIC and SysTick have reserved memory regions, they are not always accessible. For example, the NVIC registers may only be writable by privileged code, not user code.
Attempting to access a protected register from user code will typically cause a fault. The processor privilege level controls access to these sensitive regions. Some registers may be read-only or write-only as well.
Software has to check register documentation to understand if a peripheral register can actually be accessed from the current privilege level before attempting to use it.
Atomicity and Alignment
The ARM architecture provides some guarantees when accessing peripheral registers:
- Any access to a 32-bit peripheral register is guaranteed to be atomic. This ensures a consistent view of the register value even if interrupted in the middle of the access.
- Anyaccess to a 32-bit register is guaranteed to be properly aligned. The architecture handles any misaligned accesses.
For example, a multi-byte read or write to a 32-bit register cannot be interrupted or split across bus transactions. The access is guaranteed to occur atomically.
Synchronization and Coherency
As peripherals are separate from the core, there are some cases where synchronization is required:
- If the processor caches addresses, cache maintenance may be needed to get an up-to-date view of a peripheral register.
- Barriers like DSB may be needed to ensure register writes complete before further access.
- Peripheral registers are not kept coherent with core caches by default. Explicit handling may be needed if caches are enabled.
For example, if a register write enables a peripheral, a DSB may be needed to ensure the write completes before further peripheral access. The details vary based on the system configuration.
Common Peripherals
Some common peripherals that may be memory mapped include:
- UART – For serial communication
- GPIO – For general purpose I/O pin control
- Timers – For timing and PWM signal generation
- USB – For USB device/host communication
- I2C – For I2C communication bus
- SPI – For SPI communication bus
- ADC – For analog to digital conversion
The memory map provides each of these peripherals a region of addresses for their control/status registers. This gives a simple way to use them from software.
Typical Register Usage
A peripheral register memory map usually looks like this:
- Control registers – To enable peripherals, start operations, etc.
- Status registers – To get status and results of operations.
- Data registers – For data input and output.
- Configuration registers – To configure operating parameters.
For example, a UART peripheral may have a control register to enable Tx/Rx. It would have status registers to indicate transmission complete, errors, etc. Data registers would hold the next byte to transmit or the last received byte. Configuration registers would set baud rate, stop bits, etc.
Reading these various registers and writing to them is how the peripheral is used from software.
Initializing Peripherals
A typical sequence for initializing a peripheral is:
- Map peripheral address region
- Write configuration registers
- Clear status registers
- Write control register to enable peripheral
- Optionally wait for status indicating ready
For example, for a UART:
- Map UART base address
- Write baud rate, data bits, etc.
- Clear error status bits
- Write control register to enable UART
- Wait for status bit indicating UART is ready
After this initialization sequence, the peripheral is ready to be used for data transfer.
Interrupts
Many peripherals can generate interrupts to indicate events like data transfer completion, errors, etc. This allows efficient peripheral operation without constant polling.
To use interrupts, the peripheral registers are first mapped. Then the processor’s Nested Vectored Interrupt Controller (NVIC) is configured to respond to the peripheral’s interrupts.
The peripheral’s control registers are used to enable the conditions that should generate an interrupt. The NVIC registers are used to enable the interrupt handler.
Then in the interrupt handler, the peripheral’s status registers can be checked to determine the interrupt cause and handle it.
Summary
Memory mapped peripherals allow efficient processor access using standard ARM instructions like LDR and STR. Regions of the Cortex-M1 memory map are assigned to peripherals, with each address mapping to a control/status register.
Reserved regions hold core processor peripherals like NVIC and SysTick. Overlays allow multiple peripherals to have the same base address. Atomicity and alignment guarantees ensure consistent access.
Peripheral registers fall into common categories like control, status, data and configuration. A standard initialize sequence gets the peripheral ready for use. Interrupts allow efficient peripheral interaction.
Overall, memory mapped peripherals give Cortex-M1 processors a clean way to integrate I/O without always needing dedicated I/O instructions.