In digital electronics and embedded programming, 120 in hexadecimal is written as 0x78, representing the exact same quantity as decimal 120 but formatted in base-16 for direct binary translation. While the mathematical value remains identical, how you express this number in your firmware fundamentally changes what happens on the physical silicon. It dictates whether a microcontroller's UART peripheral shifts a single 8-bit byte (01111000) onto the TX wire, or parses the value as a multi-byte ASCII string, potentially breaking your serial protocol or I2C bus communication.
The Math: Converting Decimal 120 to Hex 0x78
Hexadecimal is a base-16 number system. Because 16 is a power of 2 (2^4), every single hex digit maps perfectly to a 4-bit binary nibble. This is why we use it on the bench: it is a human-readable shorthand for raw binary machine code. To convert decimal 120 to hexadecimal, we use successive division by 16.
- Divide 120 by 16: The quotient is 7, and the remainder is 8.
- Map the remainder: The remainder 8 becomes the least significant digit (rightmost).
- Map the quotient: Since the quotient (7) is less than 16, it becomes the most significant digit (leftmost).
- Combine: The result is 78 in base-16, universally written in C/C++ and Python as 0x78.
In binary, this translates seamlessly. The hex digit 7 is 0111, and the hex digit 8 is 1000. Stitch them together, and you get 01111000. When you probe a TX line with an oscilloscope or logic analyzer, this is the exact high-low voltage sequence you will see, assuming standard 8N1 UART framing with a start bit and stop bit.
Where You Meet 0x78 in Practice
You will rarely see '120' written in decimal on a datasheet. Instead, 0x78 appears across several critical embedded subsystems:
UART and the ASCII 'x' Prefix
In the standard ASCII table, decimal 120 (hex 0x78) is the lowercase letter 'x' (RFC 20 ASCII Standard). This is a meta-coincidence of programming: the very character we use to denote a hexadecimal prefix (0x) is itself represented by the hex value 0x78. When debugging serial streams, if you see a raw byte 0x78 on your logic analyzer, the peripheral might actually be receiving the letter 'x' as a text payload.
I2C 7-Bit Addressing
The I2C specification uses a 7-bit addressing scheme, allowing for 128 unique addresses (0x00 to 0x7F) (NXP I2C-bus Specification UM10204). Decimal 120 (0x78) sits near the very top of this valid range. While many addresses are reserved, 0x78 is frequently assigned to specific GPIO expanders, custom FPGA slaves, or secondary microcontrollers acting as I2C targets. If your I2C scanner script outputs 0x78, you have found a device responding at decimal address 120.
PWM and Timer Registers
When configuring hardware timers on an ESP32 or STM32, you often write raw duty cycle values to 8-bit or 16-bit registers. Setting an 8-bit PWM compare register to 0x78 yields a duty cycle of roughly 47% (120/255), a common baseline for driving logic-level MOSFETs in LED dimming circuits.
Real-World Scenario: The UART 'Missing Byte' Bug
The most common way developers shoot themselves in the foot with this number is by misunderstanding how C++ serial libraries handle integers versus raw bytes. Here is a real-world debugging walkthrough from a recent custom PCB bring-up.
0x78 to initiate the data transfer.
The Numbers: The developer wrote the following Arduino-style code to send the trigger:
// The developer thought: '120 decimal is 0x78 hex, so this sends 0x78.'
Serial.print(120);
The Outcome: The ATTiny85 never triggered. Hooking up a Saleae Logic Pro 8 to the UART TX line revealed the ESP32 wasn't sending one byte. It was sending three distinct bytes: 0x31, 0x32, and 0x30. The slave MCU received the ASCII characters '1', '2', and '0', threw a protocol framing error, and went back to sleep.
What Went Wrong: The Serial.print() function in the Arduino/ESP-IDF core is designed for human readability. When you pass it an integer like 120, it converts that integer into a base-10 ASCII string. To send the raw, single-byte binary value of 120 (which is 0x78), the developer needed to use Serial.write().
Serial.write(0x78); // Sends exactly one byte: 01111000Serial.write(120); // Also sends exactly one byte: 01111000Always use
.write() for raw binary protocols and .print() for human-readable text logging (Espressif UART API Docs).
Common Confusions: Decimal 120 vs. Hex 0x120
When reading poorly formatted forum posts or legacy code, developers frequently confuse decimal 120 with hexadecimal 0x120. This is a critical distinction that leads to register overflow and silent failures.
- Decimal 120 (Hex 0x78): Requires only 7 bits to represent (
1111000). It fits perfectly inside a standard 8-bit UART FIFO register or an 8-bit I2C payload. - Hex 0x120 (Decimal 288): Requires 9 bits to represent (
100100000).
If you attempt to write 0x120 into an 8-bit hardware register (like the UART_FIFO_REG on many microcontrollers), the hardware silently truncates the most significant bit. The 9-bit binary 1 0010 0000 gets chopped down to the lower 8 bits: 0010 0000.
That truncated 8-bit value is 0x20 in hex, which is 32 in decimal. In ASCII, 32 is the Space character. You intended to send a specific 16-bit command, but the hardware actually transmitted a blank space, leaving you debugging a 'ghost' error for hours. Always verify your bit-width requirements before assigning hex constants to peripheral registers.
Quick Reference: 120 Across Number Systems
Keep this table handy when cross-referencing datasheets, logic analyzer decodes, and serial terminal outputs.
| Format | Value | Context / Where You See It |
|---|---|---|
| Decimal | 120 | Math calculations, human-readable duty cycles, ASCII table lookups. |
| Hexadecimal | 0x78 | C/C++ source code, I2C scanner outputs, logic analyzer hex decodes. |
| Binary | 0111 1000 | Oscilloscope traces, shift-register outputs, bitwise masking. |
| Octal | 0170 | Legacy Unix file permissions, older mainframe documentation. |
| ASCII Character | 'x' | Serial text payloads, hex-prefix notation in string formatting. |
Frequently Asked Questions
Why do we use 0x78 instead of just writing 120 in code?
While the compiler treats 120 and 0x78 identically in memory, using hex signals intent to other engineers. If you are configuring a bitmask, an I2C address, or a UART register, hex visually maps to the underlying binary nibbles. Writing 0x78 tells the reader, 'I care about the bit-level arrangement of this byte,' whereas 120 implies a human-scale quantity, like a loop counter or a physical measurement.
Is 0x78 a reserved I2C address?
According to the NXP I2C specification, the 7-bit address space reserves 0x00 through 0x07 and 0x78 through 0x7F for special purposes (like 10-bit addressing headers and broadcast calls). Therefore, using 0x78 as a standard 7-bit slave address is technically a violation of the I2C spec, though many custom or non-compliant silicon vendors still use it. If you are designing a custom I2C slave, avoid 0x78 and choose an address in the 0x08 to 0x77 range.
How do I force my serial monitor to display 0x78 instead of 'x'?
If your serial terminal (like PuTTY or TeraTerm) is set to 'ASCII' or 'Text' mode, it will intercept the byte 0x78 and render the letter 'x'. To see the raw hex value, you must switch your terminal's display mode to 'Hex' or 'Hex+ASCII'. Alternatively, if you are writing the firmware that outputs the data, format the string before sending it: Serial.printf("Value: 0x%02X\n", myByte); will explicitly print the text characters '0', 'x', '7', and '8'.






