When you are writing firmware for an ESP32, configuring AVR port registers, or debugging a logic analyzer trace, you need instant translation between base-2, base-10, and base-16. A microcontroller processes everything in binary, but humans read decimal, and memory addresses are written in hexadecimal. This reference provides a complete table of binary, decimal, and hexadecimal conversions standardized for 8-bit and 16-bit embedded systems, eliminating the need to mentally calculate bit-weights on the fly.

The Master Table of Binary, Hex, and Decimal Conversions

The following table maps the 16 bit positions of a standard 16-bit integer. The notation and prefix conventions (such as 0x for hex and 0b for binary) align with the ISO/IEC 80000-13 standard for information science and technology quantities, which formalizes how binary and hexadecimal prefixes are represented in technical documentation and modern C/C++ compilers.

How to read this table: The 'Bit Index' (n) represents the positional weight ($2^n$). The '8-bit Binary' column shows the byte boundary (bits 0-7), while the '16-bit Hex' column shows the full register width. Use the 'Embedded Use Case' column to see how that specific bit-weight is typically applied in hardware abstraction layers (HAL) or direct register manipulation.

Bit Index (n) Weight ($2^n$) Decimal Hex (16-bit) Binary (8-bit) Embedded Use Case
0 (LSB) $2^0$ 1 0x0001 00000001 GPIO Pin 0 mask / Odd parity check
1 $2^1$ 2 0x0002 00000010 I2C address bit 0 / UART TX enable
2 $2^2$ 4 0x0004 00000100 SPI Chip Select (CS) line toggle
3 $2^3$ 8 0x0008 00001000 Timer prescaler bit 0 configuration
4 $2^4$ 16 0x0010 00010000 ADC channel 4 selection mask
5 $2^5$ 32 0x0020 00100000 Interrupt flag clear (e.g., INTF5)
6 $2^6$ 64 0x0040 01000000 PWM output enable / Motor direction
7 (MSB 8-bit) $2^7$ 128 0x0080 10000000 Signed 8-bit integer sign bit / Parity
8 $2^8$ 256 0x0100 N/A (9-bit) 9-bit UART mode address bit
9 $2^9$ 512 0x0200 N/A DMA transfer size threshold
10 $2^{10}$ 1024 0x0400 N/A 10-bit ADC maximum resolution (1023)
11 $2^{11}$ 2048 0x0800 N/A Flash memory page boundary offset
12 $2^{12}$ 4096 0x1000 N/A 12-bit DAC maximum resolution (4095)
13 $2^{13}$ 8192 0x2000 N/A External RAM bank switching mask
14 $2^{14}$ 16384 0x4000 N/A Watchdog timer maximum timeout scalar
15 (MSB 16-bit) $2^{15}$ 32768 0x8000 N/A Signed 16-bit integer sign bit

Selecting the Right Column for Your Code or Circuit

Knowing the numbers is only half the battle; knowing which column to use in your specific installation or codebase prevents subtle bugs. Here is the decision framework for selecting your base format:

Quick-Jump Bookmarks: If you are debugging an 8-bit register, jump straight to Bit 7 (0x80) for sign errors, or Bit 0 (0x01) for LSB alignment issues. For 10-bit ADC overflows, check Bit 10.

When to Use Hexadecimal (The 0x Column)

Use hex when configuring memory addresses, 32-bit registers, and multi-byte masks. Hexadecimal maps perfectly to binary nibbles (4 bits). If you are writing to the ESP32 GPIO_OUT_REG to set pins 8 through 11 high, writing 0x0F00 is instantly readable to another engineer. Writing the decimal equivalent (3840) or the binary equivalent (0b0000111100000000) obscures the nibble boundaries and invites transcription errors. According to the All About Circuits digital textbook, hex is the universal shorthand for machine-level architecture because it compresses long binary strings without losing the visual bit-to-position mapping.

When to Use Decimal (The Base-10 Column)

Use decimal for human-readable math, PWM duty cycles, and analog thresholds. If you are setting a PWM duty cycle to 50% on an 8-bit timer, you write 127 (or 128), not 0x7F. Similarly, when calculating voltage dividers or setting an analogRead() threshold for a 5V system on a 10-bit ADC, you use the decimal value 512 to represent the 2.5V midpoint. Decimal is for the physical world; binary and hex are for the silicon.

When to Use Binary (The 0b Column)

Use explicit binary notation strictly for bitwise masking, visual GPIO pin mapping, and truth tables. When you need to clear a specific bit in a register using the bitwise AND operator, writing PORTB &= ~0b00000100; visually proves to the reader that exactly the 3rd pin (Bit 2) is being cleared, while leaving all other pins untouched. Modern C++ compilers (and the GNU C Library manual) fully support the 0b prefix, making it safe for production firmware.

Signed Modifiers and Bit-Shifting: Altering the Base Value

The table above assumes unsigned integers (where all bits contribute positive weight). However, in embedded C/C++, variables are often signed, and modifying the base value requires understanding Two's Complement and bit-shifting.

How Two's Complement Modifies the Base Value

If your variable is a signed 8-bit integer (int8_t), the Most Significant Bit (Bit 7) ceases to be a positive weight of +128 and instead becomes a negative weight of -128.

  • Unsigned 10000000: Bit 7 is 1. Value = $2^7 = 128$.
  • Signed 10000000: Bit 7 is 1. Value = $-2^7 = -128$.
  • Signed 11111111: Value = $-128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1$.

Rule of thumb: If the MSB is 1 in a signed variable, the decimal value is negative. This is why an 8-bit signed integer maxes out at +127 (01111111), not +255.

How Bit-Shifting Modifies the Base Value

Instead of looking up the table every time you need a specific bit mask, embedded engineers use the left-shift operator (<<). Shifting a base value of 1 to the left by n positions mathematically multiplies it by $2^n$.

Writing (1 << 5) tells the compiler to take the decimal value 1 (00000001) and shift it 5 places left, resulting in 00100000 (Decimal 32, Hex 0x20). This is functionally identical to looking up Bit 5 in the table, but it allows you to write dynamic masks like (1 << pin_number) where the pin number is a variable determined at runtime.

Limitations: What the Table Cannot Tell You

While this table of binary conversions defines the mathematical weight of every bit, it lacks the physical and architectural context required to safely interface with real hardware. Keep these three blind spots in mind:

1. Endianness (Byte Ordering in Memory)

The table shows that a 16-bit decimal value of 4660 is 0x1234 in hex. However, it does not tell you how those bytes are stored in physical RAM. On a Little-Endian architecture (like ARM Cortex-M chips in STM32 or the ESP32), the least significant byte is stored first. The memory address will hold 0x34 followed by 0x12. On a Big-Endian system, it stores 0x12 then 0x34. If you are casting a 16-bit integer pointer to an 8-bit array to send over I2C, ignoring endianness will result in the receiver reading the bytes backward.

2. Physical Logic Voltage Levels

A binary '1' is a mathematical abstraction. In the physical world, a '1' is represented by a voltage threshold. On an Arduino Uno (ATmega328P), a logic HIGH is nominally 5V (acceptable range 3.0V to 5V). On an ESP32 or Raspberry Pi Pico, a logic HIGH is 3.3V. If you use this table to calculate a bitmask and output a '1' on an ESP32 GPIO pin directly into a 5V CMOS input, the receiving chip may not recognize it as a HIGH, or worse, back-feed current. Always verify the logic family (TTL vs. CMOS) and VCC levels before connecting outputs.

3. Binary Prefixes vs. Decimal Prefixes in Storage

When scaling beyond 16 bits into memory sizes (kilobytes, megabytes), the NIST guidelines on binary prefixes draw a hard line between base-10 and base-2. A 'Kilobyte' (KB) in strict SI terms is 1,000 bytes (decimal). A 'Kibibyte' (KiB) is 1,024 bytes ($2^{10}$, binary). When sizing EEPROM buffers or SD card partitions in your firmware, confusing the decimal 1000 multiplier with the binary 1024 multiplier will cause buffer overflows and off-by-one memory faults.