EmbeddedRelated.com

Little Endian

Category: Architecture | Also known as: little-endian

Little-endian is a byte-ordering convention in which a multi-byte value is stored in memory with its least-significant byte (LSB) at the lowest address. For example, the 32-bit value 0x12345678 is stored as 0x78, 0x56, 0x34, 0x12 at consecutive addresses.

In practice

Little-endian is the native byte order of x86/x64 processors and of ARM Cortex-M cores (which default to little-endian, though ARM cores that support the BE8 or BE32 modes can be configured otherwise). It is also the native order of many other common embedded architectures, including RISC-V (typically little-endian by default), ESP32 (Xtensa LX6/LX7), and MSP430. When you read a multi-byte register value from a peripheral or external memory on these devices, the CPU handles byte reassembly transparently for native-width loads, so endianness is invisible in purely single-platform code.

Endianness becomes critical at the boundaries where raw bytes are exchanged between systems: serial protocols, network stacks, file formats, and external sensor interfaces. Many communication protocols define their own byte order; TCP/IP, for example, uses big-endian network byte order. Other protocols such as CAN and Modbus have their own field-ordering rules defined per specification. Code running on a little-endian MCU must explicitly swap bytes when serializing or deserializing data for protocols that differ from the native order. Missing a swap is a common source of subtle bugs, particularly in numeric fields that rarely take values large enough to make the byte reversal obvious during smoke testing. The blog post "Endianness and Serial Communication" covers this in more detail.

When writing peripheral drivers that use memory-mapped registers, endianness of the bus matters too. On most ARM Cortex-M SoCs, peripheral registers exposed via memory-mapped I/O are laid out little-endian, though the exact byte layout is defined by the SoC design rather than by AHB/APB alone. Casting a byte pointer to a wider type (e.g., casting a uint8_t* to uint32_t*) on a little-endian machine produces a value with the first byte in the least-significant position, but this behavior also depends on alignment and aliasing rules in C and should be treated carefully for portability.

Struct overlays and unions used to pack or unpack protocol frames are particularly endianness-sensitive. A union that maps a uint32_t over four uint8_t fields will produce different field-to-byte mappings on a little-endian versus big-endian machine. Portable protocol code should use explicit bit-shift and mask operations rather than relying on struct layout or union punning to guarantee correct byte ordering on any target.

Discussed on EmbeddedRelated

Frequently asked

Is ARM always little-endian?
Most ARM Cortex-M devices operate in little-endian mode, and endian configurability varies by specific core variant and SoC implementation. Full ARM Cortex-A and Cortex-R cores often support both endian modes selectable at boot (or in some cases at runtime). In practice, the overwhelming majority of ARM-based embedded designs run little-endian; check the specific chip's reference manual if you need to be certain.
How do I swap bytes for a big-endian protocol on a little-endian MCU?
For fixed-width types, use explicit shifts and masks: val16 = (raw << 8) | (raw >> 8). Many embedded toolchains and libraries provide bswap intrinsics (e.g., __REV on ARM Cortex-M, which compiles to a single REV instruction) or macros such as ntohs()/ntohl() from a network library. Avoid relying on union punning or pointer casts for byte-swapping, as these can invoke undefined behavior in C.
Does endianness affect individual bytes, chars, or bit-level values?
No. Endianness only describes how bytes are ordered in memory for multi-byte values. A single uint8_t has only one byte, so endianness does not apply. Bit ordering within a byte is a separate concept (sometimes informally called bit endianness, though the term is not standardized) defined by individual protocols and is not determined by the CPU's byte endianness.
Will endianness affect my code if I only ever target one platform?
For purely internal computation and data storage on a single platform, endianness is transparent -- the CPU loads and stores multi-byte values consistently and you never see the individual byte layout. Endianness only surfaces when you exchange raw byte streams with other devices, read binary file formats, inspect memory in a debugger, or interface with hardware registers that have a defined byte layout.
How can I detect endianness at runtime in C?
A common idiom is: uint16_t x = 1; return (*(uint8_t *)&x == 1) ? LITTLE_ENDIAN : BIG_ENDIAN. This distinguishes the two most common cases but does not account for mixed-endian or other unusual byte orders. In most embedded projects the target architecture is known at compile time, so a compile-time check using toolchain-defined macros (e.g., __BYTE_ORDER__, __LITTLE_ENDIAN__ on GCC/Clang, or vendor-supplied macros) is preferable and avoids the pointer cast. Runtime detection is more relevant in hosted or cross-platform library code.

Differentiators vs similar concepts

Little-endian is often contrasted with big-endian, where the most-significant byte is stored at the lowest address. A third, rarely encountered convention is mixed-endian (also called middle-endian or PDP-endian), used by some older architectures like the PDP-11, where 32-bit values were stored in 16-bit word-swapped order. Endianness describes byte ordering in memory and is distinct from bit ordering within a byte, which is a separate property defined at the protocol or hardware level.