48  Bitwise Operations and Endianness

48.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Apply endianness concepts: Identify big-endian vs little-endian byte ordering
  • Perform bitwise operations: Use AND, OR, XOR, shifts, and masks for IoT programming
  • Manipulate hardware registers: Set, clear, and toggle individual bits
  • Handle cross-platform communication: Convert between network and host byte orders

Foundation Topics: - Data Representation Fundamentals - Overview and index - Number Systems and Data Units - Binary, decimal, hexadecimal - Text Encoding for IoT - ASCII, Unicode, and UTF-8 - Packet Structure and Framing - How data is packaged

Apply These Concepts: - Sensor Circuits and Signals - Signal processing - Encryption Architecture - Cryptographic data - Prototyping Hardware - Microcontroller programming

Learning Hubs: - Quiz Navigator - Test your understanding - Simulation Playground - Interactive tools

48.2 Prerequisites

Before reading this chapter, you should understand:


48.3 Endianness and Byte Ordering

⏱️ ~9 min | ⭐⭐ Intermediate | 📋 P02.C01.U04

When storing multi-byte numbers (16-bit, 32-bit, 64-bit), the order of bytes in memory matters. This is called endianness.

48.3.1 Big-Endian vs Little-Endian

Example: The 32-bit hexadecimal number 0x12345678

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','background':'#ffffff','mainBkg':'#2C3E50','secondBkg':'#16A085','tertiaryBkg':'#E67E22','textColor':'#2C3E50'}}}%%
graph TD
    Number["32-bit Number<br/>0x12345678"]

    subgraph BigEndian["Big-Endian (Network Byte Order)"]
        BE0["Address 0x00:<br/><b>0x12</b><br/>Most Significant"]
        BE1["Address 0x01:<br/><b>0x34</b>"]
        BE2["Address 0x02:<br/><b>0x56</b>"]
        BE3["Address 0x03:<br/><b>0x78</b><br/>Least Significant"]
    end

    subgraph LittleEndian["Little-Endian (Intel x86, ARM)"]
        LE0["Address 0x00:<br/><b>0x78</b><br/>Least Significant"]
        LE1["Address 0x01:<br/><b>0x56</b>"]
        LE2["Address 0x02:<br/><b>0x34</b>"]
        LE3["Address 0x03:<br/><b>0x12</b><br/>Most Significant"]
    end

    Number --> BigEndian
    Number --> LittleEndian

    BE0 --> BE1 --> BE2 --> BE3
    LE0 --> LE1 --> LE2 --> LE3

    style Number fill:#E67E22,stroke:#2C3E50,stroke-width:3px,color:#fff
    style BE0 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style BE1 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style BE2 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style BE3 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style LE0 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style LE1 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style LE2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style LE3 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff

Figure 48.1: Big-Endian vs Little-Endian Memory Byte Ordering Comparison

{fig-alt=“Endianness comparison diagram showing a 32-bit hexadecimal value 0x12345678 stored in memory using two different byte ordering schemes. The top section illustrates big-endian (network byte order) with four memory addresses 0x00 through 0x03 storing bytes in most-significant-first order: address 0x00 contains 0x12, 0x01 contains 0x34, 0x02 contains 0x56, and 0x03 contains 0x78. The bottom section shows little-endian (Intel x86 and ARM) ordering with the same addresses storing bytes in least-significant-first order: address 0x00 contains 0x78, 0x01 contains 0x56, 0x02 contains 0x34, and 0x03 contains 0x12. Both representations derive from the same source value, demonstrating how processor architecture determines memory layout.”} {#fig-endianness}

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
graph LR
    subgraph BIG["Big-Endian Protocols"]
        NET["<b>TCP/IP</b><br/>Internet protocols<br/>htonl() required"]
        MOD["<b>Modbus TCP</b><br/>Industrial control<br/>Standard network"]
        JAVA["<b>Java/JVM</b><br/>Cross-platform<br/>Consistent order"]
    end

    subgraph LITTLE["Little-Endian Protocols"]
        BLE["<b>Bluetooth LE</b><br/>Wearables, sensors<br/>ARM native"]
        USB["<b>USB</b><br/>Peripherals<br/>x86/ARM native"]
        I2C["<b>Most I2C sensors</b><br/>Check datasheet!<br/>Varies by vendor"]
    end

    subgraph DANGER["Danger Zone"]
        MISMATCH["<b>ESP32 to Cloud</b><br/>Little-endian CPU<br/>Big-endian network<br/>Must convert!"]
    end

    NET --> MISMATCH
    BLE --> MISMATCH

    style NET fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#000
    style MOD fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#000
    style JAVA fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#000
    style BLE fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style USB fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style I2C fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style MISMATCH fill:#7F8C8D,stroke:#E67E22,stroke-width:3px,color:#fff

Figure 48.2: Alternative view: Where Endianness Matters in IoT - Rather than abstract memory layouts, this diagram shows actual IoT protocols grouped by byte order. Big-endian (network byte order) is used by TCP/IP, Modbus TCP, and Java. Little-endian is used by Bluetooth LE, USB, and most ARM-based sensors. The “danger zone” highlights where conversion is needed: when an ESP32 (little-endian ARM) sends data to cloud servers expecting network byte order. {fig-alt=“Protocol map showing endianness in IoT systems. Left section (Big-Endian Protocols, orange): TCP/IP (internet protocols, htonl required), Modbus TCP (industrial control, standard network), Java/JVM (cross-platform, consistent order). Right section (Little-Endian Protocols, teal): Bluetooth LE (wearables sensors, ARM native), USB (peripherals, x86/ARM native), Most I2C sensors (check datasheet, varies by vendor). Center section (Danger Zone, gray with orange border): ESP32 to Cloud showing little-endian CPU connecting to big-endian network with warning symbol and must convert note. Arrows from TCP/IP and BLE point to the danger zone, illustrating where byte order conversion is required.”}

Big-Endian (Most Significant Byte First): - Used by: Network protocols (TCP/IP, Modbus), Java Virtual Machine - Why “Big”? The biggest (most significant) byte comes first - Memory layout: 0x12 at lowest address, 0x78 at highest address - Human-readable: Matches how we write numbers left-to-right

Little-Endian (Least Significant Byte First): - Used by: Intel x86, ARM Cortex-M (ESP32, STM32), Bluetooth LE - Why “Little”? The littlest (least significant) byte comes first - Memory layout: 0x78 at lowest address, 0x12 at highest address - Performance advantage: Processor can read lower bytes first during arithmetic

48.3.2 Endianness in IoT Protocols

Network protocols use big-endian (also called “network byte order”):

// Sending 16-bit temperature value 1234 (0x04D2) over TCP/IP
uint16_t temp = 1234;
uint16_t temp_network = htons(temp);  // Host TO Network Short
// Bytes sent: 0x04, 0xD2 (big-endian, regardless of CPU)

Bluetooth LE uses little-endian:

// BLE GATT characteristic: heart rate 72 BPM (0x0048)
uint16_t heart_rate = 72;
// Bytes sent: 0x48, 0x00 (little-endian, LSB first)
NoteReal-World Example: Multi-Sensor Gateway Data Aggregation

Scenario: A smart building gateway collects data from 50 sensors every minute. Each sensor sends a 32-bit timestamp (Unix epoch seconds).

Problem: Temperature sensors use ESP32 (little-endian ARM), but the central server expects network byte order (big-endian). Without conversion:

Sensor sends timestamp 1609459200 (Jan 1, 2021, 00:00:00 UTC): - Correct big-endian bytes: 0x60 0x5E 0x00 0x00 - ESP32 sends (WRONG): 0x00 0x00 0x5E 0x60 (little-endian) - Server interprets as: 96 seconds (Jan 1, 1970, 00:01:36 UTC) - Error: 51 years off! Data appears to be from 1970 instead of 2021

Impact: - Historical analytics show impossible negative time deltas - Energy usage graphs show 51 years of missing data - Triggered false “clock drift” alerts across all 50 sensors - Cost: 8 hours of debugging time ($800+ engineering cost)

Fix: Use htonl() (Host TO Network Long) function:

uint32_t timestamp = 1609459200;
uint32_t network_order = htonl(timestamp); // Converts to big-endian
client.write((uint8_t*)&network_order, 4);  // Sends: 60 5E 00 00

Prevention: Always use network byte order functions (htonl, htons, ntohl, ntohs) for multi-byte values in protocols.

WarningEndianness Mismatch Bug

Real scenario: ESP32 (little-endian) sends sensor data to Python server expecting network byte order:

// ESP32 code (WRONG - sends little-endian)
uint32_t timestamp = 1609459200;
client.write((uint8_t*)&timestamp, 4); // Sends: 00 00 5E 60

// Python server receives
data = sock.recv(4)
timestamp = struct.unpack('>I', data)[0]  # Expects big-endian
# Result: 96 (wrong!) instead of 1609459200

Fix: Use explicit byte order conversion:

// ESP32 code (CORRECT)
uint32_t timestamp = 1609459200;
uint32_t timestamp_be = htonl(timestamp); // Convert to big-endian
client.write((uint8_t*)&timestamp_be, 4); // Sends: 60 5E 00 00

Practical rule: Always check protocol specifications for endianness. When in doubt, network protocols use big-endian.


48.4 Bitwise Operations

⏱️ ~12 min | ⭐⭐ Intermediate | 📋 P02.C01.U05

Bitwise operations manipulate individual bits within bytes, essential for: - Setting/clearing hardware register flags - Packing multiple boolean values into single bytes - Extracting protocol header fields - Efficient sensor data encoding

48.4.1 Basic Bitwise Operators

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','background':'#ffffff','mainBkg':'#2C3E50','secondBkg':'#16A085','tertiaryBkg':'#E67E22','textColor':'#2C3E50'}}}%%
graph TB
    subgraph AND["AND Operation (&)<br/>Both bits must be 1"]
        A1["1010 1100<br/>& 1111 0000"]
        A2["= 1010 0000"]
    end

    subgraph OR["OR Operation (|)<br/>At least one bit is 1"]
        O1["1010 1100<br/>| 0000 1111"]
        O2["= 1010 1111"]
    end

    subgraph XOR["XOR Operation (^)<br/>Bits are different"]
        X1["1010 1100<br/>^ 0101 0101"]
        X2["= 1111 1001"]
    end

    subgraph NOT["NOT Operation (~)<br/>Flip all bits"]
        N1["~1010 1100"]
        N2["= 0101 0011"]
    end

    A1 --> A2
    O1 --> O2
    X1 --> X2
    N1 --> N2

    style A1 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style A2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style O1 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style O2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style X1 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style X2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style N1 fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style N2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff

Figure 48.3: Bitwise AND, OR, XOR, and NOT Operations with Binary Examples

{fig-alt=“Four-panel diagram illustrating fundamental bitwise operations with binary examples. The AND operation panel shows 1010 1100 & 1111 0000 resulting in 1010 0000, demonstrating that output bits are 1 only where both inputs are 1. The OR operation panel displays 1010 1100 | 0000 1111 producing 1010 1111, showing that output is 1 when at least one input is 1. The XOR operation panel presents 1010 1100 ^ 0101 0101 yielding 1111 1001, illustrating that output is 1 when inputs differ. The NOT operation panel shows ~1010 1100 resulting in 0101 0011, demonstrating bitwise inversion where each 0 becomes 1 and each 1 becomes 0. Each panel includes input values and resulting output with clear visual separation.”} {#fig-bitwise-operations}

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
graph TB
    subgraph AND_USE["AND: Extract / Read"]
        AND1["<b>Check if sensor ready</b><br/>status & 0x80<br/>Is bit 7 set?"]
        AND2["<b>Mask upper nibble</b><br/>value & 0x0F<br/>Keep only lower 4 bits"]
    end

    subgraph OR_USE["OR: Set / Enable"]
        OR1["<b>Enable I2C peripheral</b><br/>register |= 0x04<br/>Turn ON bit 2"]
        OR2["<b>Set multiple flags</b><br/>status |= (ERROR | WARN)<br/>Set error + warning"]
    end

    subgraph XOR_USE["XOR: Toggle / Flip"]
        XOR1["<b>Toggle LED state</b><br/>led_reg ^= 0x01<br/>Flip bit 0 ON to OFF"]
        XOR2["<b>Simple checksum</b><br/>crc ^= byte<br/>XOR all bytes"]
    end

    subgraph SHIFT_USE["Shift: Pack / Extract"]
        SHIFT1["<b>Extract temp from 16-bit</b><br/>temp = raw >> 4<br/>Shift right 4 bits"]
        SHIFT2["<b>Pack 2 values in byte</b><br/>(high << 4) | low<br/>Combine nibbles"]
    end

    style AND1 fill:#16A085,stroke:#2C3E50,stroke-width:1px,color:#fff
    style AND2 fill:#16A085,stroke:#2C3E50,stroke-width:1px,color:#fff
    style OR1 fill:#E67E22,stroke:#2C3E50,stroke-width:1px,color:#000
    style OR2 fill:#E67E22,stroke:#2C3E50,stroke-width:1px,color:#000
    style XOR1 fill:#2C3E50,stroke:#16A085,stroke-width:1px,color:#fff
    style XOR2 fill:#2C3E50,stroke:#16A085,stroke-width:1px,color:#fff
    style SHIFT1 fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px,color:#fff
    style SHIFT2 fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px,color:#fff

Figure 48.4: Alternative view: Bitwise Operations in Real IoT Code - Instead of abstract binary examples, this diagram shows actual use cases. AND extracts or checks bits (reading sensor status). OR sets bits (enabling peripherals). XOR toggles states (blinking LEDs, simple checksums). Shifts pack/unpack data (combining sensor readings into bytes). Students can identify which operation to use based on what they need to accomplish. {fig-alt=“Practical application diagram for bitwise operations in IoT. AND Extract/Read section (teal): Check if sensor ready using status AND 0x80 to test bit 7, Mask upper nibble using value AND 0x0F to keep lower 4 bits. OR Set/Enable section (orange): Enable I2C peripheral using register OR-equals 0x04 to turn ON bit 2, Set multiple flags using status OR-equals ERROR OR WARN. XOR Toggle/Flip section (navy): Toggle LED state using led_reg XOR-equals 0x01 to flip bit 0 ON-OFF, Simple checksum using crc XOR-equals byte to XOR all bytes together. Shift Pack/Extract section (gray): Extract temp from 16-bit using raw shift-right 4 bits, Pack 2 values in byte using high shift-left 4 OR low to combine nibbles. Each operation is labeled with its purpose and a concrete code example.”}

Truth tables:

A B A AND B A OR B A XOR B
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

48.4.2 Bit Shifts

Left shift (<<): Moves bits left, fills with zeros (multiplies by 2^n)

0b00001101 << 2  =  0b00110100
(13 << 2 = 52 = 13 x 4)

Right shift (>>): Moves bits right, fills with sign bit or zero (divides by 2^n)

0b00001100 >> 2  =  0b00000011
(12 >> 2 = 3 = 12 / 4)

48.4.3 Common Bit Manipulation Patterns

Setting a bit (turn ON):

// Set bit 3 of register to 1 (enable I2C)
register |= (1 << 3);  // OR with mask 0b00001000

Clearing a bit (turn OFF):

// Clear bit 5 of status byte (disable LED)
status &= ~(1 << 5);  // AND with inverted mask 0b11011111

Toggling a bit (flip state):

// Toggle bit 7 (invert MSB)
value ^= (1 << 7);  // XOR with mask 0b10000000

Checking if a bit is set:

// Check if error flag (bit 2) is set
if (status & (1 << 2)) {
    printf("Error detected!\n");
}

Extracting multi-bit fields:

// Extract bits 4-6 from sensor reading
// Example: 0b11010110, bits 4-6 = 101 (value 5)
uint8_t sensor = 0xD6;  // 11010110
uint8_t field = (sensor >> 4) & 0x07;  // Shift right 4, mask with 0b00000111
// Result: 0b00000101 = 5

48.4.4 Real IoT Example: Sensor Status Byte

Many sensors pack multiple flags into a single status byte:

// BME280 sensor status register (0xF3)
// Bit 7-4: Reserved
// Bit 3: measuring (1 = conversion running)
// Bit 2-1: Reserved
// Bit 0: im_update (1 = NVM data copying)

uint8_t status = i2c_read(0xF3);

// Check if sensor is busy
bool is_measuring = status & (1 << 3);
bool is_updating = status & (1 << 0);

if (is_measuring || is_updating) {
    delay(10);  // Wait before reading data
}

// More efficient: check both bits at once
if (status & 0b00001001) {  // Mask for bits 3 and 0
    delay(10);
}
NoteReal-World Example: LoRaWAN Device Status Byte

Scenario: A battery-powered agricultural soil sensor transmits data via LoRaWAN (51-byte payload limit). The device needs to report multiple status flags with every reading.

Traditional approach (wasteful):

bool temp_sensor_ok = true;        // 1 byte
bool moisture_ok = true;           // 1 byte
bool battery_low = false;          // 1 byte
bool calibration_needed = false;   // 1 byte
bool valve_open = true;            // 1 byte
bool network_joined = true;        // 1 byte
bool gps_fix = false;              // 1 byte
bool rain_detected = true;         // 1 byte
// Total: 8 bytes for status flags

Optimized approach (bitwise packing):

// Pack 8 status flags into 1 byte
uint8_t status = 0;  // Start with all flags cleared

// Set individual flags using bitwise OR
status |= (1 << 0);  // Bit 0: temp_sensor_ok = 1
status |= (1 << 1);  // Bit 1: moisture_ok = 1
// Bit 2: battery_low = 0 (keep cleared)
// Bit 3: calibration_needed = 0
status |= (1 << 4);  // Bit 4: valve_open = 1
status |= (1 << 5);  // Bit 5: network_joined = 1
// Bit 6: gps_fix = 0
status |= (1 << 7);  // Bit 7: rain_detected = 1

// Result: status = 0b10110011 = 0xB3
// Total: 1 byte for all status flags

Savings: - Payload size: 8 bytes to 1 byte (87.5% reduction) - Energy: Fewer bytes = faster transmission = 3.2 mAh saved per day (7 fewer bytes x 96 transmissions/day x 5 uA/byte) - Battery life: 7 extra days per year on a 2,000 mAh battery - Cost: $2 battery replacement avoided annually per device (x1,000 devices = $2,000/year savings)

Reading flags:

if (status & (1 << 2)) {
    printf("Battery low - replace soon\n");
}

Why this matters: With 1,000 agricultural sensors transmitting every 15 minutes, bitwise packing saves 4.03 GB/year of LoRaWAN bandwidth and extends field battery life significantly.

NoteBitwise Operations for Energy Efficiency

Why use bitwise operations in IoT? - Fast: Single CPU instruction (2-4 cycles) - Low power: No multiplication/division (expensive) - Compact: Pack 8 boolean flags into 1 byte instead of 8 bytes

Example: Status byte with 8 flags (temp OK, humidity OK, battery OK, etc.) uses 1 byte instead of 8 separate boolean variables.

48.4.5 Python Struct Module for Binary Data

When IoT devices communicate with Python servers/scripts, use the struct module:

import struct

# Pack 32-bit integer as big-endian (network byte order)
timestamp = 1609459200
packed = struct.pack('>I', timestamp)  # '>' = big-endian, 'I' = uint32
# Result: b'\x60\x5e\x00\x00'

# Pack multiple values into binary message
# Format: '>H f B' = big-endian, uint16, float32, uint8
sensor_id = 42
temperature = 23.5
status = 0b10010001

message = struct.pack('>H f B', sensor_id, temperature, status)
# Result: 9 bytes (2 + 4 + 1 + 2 padding)

# Unpack received binary data
data = b'\x00\x2a\x41\xbc\x00\x00\x91'
sensor_id, temp, status = struct.unpack('>H f B', data)
print(f"Sensor {sensor_id}: {temp}C, Status: {status:08b}")

48.5 Knowledge Check

Scenario: You’re programming an ESP32 microcontroller for a smart agriculture sensor. The datasheet shows the I2C peripheral control register at memory address 0x3FF53000. During debugging, you need to set bit 7 (the I2C enable bit) of the byte at offset 0x10 from this base address.

Think about: 1. What is the complete memory address in hexadecimal where you’ll write? (Base + offset) 2. What binary value should you write to enable I2C while keeping other bits unchanged? (Use bitwise OR) 3. If you read back 0x8F from that address, is I2C successfully enabled?

Key Insights: - Address calculation: 0x3FF53000 + 0x10 = 0x3FF53010 (hex addition) - Enable bit 7: Write 0x80 (binary 10000000) using bitwise OR to preserve other bits - Verification: 0x8F = 10001111 in binary - bit 7 is 1, so yes, I2C is enabled! - Real impact: Incorrect bit manipulation could disable other peripherals or crash the sensor, requiring a field technician visit costing $200+ per device

This is why every IoT developer must master hexadecimal and bitwise operations - a single bit error can mean the difference between a working $50 sensor and a $250 warranty replacement.

You’re debugging a temperature sensor connected to an ESP32. The serial monitor shows: Received bytes: 01 90. The sensor uses 16-bit big-endian format where the value represents temperature in tenths of a degree Celsius.

Question: What is the actual temperature reading?

Question: What temperature does 01 90 represent?

Explanation: B. 0x0190 = 400 in decimal, and the sensor reports tenths of a degree, so 400 = 40.0C. See the full walkthrough below.

Temperature: 40.0C

Step-by-step solution:

  1. Combine bytes (big-endian): 0x01 is MSB, 0x90 is LSB
  2. Hexadecimal value: 0x0190
  3. Convert to decimal: 0x0190 = 1 x 256 + 9 x 16 + 0 = 256 + 144 = 400
  4. Apply scaling: 400 tenths = 40.0C

Key insight: Big-endian means the most significant byte comes first. If this were little-endian (like Intel/ARM), you’d read it as 0x9001 = 36,865 tenths = 3686.5C (obviously wrong!). Always check the datasheet for byte order.

You’re designing a battery-powered environmental sensor that needs to transmit 6 status flags over LoRaWAN (where every byte matters):

  • Bit 0: Temperature sensor OK
  • Bit 1: Humidity sensor OK
  • Bit 2: Battery low warning
  • Bit 3: Calibration needed
  • Bit 4: Wi-Fi connected
  • Bit 5: SD card present

Question: If the temperature sensor is OK, humidity sensor is OK, battery is low, and Wi-Fi is connected, what single byte value should you transmit?

Question: What byte value should you transmit?

Explanation: A. Bits 0, 1, 2, and 4 are set = 00010111 = 0x17.

Byte value: 0x17 (decimal 23, binary 00010111)

Step-by-step solution:

  1. Identify set bits:

    • Bit 0: Temperature OK = 1
    • Bit 1: Humidity OK = 1
    • Bit 2: Battery low = 1
    • Bit 3: Calibration needed = 0
    • Bit 4: Wi-Fi connected = 1
    • Bit 5: SD card present = 0
  2. Build binary value: 00010111 (reading right to left: bits 0,1,2,4 are set)

  3. Convert to hex: 0001 0111 = 0x17

  4. Code implementation:

    uint8_t status = 0;
    status |= (1 << 0);  // Temp OK
    status |= (1 << 1);  // Humidity OK
    status |= (1 << 2);  // Battery low
    status |= (1 << 4);  // Wi-Fi connected
    // Result: status = 0x17

Key insight: Bitwise packing saves 5 bytes compared to sending 6 separate boolean bytes. Over thousands of transmissions, this dramatically extends battery life.

Your ESP32 sensor (little-endian ARM) sends a 32-bit Unix timestamp to a cloud server. The timestamp value is 1704067200 (January 1, 2024, 00:00:00 UTC).

Question: 1. What bytes will the ESP32 send if you just write the raw memory bytes (wrong approach)? 2. What bytes should be sent for correct network byte order (big-endian)? 3. What C function converts between these formats?

Question: Which byte sequence is the correct network byte order for the timestamp 0x659A3480?

Explanation: B. Network byte order is big-endian (most-significant byte first).

1. Raw little-endian bytes (WRONG):

First, convert 1704067200 to hexadecimal: - 1704067200 = 0x659A3480

Little-endian stores LSB first: 80 34 9A 65

2. Network byte order (CORRECT):

Big-endian stores MSB first: 65 9A 34 80

3. Conversion function:

#include <arpa/inet.h>  // or <netinet/in.h>

uint32_t timestamp = 1704067200;
uint32_t network_order = htonl(timestamp);  // Host TO Network Long

What happens with wrong byte order: - Server receives: 80 34 9A 65 (little-endian) - Server interprets as big-endian: 0x80349A65 = 2,150,013,541 - Wrong date: May 2038 instead of January 2024!

Key insight: Always use htonl() (32-bit), htons() (16-bit), ntohl(), and ntohs() when sending multi-byte values over networks. This ensures correct communication regardless of processor architecture.

You’re reading the datasheet for an I2C accelerometer. It says: - “X-axis acceleration is stored in registers 0x32 (low byte) and 0x33 (high byte)” - “Resolution: 10-bit, range: +/-2g, left-justified in 16-bit format”

You read register 0x32 = 0x40 and register 0x33 = 0x01.

Question: What is the actual acceleration value in g?

Question: What acceleration does the register pair 0x0140 represent (10-bit, +/-2g, left-justified)?

Explanation: B. Combine bytes = 0x0140, shift right 6 (left-justified) = 5, then scale 5 x (4g/1024) = 0.0195g.

Acceleration: +0.0195g (approx 0.02g)

Step-by-step solution:

  1. Combine bytes (little-endian since low byte is at lower address):
    • Low byte 0x32 = 0x40
    • High byte 0x33 = 0x01
    • Combined 16-bit value: 0x0140
  2. Handle left-justification:
    • “Left-justified in 16-bit” means the 10-bit value occupies the upper 10 bits
    • Shift right by 6 to get the actual 10-bit value: 0x0140 >> 6 = 5
  3. Interpret as signed value:
    • 10-bit range: -512 to +511 (two’s complement)
    • Value 5 is positive (MSB = 0)
  4. Convert to g:
    • +/-2g range means: -2g to +2g over 1024 levels
    • Resolution: 4g / 1024 = 0.00390625 g/LSB
    • Acceleration: 5 x 0.00390625 = 0.0195g (or about 0.02g)

Alternative calculation: - Full scale: +/-2g = 4g total range - 10-bit resolution: 2^10 = 1024 levels - Value of 5: (5/512) x 2g = 0.0195g

Key insight: Datasheets often describe “left-justified” or “right-justified” data. Left-justified means you need to shift right to extract the actual value. Always check the bit layout diagram in the datasheet!

Knowledge Check: Bitwise Operations Quick Check

Concept: Using bitwise operations for hardware control.

Question: To set bit 3 of a register without changing other bits, which operation do you use?

Explanation: A is correct. OR with a mask sets bits: x | 1 = 1 (sets bit), x | 0 = x (preserves bit). 0x08 = 0b00001000 sets only bit 3. AND would clear other bits; XOR would toggle; assign would overwrite everything.


48.7 Summary

Endianness and bitwise operations are essential for IoT programming:

  • Endianness: Big-endian (network byte order, MSB first) vs little-endian (Intel/ARM, LSB first)
  • Conversion Functions: htonl(), htons(), ntohl(), ntohs() for cross-platform communication
  • Bitwise AND: Extract bits, check flags, mask values
  • Bitwise OR: Set bits, enable features, combine flags
  • Bitwise XOR: Toggle bits, simple checksums
  • Bit Shifts: Pack/unpack data, multiply/divide by powers of 2

Key Takeaways:

  • Always check protocol specs for endianness - network protocols use big-endian
  • Bitwise operations are fast (single CPU instruction) and memory-efficient
  • Pack multiple boolean flags into single bytes to save bandwidth and battery
  • Use Python’s struct module for binary data parsing in server-side code
  • A single bit error can cause 51-year timestamp errors or crash sensors

48.8 What’s Next

Now that you understand data representation fundamentals, continue to:

Or return to the Data Representation overview for the complete topic map.