Binary and Decimal Conversion in Embedded C: Worked Examples-Free Embedded C Course Online

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

MODULE 2 · LESSON 2.1 · WORKED EXAMPLES

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.

14
Examples
7 C programs
Compiled & run
Beginner
Level

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.

#ExampleSkill you practiseEmbedded relevance
1Decimal → binary by repeated divisionDivision methodLoading a value into a register
2Decimal → binary by place-value subtractionWeight methodQuick mental conversion
3Binary → decimal (two methods)Weights and doublingReading a register in a debugger
416-bit and 32-bit valuesBytes, nibbles, wordsTimer reload and ADC values
5Binary addition with carryCarry rules, overflowCounters, checksums
6Shifts as multiply and divide by 2Bit movementFast scaling without division
7C program: print any number in binaryBit extraction loopDebug printing of registers
8C program: convert in both directionsAlgorithms in codeCommand parsers, protocol tools
9Register bits: GPIOD MODER, ODR and USART SRBit fieldsReal STM32F407 peripherals
10Interactive 8-bit explorerVisual intuitionEverything above
11Overflow and wrap-around in CModulo 2NTick counters, byte counters
12Range of N bits and the ADC example2N countingSensor resolution
13Common pitfalls with fixesOctal trap, 1<<31, int widthBugs seen in real code
14Mini exercisesSelf-testExam and interview readiness
How to use this page: Cover the result with your hand, do the conversion yourself, then compare. The mini exercises at the end have hidden answers you can reveal with one click.

Example 1: Decimal to Binary Using Repeated Division by 2

Problem: Convert the decimal value 156 to an 8-bit binary number.

Method

  1. Divide the number by 2 and write down the quotient and the remainder (always 0 or 1).
  2. Divide the new quotient by 2 again. Repeat until the quotient becomes 0.
  3. The remainders, read from the last one to the first one, are the binary digits (MSB first).
Division table for 156 (base 2)
StepDivideQuotientRemainder
1156 ÷ 2780
278 ÷ 2390
339 ÷ 2191
419 ÷ 291
59 ÷ 241
64 ÷ 220
72 ÷ 210
81 ÷ 201
▲ Read the remainders from the last step up to the first step (bottom to top)

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.

Result: 156 as an 8-bit pattern (bit numbers above the cells)
7
1
6
0
5
0
4
1
3
1
2
1
1
0
0
0

Check the answer (always do this)

Verify with place values
weight
bit
bit × weight
27
128
1
128
26
64
0
0
25
32
0
0
24
16
1
16
23
8
1
8
22
4
1
4
21
2
0
0
20
1
0
0
128 + 16 + 8 + 4 = 156

More conversions to practise the pattern

DecimalBinary (8-bit)Hex
1000001010A
250001100119
370010010125
1000110010064
156100111009C
20011001000C8
25511111111FF
Leading zeros: The decimal 37 gives 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.

WeightCompare with remainingBitRemaining afterwards
27 = 128156 ≥ 1281156 − 128 = 28
26 = 6428 < 64028 (unchanged)
25 = 3228 < 32028 (unchanged)
24 = 1628 ≥ 16128 − 16 = 12
23 = 812 ≥ 8112 − 8 = 4
22 = 44 ≥ 414 − 4 = 0
21 = 20 < 200 (unchanged)
20 = 10 < 100 (unchanged)
The same answer drawn on the weight chart
weight
bit
bit × weight
27
128
1
128
26
64
0
0
25
32
0
0
24
16
1
16
23
8
1
8
22
4
1
4
21
2
0
0
20
1
0
0
128 + 16 + 8 + 4 = 156

Which method should I use?

SituationBest methodWhy
Small value (below 256) and you know the weightsPlace-value subtractionFast, almost mental, easy to double-check
Large value (for example 70000)Repeated divisionMechanical and never needs the weight list
Writing codeRepeated division or shift-and-mask loopMaps directly to %, / or >> and &
Value is a power of two (64, 1024)Just count the zeros2n is a single 1 followed by n zeros
Power-of-two shortcut: 26 = 64 → 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

Weight chart for 1011 0101
weight
bit
bit × weight
27
128
1
128
26
64
0
0
25
32
1
32
24
16
1
16
23
8
0
0
22
4
1
4
21
2
0
0
20
1
1
1
128 + 32 + 16 + 4 + 1 = 181

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.

StepNext bitCalculationRunning value
start——0
110 × 2 + 11
201 × 2 + 02
312 × 2 + 15
415 × 2 + 111
5011 × 2 + 022
6122 × 2 + 145
7045 × 2 + 090
8190 × 2 + 1181

The result is again 181. Both methods always agree; use one to verify the other.

A second example with a nibble boundary

0110 1001 = 105
weight
bit
bit × weight
27
128
0
0
26
64
1
64
25
32
1
32
24
16
0
0
23
8
1
8
22
4
0
0
21
2
0
0
20
1
1
1
64 + 32 + 8 + 1 = 105
Reading a debugger: When the register window of STM32CubeIDE shows 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.

999 stored in a 16-bit register (TIMx_ARR)
15
0
14
0
13
0
12
0
11
0
10
0
9
1
8
1
7
1
6
1
5
1
4
0
3
0
2
1
1
1
0
1
High byte = 0000 0011 = 3Low byte = 1110 0111 = 231Value = 3 × 256 + 231 = 999

Notice 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).

Size vocabulary on a 32-bit word (STM32F407 registers are 32-bit)
Byte 3
bits 31..24
Byte 2
bits 23..16
Byte 1
bits 15..8
Byte 0
bits 7..0
Upper half-word (16 bits)
Lower half-word (16 bits)
One word (32 bits) = 4 bytes = 8 nibbles
UnitBitsUnsigned rangeC type
Nibble40 to 15(no type, half a byte)
Byte80 to 255uint8_t
Half-word160 to 65,535uint16_t
Word320 to 4,294,967,295uint32_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 bitsSum bitCarry outRule in words
0 + 000Nothing to carry
0 + 1 or 1 + 010One 1 present
1 + 1012 decimal = 10 binary: write 0, carry 1
1 + 1 + carry 1113 decimal = 11 binary: write 1, carry 1
0 + 0 + carry 110The carry lands here

Case A: the result fits (107 + 54)

0110 1011 + 0011 0110 (amber = carry arriving in that column from the right)
carry
0
1
1
1
1
1
1
0
0
A
0
1
1
0
1
0
1
1
+ B
0
0
1
1
0
1
1
0
sum
0
1
0
1
0
0
0
0
1
01101011 (107) + 00110110 (54) = 10100001 (161)

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)

1100 1000 + 0110 0100 (red = carry-out that has no bit to live in)
carry
1
1
0
0
0
0
0
0
0
A
1
1
0
0
1
0
0
0
+ B
0
1
1
0
0
1
0
0
sum
1
0
0
1
0
1
1
0
0
11001000 (200) + 01100100 (100) = 100101100 (300) — carry-out = 1, does not fit in 8 bits

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.

Embedded consequence: There is no exception, no crash, no warning on a Cortex-M4 when an unsigned addition wraps. The processor only sets the C (carry) flag in the APSR status register. Your C code must make sure the result fits, or deliberately rely on the wrap-around (as hardware tick counters do).

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.

Start with 13, shift left twice (the blue cells show where the original 1-bits travel)
13 << 0
0
0
0
0
1
1
0
1
26 << 1
0
0
0
1
1
0
1
0
52 << 2
0
0
1
1
0
1
0
0
Start with 52, shift right twice (the low bits fall off the right edge)
52 >> 0
0
0
1
1
0
1
0
0
26 >> 1
0
0
0
1
1
0
1
0
13 >> 2
0
0
0
0
1
1
0
1
shift_demo.c — shifts equal multiplication by 2^n
#include <stdio.h> #include <stdint.h> int main(void) { uint8_t x = 13; printf(“13 << 1 = %u\n”, (unsigned)(x << 1)); /* 26 */ printf(“13 << 2 = %u\n”, (unsigned)(x << 2)); /* 52 */ printf(“13 << 3 = %u\n”, (unsigned)(x << 3)); /* 104 */ printf(“52 >> 2 = %u\n”, (unsigned)(52u >> 2)); /* 13 */ printf(“55 >> 2 = %u\n”, (unsigned)(55u >> 2)); /* 13: low bits are lost */ return 0; }
Output (verified by compiling and running)
13 << 1 = 26 13 << 2 = 52 13 << 3 = 104 52 >> 2 = 13 55 >> 2 = 13
Why 55 >> 2 is also 13: 55 is 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.

print_bin.c — bit-by-bit printing
#include <stdio.h> #include <stdint.h> /* Print the lowest ‘nbits’ bits of ‘value’, MSB first, with a space after every group of four bits. */ void print_bin(uint32_t value, int nbits) { for (int i = nbits – 1; i >= 0; i–) { putchar(((value >> i) & 1U) ? ‘1’ : ‘0’); if ((i % 4 == 0) && (i != 0)) { putchar(‘ ‘); } } } int main(void) { uint8_t small = 156; uint16_t arr = 999; uint32_t big = 70000; printf(“%10u = “, (unsigned)small); print_bin(small, 8); printf(“\n”); printf(“%10u = “, (unsigned)arr); print_bin(arr, 16); printf(“\n”); printf(“%10u = “, (unsigned)big); print_bin(big, 32); printf(“\n”); return 0; }
Output (verified by compiling and running)
156 = 1001 1100 999 = 0000 0011 1110 0111 70000 = 0000 0000 0000 0001 0001 0001 0111 0000

Line-by-line explanation

CodeWhat 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) & 1UShifts 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 valueA 32-bit parameter can carry 8, 16 or 32-bit data; nbits selects how many columns to show.
Embedded use: Replace 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.

convert.c — decimal to binary string and back
#include <stdio.h> #include <stdint.h> #include <string.h> /* Repeated division by 2. ‘out’ must hold at least 33 bytes. */ void dec_to_bin(uint32_t n, char *out) { char tmp[33]; int len = 0; if (n == 0U) { strcpy(out, “0”); return; } while (n > 0U) { tmp[len++] = (char)(‘0’ + (n % 2U)); /* remainder is the next bit (LSB first) */ n /= 2U; } for (int i = 0; i < len; i++) /* reverse: MSB first */ { out[i] = tmp[len – 1 – i]; } out[len] = ‘\0’; } /* Doubling (Horner) method. Returns 0 on an invalid character. */ uint32_t bin_to_dec(const char *s) { uint32_t value = 0U; for (; *s != ‘\0’; s++) { if (*s != ‘0’ && *s != ‘1’) { return 0U; } value = (value * 2U) + (uint32_t)(*s – ‘0’); } return value; } int main(void) { char buf[33]; uint32_t tests[] = { 0U, 1U, 37U, 156U, 255U, 999U, 70000U }; for (unsigned i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { dec_to_bin(tests[i], buf); printf(“%6u -> %-20s -> %u\n”, (unsigned)tests[i], buf, (unsigned)bin_to_dec(buf)); } printf(“\”10110101\” = %u\n”, (unsigned)bin_to_dec(“10110101”)); return 0; }
Output (verified by compiling and running)
0 -> 0 -> 0 1 -> 1 -> 1 37 -> 100101 -> 37 156 -> 10011100 -> 156 255 -> 11111111 -> 255 999 -> 1111100111 -> 999 70000 -> 10001000101110000 -> 70000 “10110101” = 181

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 tmp and copy them back in reverse order.
  • Zero special case: the while loop would never run for 0, so it is handled separately. Forgetting this gives an empty string, a classic bug.
  • Unsigned types: using uint32_t avoids 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

GPIOD_ODR = 0xF000 → all four LEDs on, other pins low
15
1
PD15
14
1
PD14
13
1
PD13
12
1
PD12
11
0
10
0
9
0
8
0
7
0
6
0
5
0
4
0
3
0
2
0
1
0
0
0
PD12 green LEDPD13 orange LEDPD14 red LEDPD15 blue LED

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].

GPIOD_MODER with PD12 configured as general purpose output (01)
31
0
30
0
29
0
28
0
27
0
26
0
25
0
PD12
24
1
mode
23
0
22
0
21
0
20
0
19
0
18
0
17
0
16
0
15
0
14
0
13
0
12
0
11
0
10
0
9
0
8
0
7
0
6
0
5
0
4
0
3
0
2
0
1
0
0
0
Mode field values: 00 input, 01 output, 10 alternate function, 11 analog

9c. 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:

USART_SR = 0xC0
7
1
TXE
6
1
TC
5
0
RXNE
4
0
3
0
ORE
2
0
1
0
0
0
TXE: transmit data register emptyTC: transmission completeRXNE: data received (0 here)ORE: overrun error (0 here)
register_sim.c — the same bit arithmetic on a normal PC variable
#include <stdio.h> #include <stdint.h> int main(void) { uint32_t MODER = 0x00000000; /* simulated GPIOD->MODER (reset state of a free port) */ uint32_t ODR = 0x00000000; /* simulated GPIOD->ODR */ /* Configure PD12 as output: clear bits [25:24], then write 01 */ MODER &= ~(3UL << 24); MODER |= (1UL << 24); /* Turn green LED (PD12) on */ ODR |= (1UL << 12); printf(“MODER = 0x%08X\n”, (unsigned)MODER); printf(“ODR = 0x%04X (decimal %u)\n”, (unsigned)ODR, (unsigned)ODR); /* Decode a status byte */ uint32_t sr = 0xC0; printf(“TXE = %u, TC = %u, RXNE = %u\n”, (unsigned)((sr >> 7) & 1U), (unsigned)((sr >> 6) & 1U), (unsigned)((sr >> 5) & 1U)); return 0; }
Output (verified by compiling and running)
MODER = 0x01000000 ODR = 0x1000 (decimal 4096) TXE = 1, TC = 1, RXNE = 0
led_on.c — the real STM32F407 version (runs on the board, not on a PC)
#include “stm32f4xx.h” /* CMSIS device header */ int main(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; /* 1. enable the clock of GPIOD (bit 3) */ GPIOD->MODER &= ~(3UL << 24); /* 2. clear the mode field of PD12 */ GPIOD->MODER |= (1UL << 24); /* 01 = general purpose output */ GPIOD->ODR |= (1UL << 12); /* 3. drive PD12 high: green LED on */ while (1) { } }
Why binary skills matter here: The two lines that touch MODER are pure binary reasoning: 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.

Click the bits to build a byte
7
0
128
6
0
64
5
0
32
4
0
16
3
0
8
2
0
4
1
0
2
0
0
1
Unsigned decimal
0
Signed (2’s complement)
0
Hexadecimal
0
Binary
0
(function(){var N=8,v=0; function r(){var u=v>>>0,s=(N>(N-1))&1)?v-Math.pow(2,N):v,b=”,h; for(var i=N-1;i>=0;i–){var on=(v>>>i)&1,e=document.getElementById(‘t8_b’+i);b+=on;e.textContent=on; e.style.background=on?’#10b981′:’#e2e8f0′;e.style.color=on?’white’:’#475569′;e.style.borderColor=on?’#10b981′:’#cbd5e1′;if(i%4==0&&i>0)b+=’ ‘;} h=u.toString(16).toUpperCase();while(h.length<N/4)h=’0’+h; document.getElementById(‘t8_dec’).textContent=u;document.getElementById(‘t8_sgn’).textContent=s; document.getElementById(‘t8_hex’).textContent=’0x’+h;document.getElementById(‘t8_bin’).textContent=b;} window.tgl_t8=function(i){v=(v^(1<>>0;if(N<32)v=v&((1<<N)-1);r();}; window.rst_t8=function(){v=0;r();}; window.ful_t8=function(){v=(N<32)?((1<<N)-1):0xFFFFFFFF;r();}; r();})();
  • 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.

wrap_demo.c — uint8_t arithmetic is modulo 256
#include <stdio.h> #include <stdint.h> int main(void) { uint8_t counter = 250; for (int i = 0; i < 5; i++) { printf(“counter = %3u\n”, (unsigned)counter); counter = counter + 3; /* result is truncated back to 8 bits */ } uint8_t a = 200, b = 100; uint8_t sum8 = a + b; /* stored in 8 bits */ unsigned sum32 = (unsigned)a + b; /* computed in 32 bits */ printf(“200 + 100 as uint8_t = %u\n”, (unsigned)sum8); printf(“200 + 100 as unsigned = %u\n”, sum32); return 0; }
Output (verified by compiling and running)
counter = 250 counter = 253 counter = 0 counter = 3 counter = 6 200 + 100 as uint8_t = 44 200 + 100 as unsigned = 300
Why 253 + 3 becomes 0: the 9th bit has nowhere to go
253 + 3
true sum = 256
➜
256 in binary
1 0000 0000 (9 bits)
➜
Keep 8 bits
drop the leading 1
➜
Stored value
0000 0000 = 0

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.

Deliberate wrap-around: A 32-bit millisecond tick counter wraps after about 49.7 days. Because unsigned subtraction is also modulo 232, the expression 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 2NLargest unsigned valueWhere you meet it
41615One hex digit, a nibble
8256255uint8_t, UART data register
101,0241,023Classic 10-bit ADC
124,0964,095STM32F407 ADC (12-bit)
1665,53665,535uint16_t, timer counters
2416,777,21616,777,215Audio sample, SysTick counter
324,294,967,2964,294,967,295uint32_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.

12-bit ADC reading 2048 = half of full scale
11
1
MSB
10
0
9
0
8
0
7
0
6
0
5
0
4
0
3
0
2
0
1
0
0
0
adc_volts.c — raw count to millivolts
#include <stdio.h> #include <stdint.h> #define ADC_BITS 12U #define ADC_STEPS (1UL << ADC_BITS) /* 2^12 = 4096 */ #define VREF_MV 3300UL /* 3.3 V in millivolts */ int main(void) { uint16_t samples[] = { 0, 1, 1241, 2048, 3103, 4095 }; printf(“steps = %lu, 1 LSB = %.3f mV\n”, ADC_STEPS, (double)VREF_MV / ADC_STEPS); for (unsigned i = 0; i < sizeof(samples) / sizeof(samples[0]); i++) { uint32_t mv = ((uint32_t)samples[i] * VREF_MV) / ADC_STEPS; /* integer math, no float */ printf(“raw %4u -> %4lu mV\n”, (unsigned)samples[i], (unsigned long)mv); } return 0; }
Output (verified by compiling and running)
steps = 4096, 1 LSB = 0.806 mV raw 0 -> 0 mV raw 1 -> 0 mV raw 1241 -> 999 mV raw 2048 -> 1650 mV raw 3103 -> 2499 mV raw 4095 -> 3299 mV
Integer math in firmware: Many Cortex-M3 and M0 devices have no floating-point unit. Multiplying first and dividing last, using a power-of-two divisor like 4096, keeps precision and compiles to a fast shift.

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:

octal_trap.c
#include <stdio.h> int main(void) { int a = 10; /* decimal ten */ int b = 010; /* OCTAL 010 = eight, NOT ten */ int c = 0x10; /* hexadecimal 10 = sixteen */ int d = 0b1010; /* GCC/Clang extension (C23): binary ten */ printf(“a=%d b=%d c=%d d=%d\n”, a, b, c, d); return 0; }
Output (verified by compiling and running)
a=10 b=8 c=16 d=10
Rule: Never pad decimal numbers with leading zeros in C source (for example 007 or 0100). Use hexadecimal when you want a fixed-width look: 0x07, 0x64.

Pitfall 2 — shifting into the sign bit

WrongCorrectProblem
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

printf_formats.c
uint8_t b = 200; uint32_t w = 3000000000UL; printf(“%d\n”, b); /* OK: b is promoted to int */ printf(“%d\n”, w); /* WRONG: value does not fit in signed int */ printf(“%u\n”, w); /* OK for 32-bit unsigned on this platform */ printf(“%lu\n”, (unsigned long)w); /* portable: cast to the format */

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.

Exercise 1. Convert 45 to 8-bit binary
00101101 (= 0x2D)
Exercise 2. Convert 0110 0110 to decimal
102
Exercise 3. Convert 200 to 8-bit binary
11001000 (= 0xC8)
Exercise 4. Add 0101 1100 + 0010 1111. What is the sum and is there a carry-out?
10001011 = 139; carry-out = 0 (92 + 47 = 139 fits in 8 bits)
Exercise 5. How many different values can a 10-bit ADC report and what is the maximum raw reading?
210 = 1024 values; maximum raw reading 1023 (11 1111 1111).
Exercise 6. A 16-bit timer register holds 0000 0101 0011 0111. What decimal value is that, and what are its high and low bytes?
1335; high byte 0000 0101 = 5, low byte 0011 0111 = 55 (5 × 256 + 55)
Exercise 7. Without a calculator: what is 25 << 2?
100 (25 × 4). Binary: 0001 1001 → 0110 0100.
Exercise 8. Which is larger: 010 or 9 in C source code?
9. The constant 010 is octal and equals 8.
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

TaskMethodOne-line recipe
Decimal → binaryDivisionDivide by 2 repeatedly, read remainders bottom to top
Decimal → binarySubtractionSubtract 128, 64, 32 … 1 when they fit
Binary → decimalWeightsAdd 2n for every 1-bit
Binary → decimalDoublingtotal = total × 2 + bit, left to right
Multiply / divide by 2nShiftx << n / x >> n
Largest value in N bitsCount2N − 1
Detect 8-bit overflow when addingCarryTrue sum > 255, or carry-out of bit 7

Powers of two worth memorising:

20=121=222=423=824=1625=3226=6427=12828=256210=1,024212=4,096216=65,536232=4,294,967,296

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

Leave a Reply

Your email address will not be published. Required fields are marked *