Table of Contents
KEY TAKEAWAYS
- Big-endian stores the most significant byte at the lowest address; little-endian stores the least significant byte first
- Endianness matters when transferring data between systems or reading multi-byte hardware registers
- Network byte order is big-endian — use
htons()/ntohs()for portable network code - ARM Cortex processors support both little-endian and bi-endian configurations, with little-endian as default
The endianness refers to the byte order used by your computer or microcontroller or a machine to read or write a single “machine word” in memory (32-bit machine’s word size is 32-bit and 64-bit machine’s word size is 64-bit ). In other words, The endian will decide how to stores multiple bytes in computer memory. It doesn’t mean, the order of bits inside a byte, nor the way the computer reads an array of bytes or a file. It’s all about the order of the bytes of a word (multi-byte variable) in memory. Now we will move into Little Endian and Big Endian.
Little Endian And Big Endian
Little Endian
In little endian byte order, the least significant byte (LSB) is stored at the lowest memory address, and the most significant byte (MSB) is stored at the highest memory address. Intel x86, Pentium, and ARM Cortex processors are typically configured to use this Little endian format by default.
Thus, the little-endian byte order means, when the computer writes a word (Multi Byte) into memory, it begins by writing the Lowest byte to the lowest memory address and continues until it has written the highest byte to the highest memory address. It does this by writing subsequent and ascending memory addresses, no matter the endianness.
Let’s consider a 4-byte (32-bit) integer with the value 0x12345678:

Big Endian
In big endian byte order, the most significant byte (MSB) is stored at the lowest memory address, and the least significant byte (LSB) is stored at the highest memory address. This is also known as network byte order, because it is the format used in internet protocols like TCP/IP.
The big endian byte order means, when the computer writes a word (Multi Byte) into memory, it begins by writing the highest byte to the lowest memory address and continues until it has written the lowest byte to the highest memory address.
Using the same example of 4 bytes integer which we considered earlier – 0x12345678:

Now we will see the example, then you will understand very easily.
ARM Cortex Endianness Configuration
ARM Cortex processors offer flexible endianness support, which is particularly important in embedded systems where data compatibility across different architectures is crucial. Understanding ARM Cortex endianness configuration helps developers optimize their embedded applications for specific hardware requirements.
ARM Cortex-M Endianness
ARM Cortex-M processors are designed with a fixed little-endian configuration. This means that all Cortex-M series processors (Cortex-M0, M0+, M3, M4, M7, M23, M33, etc.) operate exclusively in little-endian mode. This design choice simplifies the architecture and reduces complexity for embedded applications where consistent byte ordering is preferred.
Key characteristics of Cortex-M endianness:
- Fixed Configuration: Cannot be changed at runtime or boot time
- Memory Access: All memory accesses follow little-endian byte ordering
- Register Access: Peripheral registers are accessed in little-endian format
- Stack Operations: Stack push/pop operations maintain little-endian consistency
ARM Cortex-A Bi-Endian Mode
ARM Cortex-A processors support bi-endian operation, meaning they can be configured to operate in either big-endian or little-endian mode. This flexibility allows the same processor to work with different operating systems and legacy applications that may require specific endianness.
Cortex-A endianness features:
- Boot-time Configuration: Endianness is typically set during system boot
- CPSR Control: The E bit in the Current Program Status Register controls endianness for data accesses
- Instruction Endianness: Instructions are typically fetched in little-endian format regardless of data endianness
- Mixed Mode Support: Some implementations support different endianness for instructions vs. data
AMBA Bus Endianness Handling
The Advanced Microcontroller Bus Architecture (AMBA) used in ARM systems handles endianness at the bus level to ensure proper data transfer between different components. Understanding AMBA bus endianness is crucial for system-level design and debugging.
AMBA endianness considerations:
- AHB/AXI Buses: Support byte lane steering to handle endianness conversion automatically
- Peripheral Integration: Bus bridges can perform endianness conversion when connecting big-endian and little-endian components
- DMA Operations: Direct Memory Access controllers must account for endianness when transferring data between memory and peripherals
- Byte Enable Signals: Used to indicate which bytes in a multi-byte transfer are valid, helping with endianness handling
Importance of understanding Endianness:
It is crucial to understand endianness when working with different hardware architectures or when transferring data between systems. Incompatible endianness can cause data corruption or incorrect results. Most modern computer systems use either big or little endian, with some even supporting both through configurable settings.
In ARM-based embedded systems, endianness becomes particularly important when:
- Interfacing with External Devices: Sensors, displays, or communication modules may use different endianness
- Network Communications: Network protocols typically use big-endian (network byte order)
- File Format Compatibility: Binary file formats may specify particular endianness requirements
- Cross-Platform Development: Code that runs on both ARM and x86 systems must handle endianness correctly
Example
Consider the number 0x11223344. This number is written with hexadecimal digits (prefix “0x“). Its decimal value is 287454020. Its consists of 4 bytes: 0x11, 0x22, 0x33 and 0x44.
In this value LSB is 0x44 and MSB is 0x11.
Now assume that the computer wants to write this number into memory beginning at address 100. This 4-byte value. So it will use the memory address 100, 101, 1o2, 103.
Programs:
Program to see memory arrangement:
Now we are going to write a program to see the memory arrangement on our computer. This program will print the memory address and content of our value.
endian_test.c
#include <stdio.h>
int main(void)
{
unsigned int value = 0x12345678;
char *r = (char *) &value;
int i;
for(i=0; i<4; i++)
{
printf("Address of 0x%x = %d \n", r[i], &r[i]);
}
return 0;
}Build this code using the following command:
gcc -o endian_test endian_test.c
Now, running the executable which is generated will give us the following output:

Here, the MSB is at the higher memory location. Thus, this system is a Little Endian system.
Program to test Endianness of a system:
#include <stdio.h>
int main(void)
{
unsigned int value = 0x1;
char *byte = (char *) &value;
if (*byte == 1)
printf("Your system is Little Endiann");
else
printf("Your system is Big Endiann");
return 0;
}This program declares an integer variable num with a value of 1 and creates a character pointer byte that points to the memory address of num. If the system is little endian, the LSB will be stored at the lowest memory address, and the value at byte will be 1. If the system is big endian, the MSB will be stored at the lowest memory address, and the value at byte will be 0. The program then checks the value at the byte pointer and prints the endianness accordingly.
Program to Test Endianness using Unions:
We can use Unions in C to test the Endianness of a system:
#include <stdio.h>
union check_endian
{
unsigned int value;
char r;
};
union check_endian endian;
int main(void)
{
endian.value = 0x1;
if (endian.r == 1)
printf("The system is Little Endiann");
else
printf("The system is Big Endiann");
return 0;
}How Endianness Affects Multi-Byte Data Storage
Endianness determines the order in which bytes of a multi-byte value are stored in memory. This fundamental concept affects how data structures, arrays, and complex data types are organized in computer memory, making it essential for embedded systems programming.
Consider how different data types are affected by endianness:
16-bit Integers (Short)
For a 16-bit value 0x1234:
- Little Endian: Address 0x1000: 0x34, Address 0x1001: 0x12
- Big Endian: Address 0x1000: 0x12, Address 0x1001: 0x34
32-bit Floating Point
IEEE 754 floating-point numbers are also affected by endianness. A 32-bit float consists of sign bit, exponent, and mantissa, all stored according to the system’s endianness.
Structure Padding and Alignment
When structures contain multi-byte members, endianness affects how individual members are stored, though structure member ordering remains unchanged:
struct data_packet {
uint8_t header; // 1 byte - endianness doesn't apply
uint16_t length; // 2 bytes - affected by endianness
uint32_t timestamp; // 4 bytes - affected by endianness
uint8_t checksum; // 1 byte - endianness doesn't apply
};Detecting Endianness at Runtime
You can detect the endianness of your system at runtime using a union or pointer cast. This is particularly useful when writing portable code that needs to work across different ARM architectures or when interfacing with external devices that may have different endianness requirements.
Here’s a more comprehensive runtime detection example:
#include <stdio.h>
#include <stdint.h>
typedef enum {
ENDIAN_LITTLE,
ENDIAN_BIG,
ENDIAN_UNKNOWN
} endian_type_t;
endian_type_t detect_endianness(void) {
union {
uint32_t value;
uint8_t bytes[4];
} test;
test.value = 0x01020304;
if (test.bytes[0] == 0x04) {
return ENDIAN_LITTLE;
} else if (test.bytes[0] == 0x01) {
return ENDIAN_BIG;
} else {
return ENDIAN_UNKNOWN;
}
}
int main(void) {
endian_type_t endian = detect_endianness();
switch(endian) {
case ENDIAN_LITTLE:
printf("System is Little Endian\n");
break;
case ENDIAN_BIG:
printf("System is Big Endian\n");
break;
default:
printf("Unknown endianness\n");
break;
}
return 0;
}Byte Swapping Functions for Endianness Conversion
When communicating between systems with different endianness (or with network protocols), you must convert byte order. This is especially important in ARM-based embedded systems that communicate with network devices or other processors with different endianness.
Here are optimized byte swapping functions for different data sizes:
#include <stdint.h>
// 16-bit byte swap
uint16_t swap_uint16(uint16_t val) {
return (val << 8) | (val >> 8);
}
// 32-bit byte swap
uint32_t swap_uint32(uint32_t val) {
val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
return (val << 16) | (val >> 16);
}
// 64-bit byte swap
uint64_t swap_uint64(uint64_t val) {
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
return (val << 32) | (val >> 32);
}
// Conditional swap based on system endianness
uint32_t host_to_big_endian_32(uint32_t val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return swap_uint32(val);
#else
return val;
#endif
}Many systems also provide built-in functions like __builtin_bswap16(), __builtin_bswap32(), and __builtin_bswap64() that are optimized for the target architecture.
Endianness Problems in Embedded Communication Protocols
Endianness bugs are among the most subtle in embedded systems. They often manifest as incorrect data interpretation rather than obvious crashes, making them particularly challenging to debug in ARM-based embedded applications.
Common Protocol Issues
SPI Communication: When ARM controllers communicate with sensors or external chips via SPI, multi-byte data transfers must account for endianness differences:
// Reading a 16-bit temperature value from sensor
uint16_t read_temperature_sensor(void) {
uint8_t high_byte = spi_read_byte();
uint8_t low_byte = spi_read_byte();
// Sensor sends data in big-endian format
// ARM processor expects little-endian
return (high_byte << 8) | low_byte; // Correct conversion
}I2C Multi-byte Registers: Many I2C devices store 16-bit or 32-bit values in specific endianness:
// Reading 16-bit register from I2C device
uint16_t read_i2c_register_16(uint8_t reg_addr) {
uint8_t buffer[2];
i2c_read_bytes(DEVICE_ADDR, reg_addr, buffer, 2);
// Check device datasheet for endianness
// This example assumes big-endian device register
return (buffer[0] << 8) | buffer[1];
}Network Protocol Handling
Network protocols universally use big-endian byte order (network byte order). ARM systems must convert between host byte order and network byte order:
#include <arpa/inet.h> // For htons, ntohs, htonl, ntohl
void send_network_packet(uint16_t port, uint32_t address) {
// Convert host byte order to network byte order
uint16_t net_port = htons(port); // Host to Network Short
uint32_t net_addr = htonl(address); // Host to Network Long
// Send over network...
}
void receive_network_packet(uint8_t *buffer) {
uint16_t net_port = *(uint16_t*)&buffer[0];
uint32_t net_addr = *(uint32_t*)&buffer[2];
// Convert network byte order to host byte order
uint16_t host_port = ntohs(net_port); // Network to Host Short
uint32_t host_addr = ntohl(net_addr); // Network to Host Long
}Endianness and Type Punning: Avoiding Undefined Behavior
A common mistake is using pointer casts to reinterpret bytes, which causes undefined behavior due to strict aliasing rules. Modern compilers may optimize code in unexpected ways when these rules are violated.
Problematic Code (Undefined Behavior)
// WRONG: Violates strict aliasing rules
uint32_t value = 0x12345678;
uint8_t *bytes = (uint8_t*)&value; // Undefined behavior
printf("First byte: 0x%02X\n", bytes[0]);Correct Approaches
Using Unions (Recommended):
union safe_cast {
uint32_t as_uint32;
uint8_t as_bytes[4];
};
void safe_byte_access(void) {
union safe_cast data;
data.as_uint32 = 0x12345678;
printf("First byte: 0x%02X\n", data.as_bytes[0]); // Safe
}Using memcpy():
#include <string.h>
void safe_memcpy_access(void) {
uint32_t value = 0x12345678;
uint8_t bytes[4];
memcpy(bytes, &value, sizeof(value)); // Safe
printf("First byte: 0x%02X\n", bytes[0]);
}Using Bit Operations:
void extract_bytes_safely(uint32_t value) {
uint8_t byte0 = (value >> 0) & 0xFF; // Portable
uint8_t byte1 = (value >> 8) & 0xFF;
uint8_t byte2 = (value >> 16) & 0xFF;
uint8_t byte3 = (value >> 24) & 0xFF;
printf("Bytes: %02X %02X %02X %02X\n", byte0, byte1, byte2, byte3);
}You Might also Like
Related on this site
- Byte order matters most when reading and writing multi-byte values to specific memory addresses — see introduction to memory mapping for the broader layout context.
- Structure layout interacts with endianness in subtle ways — see memory layout of a structure for padding, alignment, and field ordering.
- Byte-swapping (htonl/ntohl style) is built from shifts and masks — see bitwise operators in C for the building blocks.

Vivek Bhageria — Lead Firmware R&D Engineer, 12+ years. Ex-Bosch (automotive powertrain), MusicTribe (real-time audio), medical devices. M.Tech BITS Pilani. I write at NerdyElectronics — practical, register-level embedded systems for engineers who want to understand what’s actually happening under the hood.






