Hexadecimal (base-16) is the native language of microcontroller registers, memory pointers, and digital bus protocols. When you are debugging an ESP32 I2C bus, setting SPI clock dividers, or parsing serial data, you need immediate translation between hex, decimal, and binary. The direct answer for the most common embedded hex lookup—0xFF—is 255 in decimal and 11111111 in binary, representing a full 8-bit HIGH state or a pull-up enabled mask. Below is the definitive reference for translating and applying these values on the bench.
The Complete Table of Hexadecimal Conversions (Base Nibbles and Queried Registers)
This table provides the foundational base-16 nibbles (0x00–0x0F) from which all 8-bit and 16-bit values are derived, alongside the most frequently queried hexadecimal addresses for standard embedded sensors. The ASCII column references the ISO/IEC 646 (ASCII) standard, while the I2C addresses are sourced from the NXP I2C-bus specification (UM10204) and respective manufacturer datasheets.
0x). The Decimal column is the base-10 human-readable integer. The Binary column shows the raw 8-bit or 4-bit logic states (HIGH/LOW). The Modifier / Application column tells you where this specific value physically appears in embedded systems, such as sensor addresses or bitwise masks.
| Hex | Decimal | Binary | ASCII / Control | Embedded Application / Modifier |
|---|---|---|---|---|
| 0x00 | 0 | 0000 | NUL | Ground / Logic LOW / Clear Register |
| 0x01 | 1 | 0001 | SOH | Bit 0 Mask / Enable Bit |
| 0x02 | 2 | 0010 | STX | Bit 1 Mask |
| 0x03 | 3 | 0011 | ETX | UART Stop Bits (2) / I2C Start |
| 0x04 | 4 | 0100 | EOT | Bit 2 Mask |
| 0x05 | 5 | 0101 | ENQ | Bit 0 + Bit 2 Mask |
| 0x06 | 6 | 0110 | ACK | I2C Acknowledge (SDA pulled LOW) |
| 0x07 | 7 | 0111 | BEL | 3-Bit Mask (0b111) |
| 0x08 | 8 | 1000 | BS | Bit 3 Mask / SPI CS Active |
| 0x09 | 9 | 1001 | HT | Tab / Bit 0 + Bit 3 |
| 0x0A | 10 | 1010 | LF | Line Feed / Newline (Serial) |
| 0x0B | 11 | 1011 | VT | Vertical Tab |
| 0x0C | 12 | 1100 | FF | Form Feed / Bit 2 + Bit 3 |
| 0x0D | 13 | 1101 | CR | Carriage Return (Serial) |
| 0x0E | 14 | 1110 | SO | Shift Out |
| 0x0F | 15 | 1111 | SI | 4-Bit Nibble Mask (Lower half-byte) |
| 0x10 | 16 | 00010000 | DLE | Bit 4 Mask / 16-byte boundary |
| 0x20 | 32 | 00100000 | Space | ASCII Space / Bit 5 Mask |
| 0x3C | 60 | 00111100 | < | I2C: SSD1306 OLED Display (7-bit) |
| 0x50 | 80 | 01010000 | P | I2C: AT24C32 EEPROM (Base Address) |
| 0x68 | 104 | 01101000 | h | I2C: DS3231 RTC / MPU6050 IMU |
| 0x76 | 118 | 01110110 | v | I2C: BME280 / BMP280 Sensor (SDO=HIGH) |
| 0x77 | 119 | 01110111 | w | I2C: BME280 / BMP280 Sensor (SDO=LOW) |
| 0x7F | 127 | 01111111 | DEL | Max 7-bit I2C Address / Max signed 8-bit int |
| 0x80 | 128 | 10000000 | — | SPI Read Mask (OR with register address) |
| 0xFF | 255 | 11111111 | — | Max unsigned 8-bit int / Full PORT HIGH |
Which Column Applies to Your Installation and Protocol
Choosing the wrong column representation is the root cause of most "sensor not found" errors on the bench. Your bus protocol dictates which column you must use in your C/C++ code.
- Use the Decimal Column when interacting with user-facing math or Arduino abstraction layers. Functions like
analogWrite(pin, 128)orledcWriteTone()expect base-10 integers for PWM duty cycles and frequencies. - Use the Binary Column for direct port manipulation and pin state debugging. If you are reading
PINBon an ATmega328P or setting GPIO direction registers on an ESP32, binary literals (e.g.,B10100000) map 1:1 with the physical silicon pins. - Use the Hexadecimal Column for memory addresses, I2C device addressing, SPI register mapping, and RGB color codes. Wire libraries (like
Wire.beginTransmission(0x3C)) and hardware abstraction layers expect hex literals to match the manufacturer's datasheet register maps.
How Modifier Operations "Derate" or Shift the Base Hex Value
In AC wiring tables, derating rows reduce ampacity based on ambient heat. In hexadecimal register tables, bit-shifting and masking modify the base hex value to accommodate protocol overhead. If you plug the raw 7-bit hex value from the table above directly into a logic analyzer's 8-bit decoder, it will fail. You must apply the protocol's modifier.
The I2C 7-Bit to 8-Bit Shift
The I2C specification defines addresses as 7-bit values. However, the physical bus transmits 8 bits. The 8th bit is the Read/Write (R/W) flag. To get the actual byte placed on the SDA line, you must shift the base hex value left by one bit (<< 1) and append the R/W bit.
- Base Value: SSD1306 OLED is
0x3C(Binary:0111100). - Write Modifier (R/W = 0): Shift left ->
1111000. Add 0 ->0x78. - Read Modifier (R/W = 1): Shift left ->
1111000. Add 1 ->0x79.
Bench Tip: If your logic analyzer shows a transaction to 0x78 but your Arduino code says Wire.beginTransmission(0x3C), the hardware is working perfectly. The Wire library handles the shift automatically; the logic analyzer shows the raw shifted bus state.
SPI Register Read Masking
When reading a register over SPI (e.g., the WHO_AM_I register on an MPU6050), the base hex address is 0x75. However, SPI requires you to set the most significant bit (MSB) HIGH to indicate a read operation. You must bitwise OR the base value with 0x80.
- Base Address:
0x75(01110101) - Read Mask:
0x80(10000000) - Modified Value Sent:
0x75 | 0x80 = 0xF5(11110101)
What This Table Cannot Tell You: Endianness and Signed Limits
A standard table of hexadecimal conversions shows raw mathematical magnitude. It completely hides two critical architecture-level realities that will corrupt your data if ignored: Endianness and Signedness.
When probing hex bus traffic with an oscilloscope or logic analyzer, ensure your microcontroller's VCC matches the sensor. Feeding a 5V I2C bus into the 3.3V GPIO pins of an ESP32-WROOM-32 will permanently damage the silicon. Use a bidirectional logic level shifter (like the BSS138 MOSFET circuit) or run the bus at 3.3V with 2.2kΩ pull-up resistors to 3.3V.
Endianness (Byte Order in Memory)
If you are reading a 16-bit hex value like 0x1234 from an I2C sensor, the table tells you it equals 4660 in decimal. But how is it stored in the ESP32's RAM?
Most modern microcontrollers (ARM Cortex, ESP32, AVR) are Little-Endian. They store the least significant byte first. Therefore, 0x1234 is stored in memory as 0x34 followed by 0x12. If you cast a raw byte buffer directly to a uint16_t pointer without using a union or bitwise shifting, your decimal output will be 0x3412 (13330), completely ruining your sensor calibration.
Signedness (Two's Complement)
The table lists 0x80 as 128. This is only true if your variable is an unsigned 8-bit integer (uint8_t). If your C++ code defines the variable as a signed 8-bit integer (int8_t), the MSB acts as a sign bit. In Two's Complement arithmetic, 0x80 evaluates to -128, and 0xFF evaluates to -1. Always explicitly define your integer types using <stdint.h> to prevent the compiler from guessing your signedness intent.
Decision Path: Selecting the Correct Hex Format for Your Code
Use this decision tree to determine exactly how to format and manipulate your hexadecimal literals in your next firmware build.
| If your task is... | And your hardware protocol is... | Then use this exact code format |
|---|---|---|
| Setting an I2C sensor address | I2C (Wire library) | Wire.beginTransmission(0x3C); (Use raw 7-bit hex) |
| Reading an SPI sensor register | SPI (Hardware or BitBang) | SPI.transfer(reg | 0x80); (OR base hex with 0x80 mask) |
| Setting a GPIO pin HIGH via register | Direct Port Manipulation | PORTD |= (1 << PD5); (Use bit-shift, avoid raw hex) |
| Parsing a 16-bit Big-Endian I2C payload | I2C (Wire.readBytes) | val = (buf[0] << 8) | buf[1]; (Manually reconstruct endianness) |
| Defining an RGB LED color | WS2812B / NeoPixel | strip.setPixelColor(0, 0xFF0000); (Use 24-bit packed hex) |
By anchoring your code to the correct hexadecimal representation and applying the necessary bitwise modifiers for your specific bus protocol, you eliminate the most common class of embedded communication errors. Keep the base nibble table bookmarked, and always verify your logic analyzer captures against the shifted 8-bit bus values, not just the raw 7-bit datasheet addresses.






