Binary Data in Embedded Systems
Embedded systems operate at the boundary between software and hardware, where every bit matters. Microcontrollers communicate with sensors, actuators, and communication peripherals through registers — memory-mapped addresses where individual bits control hardware behavior. Understanding binary representation is not optional in this domain; it is the language the hardware speaks.
Register Manipulation with Bitwise Operations
Hardware registers pack multiple fields into a single byte or word. Setting, clearing, or toggling individual bits without disturbing others requires bitwise operations.
Setting a Bit (OR)
To enable a peripheral, you set a specific bit to 1:
// Set bit 3 of register CTRL (enable interrupt)
CTRL |= (1 << 3);
// Binary: if CTRL was 01010010, result is 01011010
The OR operation leaves all other bits unchanged because x | 0 = x.
Clearing a Bit (AND with NOT)
To disable a feature, you clear a bit to 0:
// Clear bit 5 of register MODE (disable watchdog)
MODE &= ~(1 << 5);
// ~(1 << 5) = 11011111
// AND with 11011111 forces bit 5 to 0, preserves everything else
Toggling a Bit (XOR)
To flip a bit — useful for blinking an LED — use XOR:
// Toggle bit 2 of PORTB (blink LED on pin 2)
PORTB ^= (1 << 2);
Reading a Bit (AND)
To check whether a flag is set:
// Check if bit 4 of STATUS is set (data ready)
if (STATUS & (1 << 4)) {
read_sensor_data();
}
Multi-Bit Fields
Many registers group adjacent bits into fields. For example, an ADC control register might use bits 2:0 to select a channel:
| Bits 2:0 | Channel |
|---|---|
| 000 | Channel 0 |
| 001 | Channel 1 |
| 010 | Channel 2 |
| 011 | Channel 3 |
To write a 3-bit value into this field without affecting other bits:
// Set channel to 5 (binary 101) in bits [2:0]
ADC_CTRL = (ADC_CTRL & ~0x07) | 0x05;
// 0x07 = 00000111 — mask for bits [2:0]
// ~0x07 = 11111000 — clears bits [2:0], preserves rest
// | 0x05 = sets bits [2:0] to 101
To extract the channel value:
uint8_t channel = ADC_CTRL & 0x07;
Endianness in Communication Protocols
Embedded systems frequently exchange binary data over serial buses (SPI, I2C, UART) and network interfaces (CAN, Ethernet). Byte order — endianness — determines how multi-byte values are laid out in memory and on the wire.
| Order | Layout of 0x1234 | Used By |
|---|---|---|
| Big-endian | 12 34 (MSB first) | Network protocols, CAN, Modbus |
| Little-endian | 34 12 (LSB first) | x86, ARM (default), most RTOS |
When an ARM microcontroller sends a 16-bit value over a CAN bus (big-endian protocol), you must swap bytes:
uint16_t value = 0x1234;
uint16_t network_order = ((value & 0xFF) << 8) | ((value >> 8) & 0xFF);
// Result: 0x3412 → transmitted as bytes 0x12, 0x34
Many compiler toolchains provide htons() / ntohs() helpers for this purpose.
Bitfields in C: Convenient but Risky
C allows you to define bitfields in structs:
typedef struct {
uint8_t channel : 3;
uint8_t enabled : 1;
uint8_t mode : 2;
uint8_t reserved : 2;
} ADC_Config;
Bitfields improve readability but have portability issues: the C standard does not specify the allocation order of bits within a byte, so the layout can differ between compilers. For hardware register access, explicit bitwise operations are safer and more predictable across toolchains.
Practical Example: Configuring a UART
A typical UART configuration register might look like this:
| Bits | Field | Values |
|---|---|---|
| 1:0 | Parity | 00=none, 01=even, 10=odd |
| 3:2 | Stop bits | 00=1 bit, 01=1.5 bits, 10=2 bits |
| 6:4 | Data bits | 000=5bit, 001=6bit, 010=7bit, 011=8bit |
| 7 | Enable | 0=off, 1=on |
Configuring 8-N-1 (8 data bits, no parity, 1 stop bit, enabled):
UART_CFG = (1 << 7) // enable
| (3 << 4) // 8 data bits
| (0 << 2) // 1 stop bit
| 0; // no parity
// Binary: 10110000 = 0xB0
Key Takeaways
- Bitwise OR sets bits, AND with NOT clears bits, XOR toggles bits — these three operations cover most register manipulation tasks.
- Multi-bit fields require a clear-then-set pattern: mask out the field bits with AND, then write the new value with OR.
- Always respect protocol endianness; big-endian is standard for network and automotive buses.
- Prefer explicit bitwise operations over C bitfields for hardware register access to avoid compiler-specific layout differences.
Try It Yourself
Need to convert register values or debug binary protocols? Use the Text to Binary Converter to encode data, or check the reverse conversion to decode binary output from your embedded device.