Skip to content

Binary, Hex, and Number Systems

Binary, Hex, and Number Systems hero image
Modified:
Published:

Every microcontroller register, every memory address, and every data byte is a pattern of ones and zeros. When you write PORTB = 0x3F; in C, you are setting six specific bits high and two bits low. This lesson teaches you to think in binary and hexadecimal so that register configurations stop being mysterious hex values and start being clear, intentional bit patterns. #Binary #Hexadecimal #NumberSystems

Why Number Systems Matter for Embedded Programming

When you configure a microcontroller, you are writing specific bit patterns to hardware registers. Consider this line of STM32 code:

GPIOA->CRL = 0x44444443;

This single hexadecimal value configures eight GPIO pins. Each hex digit (4 bits) controls one pin’s mode and speed. Without understanding hex and binary, this line is opaque. With that understanding, you can read it like a sentence: “Pin 0 is push-pull output at 50 MHz, pins 1 through 7 are floating inputs.”

Every embedded programmer needs three number systems:

BaseNameDigits UsedPrefix in CCommon Use
2Binary0, 10bIndividual bit manipulation
10Decimal0-9(none)Human-readable values
16Hexadecimal0-9, A-F0xRegister values, addresses

Binary: The Language of Hardware



Counting in Binary

A single binary digit (bit) can be 0 or 1. With more bits, you can represent larger numbers by assigning each position a power of two:

8-bit Register (one byte)
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ Bit7│ Bit6│ Bit5│ Bit4│ Bit3│ Bit2│ Bit1│ Bit0│
│ 1 │ 0 │ 1 │ 1 │ 0 │ 0 │ 1 │ 1 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
128 64 32 16 8 4 2 1
(MSB) (LSB)
Value = 128 + 32 + 16 + 2 + 1 = 179
Bit Position76543210
Weight

To find the decimal value of a binary number, multiply each bit by its weight and add the results.

Example: 0b10110011

Counting from 0 to 15 in Binary

DecimalBinary (4-bit)DecimalBinary (4-bit)
0000081000
1000191001
20010101010
30011111011
40100121100
50101131101
60110141110
70111151111

Notice the pattern: bit 0 toggles every count, bit 1 toggles every 2 counts, bit 2 toggles every 4 counts, and bit 3 toggles every 8 counts. This is exactly how a binary counter circuit works (Lesson 5).

Powers of Two You Should Memorize

These come up constantly in embedded work:

PowerValueSignificance
256One byte range (0 to 255)
1,0241 KB (kilobyte)
4,09612-bit ADC range (0 to 4095)
65,53616-bit timer range, 64 KB
1,048,5761 MB
4,294,967,29632-bit address space (4 GB)

Byte and Word Storage in Memory

Each memory address holds one byte. Multi-byte values use consecutive addresses:

Memory layout: 32-bit value 0x12345678
Address Byte (little-endian)
┌────────┬──────────┐
│ 0x1000 │ 0x78 │ (LSB stored first)
├────────┼──────────┤
│ 0x1001 │ 0x56 │
├────────┼──────────┤
│ 0x1002 │ 0x34 │
├────────┼──────────┤
│ 0x1003 │ 0x12 │ (MSB stored last)
└────────┴──────────┘
ARM Cortex-M uses little-endian byte order.

Converting Decimal to Binary

  1. Divide the decimal number by 2.
  2. Record the remainder (0 or 1). This is the next bit, starting from the least significant.
  3. Repeat with the quotient until it reaches 0.
  4. Read the remainders from bottom to top.

Example: Convert 179 to binary.

StepDivisionQuotientRemainder
1179 / 2891 (bit 0)
289 / 2441 (bit 1)
344 / 2220 (bit 2)
422 / 2110 (bit 3)
511 / 251 (bit 4)
65 / 221 (bit 5)
72 / 210 (bit 6)
81 / 201 (bit 7)

Reading remainders bottom to top: 10110011. Verify: .

Hexadecimal: Compact Binary



Why Hex?

Binary is precise but verbose. Writing 0b11111111111111111111111111111111 for a 32-bit value is impractical. Hexadecimal solves this by grouping every 4 bits into a single digit:

Hex DigitBinaryDecimal
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
A101010
B101111
C110012
D110113
E111014
F111115

One hex digit = 4 bits. Two hex digits = 8 bits (one byte). Eight hex digits = 32 bits (one word on ARM Cortex-M).

Hex to Binary (and Back)

Converting between hex and binary is purely mechanical. Replace each hex digit with its 4-bit binary equivalent:

Example: 0x3F to binary

  • 3 = 0011
  • F = 1111
  • Result: 0b00111111

Example: 0b10110011 to hex

  • Split into groups of 4 from the right: 1011 0011
  • 1011 = B
  • 0011 = 3
  • Result: 0xB3

Hex to Decimal

Multiply each hex digit by its positional power of 16:

Example: 0x3F

Example: 0x40021000 (a typical STM32 peripheral base address)

In practice, you almost never convert large hex addresses to decimal. You work in hex because the bit grouping is what matters.

Reading Register Values in Hex

MCU Memory Address Map (simplified)
┌────────────────────────┐ 0xFFFFFFFF
│ System / Cortex-M │
├────────────────────────┤ 0xE0000000
│ (reserved) │
├────────────────────────┤ 0x40000000
│ Peripheral Regs │
│ (GPIO, UART, SPI...) │
├────────────────────────┤ 0x20000000
│ SRAM (variables) │
├────────────────────────┤ 0x08000000
│ Flash (program code) │
└────────────────────────┘ 0x00000000

Here is a real example. The STM32F103 GPIO port A configuration register low (GPIOA_CRL) at address 0x40010800 might contain:

GPIOA->CRL = 0x44444443

Break it into nibbles (4-bit groups), one per pin:

Hex DigitBinaryPinMeaning
30011PA0Output, push-pull, 50 MHz
40100PA1Floating input
40100PA2Floating input
40100PA3Floating input
40100PA4Floating input
40100PA5Floating input
40100PA6Floating input
40100PA7Floating input

The least significant hex digit corresponds to the lowest pin number. Each nibble encodes both the mode (input/output) and the configuration (push-pull, open-drain, floating, pull-up/pull-down) for one pin.

Binary Coded Decimal (BCD)



What Is BCD?

BCD encodes each decimal digit separately in 4 bits of binary:

DecimalBCD
00000
10001
20010
91001

The combinations 1010 through 1111 are unused in BCD. This wastes some bit patterns, but it makes decimal display straightforward.

Example: Decimal 47 in BCD

  • 4 = 0100
  • 7 = 0111
  • BCD: 0100 0111

Compare with pure binary: 47 in binary is 00101111. The BCD representation is different because each decimal digit is encoded independently.

Where BCD Appears in Embedded Systems

BCD is common in:

  • Real-Time Clock (RTC) chips: The DS3231 and the STM32 internal RTC store time in BCD format. When you read the hours register and get 0x23, that means 23 hours, not decimal 35.
  • Seven-segment display drivers: BCD-to-7-segment decoder chips (like the 74LS47) take 4-bit BCD input and directly drive the segments.
  • Legacy instrumentation: Many industrial systems use BCD for human-readable numeric displays.

BCD Conversion in C

// Convert binary to BCD (for values 0-99)
uint8_t binary_to_bcd(uint8_t binary) {
return ((binary / 10) << 4) | (binary % 10);
}
// Convert BCD to binary
uint8_t bcd_to_binary(uint8_t bcd) {
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}

When reading time from an RTC:

uint8_t hours_bcd = rtc_read(RTC_HOURS_REG); // Returns 0x23
uint8_t hours = bcd_to_binary(hours_bcd); // Returns 23

Signed Integers: Two’s Complement



The Problem with Unsigned Numbers

An 8-bit unsigned integer represents values from 0 to 255. But what about negative numbers? Embedded systems constantly deal with signed values: temperature readings, accelerometer data, motor direction.

Two’s Complement Representation

Two’s complement is the standard way digital systems represent signed integers. The most significant bit (MSB) becomes the sign bit:

  • MSB = 0: the number is positive (or zero)
  • MSB = 1: the number is negative

For an 8-bit signed integer:

BinaryUnsigned ValueSigned Value (Two’s Complement)
0111 1111127+127
0000 00011+1
0000 000000
1111 1111255-1
1111 1110254-2
1000 0001129-127
1000 0000128-128

An 8-bit signed integer ranges from -128 to +127.

Finding the Two’s Complement

To negate a number in two’s complement:

  1. Invert all bits (change 0 to 1 and 1 to 0). This is called the one’s complement.
  2. Add 1 to the result.

Example: Find the two’s complement representation of -5.

  1. Start with +5: 0000 0101
  2. Invert all bits: 1111 1010
  3. Add 1: 1111 1011

Verify: 1111 1011 as a signed byte. The MSB is 1, so it is negative. Invert and add 1 to find the magnitude: invert gives 0000 0100, add 1 gives 0000 0101 = 5. So the original value is -5.

Why Two’s Complement Works

The beauty of two’s complement is that addition works the same for both signed and unsigned numbers. The hardware does not need separate adder circuits:

0000 0011 (+3)
+ 1111 1011 (-5)
-----------
1111 1110 (-2) ✓

The carry out of the MSB is simply discarded. This is why the ALU inside your microcontroller uses a single adder for both signed and unsigned arithmetic.

Two’s Complement in C

In C, int8_t is a signed 8-bit type and uint8_t is unsigned. The same bit pattern is interpreted differently:

uint8_t a = 0xFB; // Unsigned: 251
int8_t b = 0xFB; // Signed: -5 (same bits, different interpretation)
printf("Unsigned: %u\n", a); // Prints 251
printf("Signed: %d\n", b); // Prints -5

This matters when reading sensor data. An accelerometer might return a signed 16-bit value where 0xFFE0 means -32, not 65,504.

Two's complement number line (4-bit)
-8 -7 -6 -5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5 +6 +7
1000 1001 1010 1011 1100 1101 1110 1111 0000 0001 0010 0011 0100 0101 0110 0111
│ │ │
Most Zero Most
negative positive

Sign Extension

When you move a signed value from a smaller type to a larger type, the sign bit must be replicated into the upper bits. This is called sign extension:

int8_t small = -5; // 0xFB = 1111 1011
int16_t large = small; // 0xFFFB = 1111 1111 1111 1011

The compiler handles this automatically when you assign between signed types, but you need to be aware of it when doing manual bit manipulation.

Bit Manipulation: The Bridge to Hardware



Bitwise Operators in C

These operators work on individual bits and map directly to logic gates (Lesson 2):

OperatorNameExampleResultGate Equivalent
&AND0xF0 & 0x3C0x30AND gate
|OR0xF0 | 0x3C0xFCOR gate
^XOR0xF0 ^ 0x3C0xCCXOR gate
~NOT~0xF00x0FNOT gate (inverter)
<<Left shift0x01 << 30x08Shift register
>>Right shift0x80 >> 30x10Shift register

Common Bit Manipulation Patterns

These patterns appear in virtually every embedded program:

Set a bit (turn it to 1 without changing other bits):

PORTB |= (1 << 5); // Set bit 5 of PORTB

Clear a bit (turn it to 0 without changing other bits):

PORTB &= ~(1 << 5); // Clear bit 5 of PORTB

Toggle a bit (flip its value):

PORTB ^= (1 << 5); // Toggle bit 5 of PORTB

Check a bit (test if it is set):

if (PINB & (1 << 5)) {
// Bit 5 is high
}

Set multiple bits using a mask:

// Set bits 0, 1, and 5 of a control register
CTRL_REG |= (1 << 0) | (1 << 1) | (1 << 5); // 0x23

Clear a field and set a new value:

// Set timer prescaler bits [2:0] to value 5, without changing other bits
TCCR0B = (TCCR0B & ~0x07) | 0x05;
Bit manipulation: set bit 5 of PORTB
Before: PORTB = 0xC1
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┘
7 6 5 4 3 2 1 0
Mask: (1 << 5) = 0x20
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 0 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┘
After: PORTB |= (1 << 5) => 0xE1
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┘

This pattern (mask then set) is the most important bit manipulation idiom in embedded programming. You will use it hundreds of times.

Practical Exercises



Exercise 1: Base Conversion Table

Fill in the missing values:

DecimalBinary (8-bit)Hexadecimal
42??
?1010 1010?
??0x7F
200??
?0110 0100?
Solutions
DecimalBinary (8-bit)Hexadecimal
420010 10100x2A
1701010 10100xAA
1270111 11110x7F
2001100 10000xC8
1000110 01000x64

Exercise 2: Two’s Complement

Find the 8-bit two’s complement representation of these signed values:

  1. -1
  2. -128
  3. -42
  4. -100
Solutions
  1. -1: invert 0000 0001 to 1111 1110, add 1 = 1111 1111 = 0xFF
  2. -128: 1000 0000 = 0x80 (the most negative 8-bit signed value)
  3. -42: invert 0010 1010 to 1101 0101, add 1 = 1101 0110 = 0xD6
  4. -100: invert 0110 0100 to 1001 1011, add 1 = 1001 1100 = 0x9C

Exercise 3: Decode a Register Value

An STM32 GPIO output data register (ODR) reads 0x00000A5F. Which pins (0 through 15) are high?

Solution

Focus on the lower 16 bits: 0x0A5F

Convert to binary: 0000 1010 0101 1111

Pins that are high (bit = 1):

  • Pin 0: 1 (high)
  • Pin 1: 1 (high)
  • Pin 2: 1 (high)
  • Pin 3: 1 (high)
  • Pin 4: 1 (high)
  • Pin 5: 0 (low)
  • Pin 6: 1 (high)
  • Pin 7: 0 (low)
  • Pin 8: 0 (low)
  • Pin 9: 1 (high)
  • Pin 10: 0 (low)
  • Pin 11: 1 (high)
  • Pins 12-15: 0 (low)

Pins high: 0, 1, 2, 3, 4, 6, 9, 11.

Exercise 4: Bit Manipulation in C

Write C expressions to:

  1. Set bits 4 and 7 of register REG without changing other bits.
  2. Clear bits 0 through 3 of register REG without changing other bits.
  3. Check if bit 5 of register STATUS is set.
  4. Extract bits [7:4] from register DATA as a value from 0 to 15.
Solutions
// 1. Set bits 4 and 7
REG |= (1 << 4) | (1 << 7); // or: REG |= 0x90;
// 2. Clear bits 0-3
REG &= ~0x0F; // or: REG &= 0xFFFFFFF0;
// 3. Check bit 5
if (STATUS & (1 << 5)) { /* bit 5 is set */ }
// 4. Extract bits [7:4]
uint8_t value = (DATA >> 4) & 0x0F;

How This Connects to Embedded Programming



Everything in this lesson is directly applicable the moment you touch a microcontroller:

Register Configuration

Every peripheral setup involves writing hex values to registers. Understanding binary lets you decode what 0x44444443 means for GPIO configuration instead of copying values blindly.

Debugging

When you read a register value in a debugger and see 0x0000040D, you need to instantly know which bits are set. That skill starts with fluent binary/hex conversion.

Data Interpretation

Sensor data often arrives as signed integers, BCD values (from RTCs), or multi-byte values that need assembly from individual bytes. Two’s complement and bit shifting are essential.

Efficient Code

Bit manipulation (set, clear, toggle, check) is how you control hardware efficiently. These operations compile to single instructions on most MCUs.

Summary



ConceptKey Takeaway
BinaryEvery bit is a power of 2. An 8-bit byte holds 0 to 255.
HexadecimalEach hex digit is 4 bits. Two hex digits make one byte.
BCDEach decimal digit stored as 4 bits. Used in RTCs and displays.
Two’s complementSigned numbers where the MSB is the sign bit. Invert and add 1 to negate.
Bit manipulationAND to clear/test, OR to set, XOR to toggle, shift to position.

In the next lesson, you will see how these binary values are processed by logic gates, the physical building blocks that perform AND, OR, and NOT operations in hardware.

Comments

Loading comments...


© 2021-2026 SiliconWit®. All rights reserved.