The ARM Cortex-M processors are designed to operate with little endian data by default. However, they can be configured to work with big endian data as well through changes to the register settings and data access methods. This allows flexibility in integrating Cortex-M cores into systems that need to work with mixed endianness data.
Overview of Endianness
Endianness refers to the order in which bytes are arranged in memory for larger data types like integers. In little endian format, the least significant byte is stored at the lowest memory address. In big endian format, the most significant byte is stored first. For example, the 32-bit hexadecimal value 0x12345678 would be stored in memory as:
- Little endian: 0x78 0x56 0x34 0x12
- Big endian: 0x12 0x34 0x56 0x78
When accessing larger data types, the order of accessing the individual bytes matters. Little endian systems access the bytes from least significant to most significant. Big endian systems access them most significant to least significant.
The Cortex-M processors use little endian by default. This matches the endianness used by most other ARM processors. However, in some applications like networking, big endian data formats are common. So Cortex-M needs to support non-native endian configurations.
Endianness Configuration in Cortex-M
The Cortex-M processors have several control registers that allow configuring the endianness. The main ones are:
- SCTLR.E bit – Enables big endian memory accesses for data loads/stores. Does not affect instruction fetches.
- ACTLR.E bit – Enables big endian access for instruction fetches. Used along with SCTLR.E to enable full big endian operation.
- CPACR.cp10 – Enables big endian access for floating point and NEON instructions.
Setting these register bits allows the processor to perform accesses in big endian mode instead of the default little endian. The processor handles endianness conversion during load and store operations to present the correct byte order to software.
Data Access Endianness
The SCTLR.E bit controls the endianness for general data accesses like explicit memory load/store instructions. When set to 1, it configures the processor to perform the following actions:
- Convert byte order during load operations – Fetch big endian data from memory and convert it to little endian format for the core
- Convert byte order during store operations – Take data in little endian format and convert it to big endian for storing to memory
This allows data to be stored in big endian format in the main memory while the processor core still sees it in little endian format natively. The conversion happens automatically in the background during the load/store. So software works unchanged.
Instruction Fetch Endianness
In addition to data accesses, instruction fetches also need to be handled correctly. The ACTLR.E bit controls endianess during instruction fetch:
- ACTLR.E = 0 – Fetches code in little endian format
- ACTLR.E = 1 – Fetches code in big endian format
ACTLR.E needs to be set along with SCTLR.E to enable complete big endian operation for both code and data.
Floating Point/NEON Endianness
The CPACR.cp10 register field controls the endianness for floating point and NEON instructions. This is needed because floating point values like double precision floats have their own big vs little endian formats.
When CPACR.cp10 is set, the processor will use big endian format for floating point registers and data during relevant register load/store/math operations.
Performing Byte Swapping
In non-native endian mode, explicit byte swapping is not needed for standard loads/stores. But for operations like memory copy, the processor needs to perform byte swapping to correctly handle the endianness difference between source and destination memory regions.
The Cortex-M processors provide some instructions to accelerate byte swapping operations:
- REV – Reverse byte order in a 32-bit register
- REV16 – Reverse byte order in each 16-bit halfword of a 32-bit register
- REVSH – Reverse byte order in a 16-bit register, with sign extension
Using these along with regular data processing instructions, efficient byte-swapped copies can be implemented for different endianness source and destination.
Interacting with External Busses
When interfacing the Cortex-M processor with external busses, additional endian conversion may be required if the bus endianness differs from the configured processor endianness.
Some Cortex-M based microcontrollers include integrated endian swap units to bridge between external buses using different endianness. For example, the Cortex-M7 based LPC54S018 MCU from NXP includes an endian swap unit for its external bus interface.
The endian swap unit can be enabled on the bus interface using control registers. When enabled, it will automatically convert between little and big endian during DMA transfers between the SoC and external memory. This allows the microcontroller to seamlessly interact with either endian external memories and peripherals.
From software’s perspective, the processor appears to be using its native endianness format regardless of the configured endianness. The byte swapping occurs invisibly during load/store. Hence code doesn’t need any special handling for endianness itself.
However, software should still be written with endianness in mind for portability and interoperability. Some guidelines include:
- Use endian-neutral data types like char, int32_t instead of short, long
- Avoid unsafe type punning like aliasing between int32_t and float
- Use endian conversion utilities when explicitly handling byte order
- Use bitfield structs instead of byte array unions to overlay data
- Use fixed-size intrinsics instead of base ISA for 16-bit and 64-bit operations
Following these practices ensures that software works correctly independent of the target endianness configuration.
Initializing Non-Native Endianness
To configure the Cortex-M processor to operate in non-native big endian mode, the SCTLR, ACTLR, and CPACR registers need to initialized early during application startup even before main(). For example: .section .init, “ax” ENABLE_BIG_ENDIAN: // Set SCTLR.E bit for big endian memory accesses ldr r1, =0x00001000 ldr r0, =SCTLR ldr r2, [r0] orr r2, r2, r1 str r2, [r0] // Set ACTLR.E bit for big endian instruction accesses ldr r1, =0x00001000 ldr r0, =ACTLR ldr r2, [r0] orr r2, r2, r1 str r2, [r0] // Set CPACR.cp10 for floating point big endian ldr r1, =0x0000F000 ldr r0, =CPACR ldr r2, [r0] orr r2, r2, r1 str r2, [r0] // Complete initialization bx lr
This early initialization code needs to execute before any other code including constructors that may perform data or code accesses. The linker script can place it in the .init section which contains initialization functions that run before main().
The Cortex-M processors support flexible endianness configurations through controls like SCTLR.E, ACTLR.E, and CPACR.cp10. Using these registers, the processor can be configured for either native little endian or non-native big endian operation.
In big endian mode, the processor handles conversion for both code and data accesses. Additional instructions like REV provide accelerated byte swapping. External bus interfaces may also include endian conversion hardware.
From software’s view, the configured endianness works transparently. Code just needs to follow good practices on type punning and byte order. The endian configuration is initialized early before main. This allows Cortex-M based systems to efficiently integrate and exchange data with either endian external components.