Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Binary and Decimal Conversion in Embedded C: Worked Examples
Fourteen step-by-step examples covering decimal-to-binary, binary-to-decimal, binary addition, overflow, shifts, register bit patterns on the STM32F407, and verified C programs.
Binary and Decimal Conversion in Embedded C: What These Examples Cover
The theory page explained why a microcontroller thinks in binary. This page is the workshop: every concept is turned into a worked, step-by-step example that you can follow with a pencil. By the end you will be able to do binary and decimal conversion in embedded C in your head for small values, on paper for any value, and in code for everything else.
Each example follows the same pattern: problem → method → diagram → C code → verified output → explanation. All C programs on this page were compiled with GCC and executed, and the output boxes show the real output, not hand-typed guesses.
| # | Example | Skill you practise | Embedded relevance |
|---|---|---|---|
| 1 | Decimal → binary by repeated division | Division method | Loading a value into a register |
| 2 | Decimal → binary by place-value subtraction | Weight method | Quick mental conversion |
| 3 | Binary → decimal (two methods) | Weights and doubling | Reading a register in a debugger |
| 4 | 16-bit and 32-bit values | Bytes, nibbles, words | Timer reload and ADC values |
| 5 | Binary addition with carry | Carry rules, overflow | Counters, checksums |
| 6 | Shifts as multiply and divide by 2 | Bit movement | Fast scaling without division |
| 7 | C program: print any number in binary | Bit extraction loop | Debug printing of registers |
| 8 | C program: convert in both directions | Algorithms in code | Command parsers, protocol tools |
| 9 | Register bits: GPIOD MODER, ODR and USART SR | Bit fields | Real STM32F407 peripherals |
| 10 | Interactive 8-bit explorer | Visual intuition | Everything above |
| 11 | Overflow and wrap-around in C | Modulo 2N | Tick counters, byte counters |
| 12 | Range of N bits and the ADC example | 2N counting | Sensor resolution |
| 13 | Common pitfalls with fixes | Octal trap, 1<<31, int width | Bugs seen in real code |
| 14 | Mini exercises | Self-test | Exam and interview readiness |
Example 1: Decimal to Binary Using Repeated Division by 2
Problem: Convert the decimal value 156 to an 8-bit binary number.
Method
- Divide the number by 2 and write down the quotient and the remainder (always 0 or 1).
- Divide the new quotient by 2 again. Repeat until the quotient becomes 0.
- The remainders, read from the last one to the first one, are the binary digits (MSB first).
| Step | Divide | Quotient | Remainder |
|---|---|---|---|
| 1 | 156 ÷ 2 | 78 | 0 |
| 2 | 78 ÷ 2 | 39 | 0 |
| 3 | 39 ÷ 2 | 19 | 1 |
| 4 | 19 ÷ 2 | 9 | 1 |
| 5 | 9 ÷ 2 | 4 | 1 |
| 6 | 4 ÷ 2 | 2 | 0 |
| 7 | 2 ÷ 2 | 1 | 0 |
| 8 | 1 ÷ 2 | 0 | 1 |
Reading the remainders from bottom to top gives 1 0 0 1 1 1 0 0. The first remainder you obtained (0) is the least significant bit, the last remainder (1) is the most significant bit. That is why the order must be reversed.
Check the answer (always do this)
More conversions to practise the pattern
| Decimal | Binary (8-bit) | Hex |
|---|---|---|
| 10 | 00001010 | A |
| 25 | 00011001 | 19 |
| 37 | 00100101 | 25 |
| 100 | 01100100 | 64 |
| 156 | 10011100 | 9C |
| 200 | 11001000 | C8 |
| 255 | 11111111 | FF |
100101 from the division method (6 bits). A register is 8, 16 or 32 bits wide, so we pad on the left with zeros: 0010 0101. Leading zeros never change the value, but they matter when you align bits with a register layout.Example 2: Decimal to Binary Using Place-Value Subtraction
Problem: Convert 156 again, this time without long division. For numbers below 256 this “weights” method is faster once you remember the eight powers of two: 128, 64, 32, 16, 8, 4, 2, 1.
Walk from the biggest weight to the smallest. If the weight fits into what is left, write 1 and subtract it. Otherwise write 0 and move on.
| Weight | Compare with remaining | Bit | Remaining afterwards |
|---|---|---|---|
| 27 = 128 | 156 ≥ 128 | 1 | 156 − 128 = 28 |
| 26 = 64 | 28 < 64 | 0 | 28 (unchanged) |
| 25 = 32 | 28 < 32 | 0 | 28 (unchanged) |
| 24 = 16 | 28 ≥ 16 | 1 | 28 − 16 = 12 |
| 23 = 8 | 12 ≥ 8 | 1 | 12 − 8 = 4 |
| 22 = 4 | 4 ≥ 4 | 1 | 4 − 4 = 0 |
| 21 = 2 | 0 < 2 | 0 | 0 (unchanged) |
| 20 = 1 | 0 < 1 | 0 | 0 (unchanged) |
Which method should I use?
| Situation | Best method | Why |
|---|---|---|
| Small value (below 256) and you know the weights | Place-value subtraction | Fast, almost mental, easy to double-check |
| Large value (for example 70000) | Repeated division | Mechanical and never needs the weight list |
| Writing code | Repeated division or shift-and-mask loop | Maps directly to %, / or >> and & |
| Value is a power of two (64, 1024) | Just count the zeros | 2n is a single 1 followed by n zeros |
0100 0000. 210 = 1024 → a 1 followed by ten zeros. Any value of the form 2n − 1 is n ones in a row: 255 = 1111 1111, 15 = 1111.Example 3: Binary to Decimal Conversion (Two Methods)
Problem: What decimal number is stored in the byte 1011 0101?
Method A — add the weights of the 1-bits
Only the columns that contain a 1 contribute: 128 + 32 + 16 + 4 + 1 = 181.
Method B — the doubling (Horner) method
Start with 0. For every bit from the left: double the running total and add the bit. It needs no powers of two, and it is exactly how a program parses a binary string.
| Step | Next bit | Calculation | Running value |
|---|---|---|---|
| start | — | — | 0 |
| 1 | 1 | 0 × 2 + 1 | 1 |
| 2 | 0 | 1 × 2 + 0 | 2 |
| 3 | 1 | 2 × 2 + 1 | 5 |
| 4 | 1 | 5 × 2 + 1 | 11 |
| 5 | 0 | 11 × 2 + 0 | 22 |
| 6 | 1 | 22 × 2 + 1 | 45 |
| 7 | 0 | 45 × 2 + 0 | 90 |
| 8 | 1 | 90 × 2 + 1 | 181 |
The result is again 181. Both methods always agree; use one to verify the other.
A second example with a nibble boundary
0x69 you can convert it nibble by nibble: 6 = 0110, 9 = 1001 → 0110 1001 = 105. Lesson 2.2 trains this skill in depth.Example 4: 16-bit and 32-bit Values (Bytes, Half-words, Words)
Real peripherals are 16 or 32 bits wide. The conversion method does not change, only the number of columns does. Consider a timer auto-reload value for a 1 ms interrupt with a 1 MHz timer clock: the counter must count 1000 ticks, so ARR = 1000 − 1 = 999.
0000 0011 = 3Low byte = 1110 0111 = 231Value = 3 × 256 + 231 = 999Notice how the value is assembled from two bytes: high × 256 + low. This is the arithmetic behind (high << 8) | low, which you will use constantly when reading 16-bit sensor values over I2C or SPI (two bytes arrive one after another).
| Unit | Bits | Unsigned range | C type |
|---|---|---|---|
| Nibble | 4 | 0 to 15 | (no type, half a byte) |
| Byte | 8 | 0 to 255 | uint8_t |
| Half-word | 16 | 0 to 65,535 | uint16_t |
| Word | 32 | 0 to 4,294,967,295 | uint32_t |
Example 5: Binary Addition With Carry, and When It Overflows
Binary addition uses only five facts. Learn them once and you can add any two numbers, which is exactly what the ALU of the Cortex-M4 does in one clock cycle.
| Input bits | Sum bit | Carry out | Rule in words |
|---|---|---|---|
| 0 + 0 | 0 | 0 | Nothing to carry |
| 0 + 1 or 1 + 0 | 1 | 0 | One 1 present |
| 1 + 1 | 0 | 1 | 2 decimal = 10 binary: write 0, carry 1 |
| 1 + 1 + carry 1 | 1 | 1 | 3 decimal = 11 binary: write 1, carry 1 |
| 0 + 0 + carry 1 | 1 | 0 | The carry lands here |
Case A: the result fits (107 + 54)
Every column is added right to left. When a column produces a carry, it appears as an amber 1 above the next column to the left, where it is added in. The answer 1010 0001 equals 161 and fits into 8 bits because the leftmost carry-out is 0.
Case B: carry out of the MSB — overflow of an 8-bit variable (200 + 100)
The true sum is 300, which needs 9 bits. An 8-bit register or uint8_t keeps only the lower 8 bits 0010 1100 = 44. The red carry-out is simply lost. That is the formal definition of unsigned overflow: the result of an addition is reduced modulo 256 (modulo 2N for N bits). In Example 11 you will see this in a real C program.
Example 6: Shifting Bits Is Multiplying or Dividing by Powers of Two
Moving every bit one place to the left doubles the number, because each bit moves to a column that is worth twice as much. Moving right halves it (integer division, the lowest bit falls off). This is why embedded programmers write x << 3 instead of x * 8.
0011 0111. Shifting right by 2 discards the two lowest bits (11) and gives 0000 1101 = 13. Right shift is therefore an integer division that rounds toward zero: 55 / 4 = 13.75 → 13.Example 7: A C Function That Prints Any Value in Binary
Standard printf has no portable binary format specifier (C23 adds %b, but most embedded toolchains do not support it yet). When you debug registers over UART or semihosting you will want your own helper. The idea: test one bit at a time with a shift and a mask.
Line-by-line explanation
| Code | What it does |
|---|---|
for (int i = nbits - 1; i >= 0; i--) | Starts at the most significant bit position and walks down to bit 0, so the output reads left to right like a written binary number. |
(value >> i) & 1U | Shifts bit number i into position 0, then masks every other bit away. The result is 0 or 1. |
putchar(... ? '1' : '0') | Prints the character for that bit. |
if ((i % 4 == 0) && (i != 0)) | Inserts a space after every nibble except after the last one. |
uint32_t value | A 32-bit parameter can carry 8, 16 or 32-bit data; nbits selects how many columns to show. |
putchar with your UART transmit function and you have a one-line register dump for the serial terminal.Example 8: Converting in Both Directions Inside a C Program
Now we implement Examples 1 and 3 as functions: dec_to_bin() uses repeated division, and bin_to_dec() uses the doubling method. Command-line tools, bootloaders and protocol parsers contain code exactly like this.
What to notice
- Round trip: the last column equals the first column for every test value, proving that the two functions are inverse operations.
- LSB first, then reverse: division produces the least significant bit first, so we store digits in
tmpand copy them back in reverse order. - Zero special case: the
whileloop would never run for 0, so it is handled separately. Forgetting this gives an empty string, a classic bug. - Unsigned types: using
uint32_tavoids sign surprises and makes the width explicit.
Example 9: Register Bit Representation on the STM32F407
Hardware registers are just numbers whose bits have meanings. The STM32F407 Discovery board has four user LEDs on port D: PD12 (green), PD13 (orange), PD14 (red), PD15 (blue). The GPIOD peripheral lives at base address 0x40020C00.
9a. Output data register (GPIOD_ODR): one bit per pin
The value 0xF000 is simply 1111 0000 0000 0000 in binary: bits 15 to 12 set. Setting only the green LED means setting only bit 12, which is the number 1 << 12 = 0x1000 = 4096.
9b. Mode register (GPIOD_MODER): two bits per pin
To make PD12 an output we must write the 2-bit pattern 01 into bits [25:24]. The 32 bits of MODER are divided into sixteen 2-bit fields, pin 0 in bits [1:0] up to pin 15 in bits [31:30].
00 input, 01 output, 10 alternate function, 11 analog9c. Status register (USART_SR): reading flags
The USART status register returns 0xC0 when the transmit buffer is empty and the last transmission has completed. Decode it by writing the byte in binary and looking up the bit names in the reference manual:
3UL << 24 is the 2-bit mask 11 placed at bit 24, ~ flips it so that only those two bits are cleared, and 1UL << 24 writes the pattern 01. Module 5 builds this bit-manipulation toolkit systematically.Example 10: Interactive 8-bit Explorer
Click any bit to flip it. Watch the unsigned value, the signed value (2’s complement, covered in Lesson 2.3), the hex digits and the binary string update together. Try these targets: 156, 0x0F, 0x80 and 0xFF.
- Set only bit 7 and observe: unsigned 128, signed −128. The same pattern, two meanings.
- Set bits 3..0: you get
0x0F= 15, the maximum value of one nibble. - Set all bits: unsigned 255, signed −1, hex
0xFF.
Example 11: Overflow and Wrap-Around in a Real C Program
Example 5 showed overflow on paper. Here is the same event in a running program. A byte counter is incremented in steps of 3 starting at 250. Watch what happens when the sum would reach 256.
The compiler first promotes both operands to int (a rule called integer promotion), performs the addition, and converts the result back to uint8_t. The conversion keeps only the low 8 bits, which is exactly the arithmetic modulo 256 we computed by hand.
now − start still gives the right elapsed time across the wrap. Never compare two raw tick values with <; always subtract.Example 12: How Many Values Fit in N Bits? (ADC Resolution Example)
Counting is the most useful binary skill for hardware design: N bits can represent 2N different values, from 0 up to 2N − 1.
| Bits (N) | Different values 2N | Largest unsigned value | Where you meet it |
|---|---|---|---|
| 4 | 16 | 15 | One hex digit, a nibble |
| 8 | 256 | 255 | uint8_t, UART data register |
| 10 | 1,024 | 1,023 | Classic 10-bit ADC |
| 12 | 4,096 | 4,095 | STM32F407 ADC (12-bit) |
| 16 | 65,536 | 65,535 | uint16_t, timer counters |
| 24 | 16,777,216 | 16,777,215 | Audio sample, SysTick counter |
| 32 | 4,294,967,296 | 4,294,967,295 | uint32_t, all ARM registers |
Worked problem: converting an ADC reading to volts
The STM32F407 ADC is 12-bit and compares the input with a 3.3 V reference. The smallest step (one count) is 3.3 V ÷ 4096 ≈ 0.806 mV. A raw reading of 2048 (binary 1000 0000 0000) is therefore half scale.
Example 13: Common Pitfalls With Binary and Decimal in C (Wrong vs Correct)
Pitfall 1 — a leading zero makes the number octal
In C, a constant that starts with 0 is an octal number. This silently changes the value:
007 or 0100). Use hexadecimal when you want a fixed-width look: 0x07, 0x64.Pitfall 2 — shifting into the sign bit
| Wrong | Correct | Problem |
|---|---|---|
uint32_t m = 1 << 31; | uint32_t m = 1UL << 31; | 1 is a signed int; shifting a 1 into the sign bit is undefined behaviour in C. |
x = 1 << 40; | x = 1ULL << 40; | Shifting by more than the width of the type is undefined; use a 64-bit constant. |
int mask = 0xFFFFFFFF; | uint32_t mask = 0xFFFFFFFFU; | On a 32-bit signed int the constant is converted to −1; be explicit with unsigned. |
Pitfall 3 — assuming int has a fixed width
On the STM32F407 an int is 32 bits, but on an 8-bit AVR or a 16-bit MSP430 it is 16 bits. A conversion routine that assumes 32 bits will break after porting. Use the fixed-width types from <stdint.h> (Module 3) whenever the number of bits matters.
Pitfall 4 — printing with the wrong format specifier
Example 14: Mini Exercises — Try It Yourself
Solve each exercise on paper first, then reveal the answer. If you miss more than two, repeat Examples 1 to 5.
function revealAns(btn){var d=btn.nextElementSibling,o=d.style.display===’block’;d.style.display=o?’none’:’block’;btn.textContent=o?’Show answer’:’Hide answer’;}Quick Reference Card
| Task | Method | One-line recipe |
|---|---|---|
| Decimal → binary | Division | Divide by 2 repeatedly, read remainders bottom to top |
| Decimal → binary | Subtraction | Subtract 128, 64, 32 … 1 when they fit |
| Binary → decimal | Weights | Add 2n for every 1-bit |
| Binary → decimal | Doubling | total = total × 2 + bit, left to right |
| Multiply / divide by 2n | Shift | x << n / x >> n |
| Largest value in N bits | Count | 2N − 1 |
| Detect 8-bit overflow when adding | Carry | True sum > 255, or carry-out of bit 7 |
Powers of two worth memorising:
Finished the worked examples?
Test yourself with the quiz page: multiple choice, fill-in-the-blank, true/false, code snippet challenges, and detailed interview questions with model answers.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments