For Loop Examples in Embedded C: LED Scanner, ADC Average, Soft SPI, CRC-8 and Keypad Scan-Free Embedded C Tutorial

Need Help or More Information?

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

MODULE 7 · LESSON 7.1 · EXAMPLES

For Loop Examples in Embedded C: LED Scanner, ADC Average, Soft SPI, CRC-8 and Keypad Scan

Eight worked examples with real output: a Knight-Rider LED scanner, moving average, bit counting and reversal, software SPI, bubble sort, CRC-8, DAC waveform tables and a keypad matrix scan. Study this one of the for loop examples in embedded C closely.

9
Worked Examples
Beginner → Intermediate
Level
Real output
Compiled and run

For Loop Examples in Embedded C: What This Page Covers

These for loop examples in embedded C apply Lesson 7.1 to typical firmware jobs: LED animations, filtering ADC samples, bit manipulation, a software SPI byte, sorting, a CRC, waveform tables and a keypad scan. Every program was compiled with GCC and run, and the output you see is real. Hardware registers are simulated by variables carrying the real STM32F407 names. These for loop examples in embedded C build on each other.

#ExampleSkill
1Knight-Rider scanner on PD12–PD15Counting up and down
2Moving average of ADC samplesWindow loops
3Count and reverse the bits of a byteBit loops
4Software SPI: shifting out a byteMSB-first loop
5Bubble sort of sensor readingsNested for
6CRC-8 of a messageLoop within a loop
7Waveform tables for a 12-bit DACTable generation
84×4 keypad scanRow/column scanning
9Mini exercisesSelf-test

Example 1: A Knight-Rider Scanner on the Four LEDs

Problem: Make one lit LED move from PD12 to PD15 and back. Each pass of the loop writes a single-bit mask to the output data register. The return sweep starts at PD14 and stops at PD13 so that the end LEDs are not lit twice in a row. Among the for loop examples in embedded C this pattern is very common.

scanner.c
#include <stdio.h> #include <stdint.h> static uint32_t GPIOD_ODR; static void show(void) { for (int pin = 12; pin <= 15; pin++) putchar((GPIOD_ODR >> pin) & 1u ? ‘#’ : ‘.’); putchar(‘\n’); } int main(void) { for (int pin = 12; pin <= 15; pin++) /* sweep right */ { GPIOD_ODR = 1u << pin; show(); } for (int pin = 14; pin >= 13; pin–) /* sweep back, skipping the end LEDs */ { GPIOD_ODR = 1u << pin; show(); } return 0; }
Output (verified by compiling and running)
#… .#.. ..#. …# ..#. .#..
On hardware: Insert a delay of about 100 ms (timer based) between the steps. In this model the display line stands for one LED state per pass. The loop variable doubles as the bit position, so the mask is 1u << pin.

Example 2: A Moving Average of ADC Samples

Problem: Smooth noisy 12-bit readings by averaging the last four. The inner loop walks backwards from the current index, and its condition k <= i stops at the start of the data so that no sample before index 0 is read. Keep these for loop examples in embedded C for revision.

moving_average.c
#include <stdio.h> #include <stdint.h> #define WINDOW 4 int main(void) { uint16_t adc[] = { 100, 104, 98, 250, 102, 101, 99, 103 }; /* 250 is a spike */ unsigned n = sizeof adc / sizeof adc[0]; printf(” i raw avg(4)\n”); for (unsigned i = 0; i < n; i++) { uint32_t sum = 0; unsigned count = 0; for (unsigned k = 0; k < WINDOW && k <= i; k++) /* only samples that exist */ { sum += adc[i – k]; count++; } printf(“%2u %4u %6u\n”, i, adc[i], (unsigned)(sum / count)); } return 0; }
Output (verified by compiling and running)
i raw avg(4) 0 100 100 1 104 102 2 98 100 3 250 138 4 102 138 5 101 137 6 99 138 7 103 101

The spike of 250 is reduced to an average of 138 and then fades as it leaves the window. For a real-time filter keep a running sum and a circular buffer instead of re-adding the window each time (cost O(1) instead of O(window)).

Example 3: Count and Reverse the Bits of a Byte

Problem: Count the set bits (parity checks, error detection) and reverse the bit order (needed when a peripheral sends LSB first but the protocol expects MSB first). Both loops make exactly 8 passes.

bit_loops.c
#include <stdio.h> #include <stdint.h> static unsigned popcount8(uint8_t x) { unsigned c = 0; for (int i = 0; i < 8; i++) c += (x >> i) & 1u; return c; } static uint8_t reverse8(uint8_t x) { uint8_t r = 0; for (int i = 0; i < 8; i++) { r = (uint8_t)((r << 1) | (x & 1u)); /* move the lowest bit of x to the bottom of r */ x >>= 1; } return r; } static void bits(uint8_t x) { for (int i = 7; i >= 0; i–) putchar(((x >> i) & 1u) ? ‘1’ : ‘0’); } int main(void) { uint8_t v[] = { 0x01, 0xA5, 0xF0, 0xFF }; for (int i = 0; i < 4; i++) { printf(“0x%02X = “, v[i]); bits(v[i]); printf(” ones=%u reversed=”, popcount8(v[i])); bits(reverse8(v[i])); printf(” (0x%02X)\n”, reverse8(v[i])); } return 0; }
Output (verified by compiling and running)
0x01 = 00000001 ones=1 reversed=10000000 (0x80) 0xA5 = 10100101 ones=4 reversed=10100101 (0xA5) 0xF0 = 11110000 ones=4 reversed=00001111 (0x0F) 0xFF = 11111111 ones=8 reversed=11111111 (0xFF)
Faster: The Cortex-M4 has a single RBIT instruction (reverse bits) available through the CMSIS function __RBIT(), and GCC offers __builtin_popcount(). The loops show the algorithm; use the intrinsics in production code.

Example 4: Software SPI: Shifting Out a Byte

Problem: Send a byte over two GPIO pins in SPI mode 0, most significant bit first. The counter runs from 7 down to 0 and is a signed int, so the test bit >= 0 works. On the STM32F407 the lines would be PB13 (SCK) and PB15 (MOSI) written through the BSRR register.

soft_spi.c
#include <stdio.h> #include <stdint.h> static int sck, mosi; /* the two output pins */ static void pin_change(void) { printf(” SCK=%d MOSI=%d\n”, sck, mosi); } static void spi_write_byte(uint8_t b) { for (int bit = 7; bit >= 0; bit–) /* MSB first, mode 0 */ { mosi = (b >> bit) & 1; /* put the data bit on the line */ sck = 0; pin_change(); sck = 1; pin_change(); /* rising edge: slave samples */ } sck = 0; } int main(void) { printf(“send 0xA6 (1010 0110):\n”); spi_write_byte(0xA6); return 0; }
Output (verified by compiling and running)
send 0xA6 (1010 0110): SCK=0 MOSI=1 SCK=1 MOSI=1 SCK=0 MOSI=0 SCK=1 MOSI=0 SCK=0 MOSI=1 SCK=1 MOSI=1 SCK=0 MOSI=0 SCK=1 MOSI=0 SCK=0 MOSI=0 SCK=1 MOSI=0 SCK=0 MOSI=1 SCK=1 MOSI=1 SCK=0 MOSI=1 SCK=1 MOSI=1 SCK=0 MOSI=0 SCK=1 MOSI=0

Read the output as a pairs of lines per bit: data first, then the clock rising edge at which the slave samples. The data bits appear as 1, 0, 1, 0, 0, 1, 1, 0, which is 0xA6.

Example 5: Bubble Sort of Sensor Readings

Problem: Sort six readings in ascending order so that the median can be picked. The outer loop counts the passes; the inner loop compares neighbours and shrinks by one each pass because the largest value has bubbled to the end. The swapped flag ends the sort early.

bubble_sort.c
#include <stdio.h> #define N 6 int main(void) { int a[N] = { 42, 7, 19, 3, 88, 12 }; for (int pass = 0; pass < N – 1; pass++) { int swapped = 0; for (int i = 0; i < N – 1 – pass; i++) /* the tail is already sorted */ { if (a[i] > a[i + 1]) { int t = a[i]; a[i] = a[i + 1]; a[i + 1] = t; swapped = 1; } } printf(“pass %d:”, pass + 1); for (int i = 0; i < N; i++) printf(” %2d”, a[i]); printf(“\n”); if (!swapped) break; /* already sorted: stop early */ } return 0; }
Output (verified by compiling and running)
pass 1: 7 19 3 42 12 88 pass 2: 7 3 19 12 42 88 pass 3: 3 7 12 19 42 88 pass 4: 3 7 12 19 42 88
Cost: Nested loops with n elements make about n²/2 comparisons. That is fine for 6 values, but for hundreds of samples use qsort or insertion sort on nearly-sorted data.

Example 6: CRC-8 of a Message

Problem: Protect a serial frame with a cyclic redundancy check. The outer loop takes bytes; the inner loop of exactly 8 passes shifts the register bit by bit and XORs the polynomial whenever the top bit falls out. The well-known check value for the ASCII string “123456789” with polynomial 0x07 is 0xF4.

crc8.c
#include <stdio.h> #include <stdint.h> /* CRC-8, polynomial 0x07 (x^8 + x^2 + x + 1), initial value 0, no reflection */ static uint8_t crc8(const uint8_t *data, unsigned len) { uint8_t crc = 0x00; for (unsigned i = 0; i < len; i++) { crc ^= data[i]; for (int bit = 0; bit < 8; bit++) { if (crc & 0x80u) crc = (uint8_t)((crc << 1) ^ 0x07u); else crc = (uint8_t)(crc << 1); } } return crc; } int main(void) { const uint8_t msg[] = “123456789”; printf(“CRC-8 of \”123456789\” = 0x%02X (standard check value is 0xF4)\n”, crc8(msg, 9)); uint8_t frame[] = { 0x01, 0x02, 0x03 }; printf(“CRC-8 of 01 02 03 = 0x%02X\n”, crc8(frame, 3)); frame[1] ^= 0x10; /* flip one bit */ printf(“after one bit flip = 0x%02X (differs: error detected)\n”, crc8(frame, 3)); return 0; }
Output (verified by compiling and running)
CRC-8 of “123456789” = 0xF4 (standard check value is 0xF4) CRC-8 of 01 02 03 = 0x48 after one bit flip = 0x1F (differs: error detected)
Speed: The bitwise form takes 8 loop passes per byte. A 256-entry lookup table does the same in one step per byte (256 bytes of flash), and the STM32F407 has a hardware CRC unit (32-bit, CRC->DR) for bulk data.

Example 7: Waveform Tables for a 12-bit DAC

Problem: Fill two lookup tables for the STM32F407’s 12-bit DAC (full scale 4095): a sawtooth ramp and a triangle wave. Timer-triggered DMA can then stream the table to the DAC at a fixed sample rate with zero CPU load.

wave_tables.c
#include <stdio.h> #include <stdint.h> #define STEPS 16 int main(void) { uint16_t saw[STEPS], tri[STEPS]; for (int i = 0; i < STEPS; i++) saw[i] = (uint16_t)(i * 4095 / (STEPS – 1)); /* 0 .. 4095 ramp */ for (int i = 0; i < STEPS; i++) { int up = (i < STEPS / 2) ? i : (STEPS – 1 – i); /* 0..7 then 7..0 */ tri[i] = (uint16_t)(up * 4095 / (STEPS / 2 – 1)); } printf(“idx saw tri\n”); for (int i = 0; i < STEPS; i++) printf(“%2d %4u %4u\n”, i, saw[i], tri[i]); return 0; }
Output (verified by compiling and running)
idx saw tri 0 0 0 1 273 585 2 546 1170 3 819 1755 4 1092 2340 5 1365 2925 6 1638 3510 7 1911 4095 8 2184 4095 9 2457 3510 10 2730 2925 11 3003 2340 12 3276 1755 13 3549 1170 14 3822 585 15 4095 0

Computing the table at start-up costs RAM; for a fixed waveform declare it static const so that the table lives in flash. Sine tables are made the same way, using sinf() in the loop on a PC and pasting the result.

Example 8: A 4×4 Keypad Scan with Nested Loops

Problem: A keypad connects 4 row lines (outputs) to 4 column lines (inputs). To find the pressed key, drive one row high at a time and read the columns. The outer loop selects the row, the inner loop tests the 4 column bits, and a lookup table turns the position into a character.

keypad.c
#include <stdio.h> #include <stdint.h> /* A 4×4 keypad: 4 row outputs (PD0-PD3), 4 column inputs (PE0-PE3). */ static const char keymap[4][4] = { {‘1’,‘2’,‘3’,‘A’}, {‘4’,‘5’,‘6’,‘B’}, {‘7’,‘8’,‘9’,‘C’}, {‘*’,‘0’,‘#’,‘D’} }; static int pressed_row = 2, pressed_col = 1; /* user holds key ‘8’ */ static unsigned read_columns(int row) { /* the simulated hardware: a column bit is 1 only for the pressed key’s row */ return (row == pressed_row) ? (1u << pressed_col) : 0u; } static char scan(void) { for (int row = 0; row < 4; row++) /* drive one row at a time */ { unsigned cols = read_columns(row); for (int col = 0; col < 4; col++) if (cols & (1u << col)) return keymap[row][col]; } return 0; } int main(void) { char k = scan(); printf(“key found: %c (row %d, col %d)\n”, k, pressed_row, pressed_col); pressed_row = –1; printf(“no key held: scan returns %d\n”, scan()); return 0; }
Output (verified by compiling and running)
key found: 8 (row 2, col 1) no key held: scan returns 0
Keypad matrix: 4 rows by 4 columns
col 0col 1col 2col 3
row 0123A
row 1456B
row 2789C
row 3*0#D

A real scanner also needs debouncing (Lesson 6.1 edge detection) and a short settling delay after each row change.

Example 9: Mini Exercises

Exercise 1: How many times does for (i = 2; i < 20; i += 4) run?
The values are 2, 6, 10, 14, 18: five times.
Exercise 2: What is i after for (i = 0; i < 7; i += 3);?
9. The values 0, 3, 6 pass the test and 9 fails it.
Exercise 3: Write a loop that clears all 16 bytes of buf in reverse order using a size_t counter.
for (size_t i = 16; i-- > 0; ) buf[i] = 0;
Exercise 4: How many comparisons does bubble sort make at most for n = 6?
5 + 4 + 3 + 2 + 1 = 15.
Exercise 5: How can the knight-rider sweep be written as one loop instead of two?
Loop over 0..5 and map to a pin with pos = i < 4 ? i : 6 - i.
Exercise 6: Why does the SPI loop use a signed counter?
The test bit >= 0 must be able to become false when the counter reaches -1.
Exercise 7: What is the trip count of the inner CRC loop per message byte?
Exactly 8.

Key Takeaways from the For Loop Examples in Embedded C

  • These for loop examples in embedded C show that the loop variable is often a bit position, a pin number or a table index.
  • Nested loops multiply: sorting and scanning both cost the product of their trip counts.
  • Down-counting loops need a signed counter or the i-- > 0 form.
  • Tables built in loops belong in flash when they are constant.
  • Check the loop limits against the data actually present (k <= i in the moving average).
  • Prefer hardware (CRC unit, DMA, RBIT) over loops for bulk work.

Ready for the quiz?

Next: predict loop output, find the off-by-one bug and answer the for loop interview questions.

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 *