DEV Community

Keerthinath
Keerthinath

Posted on

Getting Started with Embedded C: Blinking an LED on Raspberry Pi

Blinking an LED is the "Hello, World!" of embedded systems. It sounds simple, but it teaches you the most fundamental skill in embedded programming — controlling hardware directly from software. In this tutorial, we'll blink an LED on a Raspberry Pi using pure C, without any Python libraries or high-level frameworks. Just C, Linux, and hardware.
By the end, you'll understand GPIO control, memory-mapped I/O, and how to compile and run embedded-style C code on a Linux system.

What You'll Need

Raspberry Pi (any model: 3B, 4, or Zero W)
1× LED (any color)
1× 330Ω resistor
Breadboard and jumper wires
SSH access or a connected keyboard/monitor

Step 1: Understanding GPIO on Raspberry Pi
GPIO stands for General Purpose Input/Output. The Raspberry Pi exposes 40 pins on its header, many of which can be configured as digital inputs or outputs. When you set a GPIO pin HIGH, it outputs 3.3V. When you set it LOW, it outputs 0V.
We'll use GPIO 17 (physical pin 11) to drive our LED.
The Circuit
Connect your components like this:
Raspberry Pi Pin 11 (GPIO 17)
|
[330Ω Resistor]
|
[LED Anode (+)]
[LED Cathode (-)]
|
Raspberry Pi Pin 6 (GND)
The resistor is critical — it limits current through the LED so it doesn't burn out.

Step 2: Two Approaches to GPIO in C
There are two main ways to control GPIO from C on a Raspberry Pi:
ApproachMethodBest Forsysfs (filesystem interface)Write to /sys/class/gpio/Beginners, simple scriptsMemory-mapped I/ODirect register access via /dev/memAdvanced, bare-metal style
We'll start with sysfs — it's beginner-friendly and doesn't require root privileges for GPIO access (after setup). Then we'll peek at the memory-mapped approach for the embedded purists.

Step 3: Blink Using sysfs (Beginner Approach)
The Linux sysfs interface exposes GPIO pins as virtual files. You control them by reading and writing these files — perfect for learning the concept cleanly.
3.1 Enable GPIO Access Without Root (Optional)
By default, GPIO access requires root. To avoid running everything as root, add your user to the gpio group:
bashsudo usermod -aG gpio $USER

Log out and back in for this to take effect

3.2 Write the C Program
Create a file called blink_sysfs.c:
c#include

include

include

include

include

define GPIO_PIN "17"

define GPIO_EXPORT "/sys/class/gpio/export"

define GPIO_DIRECTION "/sys/class/gpio/gpio17/direction"

define GPIO_VALUE "/sys/class/gpio/gpio17/value"

define BLINK_COUNT 10

define DELAY_US 500000 // 500ms

/* Write a string to a sysfs file */
static int gpio_write(const char *path, const char *value) {
int fd = open(path, O_WRONLY);
if (fd < 0) {
perror("Failed to open GPIO file");
return -1;
}
write(fd, value, strlen(value));
close(fd);
return 0;
}

int main(void) {
int i;

/* Step 1: Export GPIO pin to userspace */
printf("Exporting GPIO %s...\n", GPIO_PIN);
if (gpio_write(GPIO_EXPORT, GPIO_PIN) < 0) {
    fprintf(stderr, "Export failed. Is the pin already exported?\n");
    /* Continue anyway — pin might already be exported */
}

/* Small delay to let sysfs create the GPIO directory */
usleep(100000);

/* Step 2: Set GPIO direction to output */
printf("Setting GPIO direction to output...\n");
if (gpio_write(GPIO_DIRECTION, "out") < 0) {
    fprintf(stderr, "Failed to set direction.\n");
    return EXIT_FAILURE;
}

/* Step 3: Blink the LED */
printf("Blinking LED %d times...\n", BLINK_COUNT);
for (i = 0; i < BLINK_COUNT; i++) {
    gpio_write(GPIO_VALUE, "1");    /* LED ON */
    printf("LED ON\n");
    usleep(DELAY_US);

    gpio_write(GPIO_VALUE, "0");    /* LED OFF */
    printf("LED OFF\n");
    usleep(DELAY_US);
}

/* Step 4: Cleanup — unexport the GPIO pin */
printf("Cleaning up...\n");
gpio_write("/sys/class/gpio/unexport", GPIO_PIN);

printf("Done!\n");
return EXIT_SUCCESS;
Enter fullscreen mode Exit fullscreen mode

}
3.3 Compile and Run
bash# Compile
gcc -o blink_sysfs blink_sysfs.c

Run (may need sudo if not in gpio group)

./blink_sysfs
Expected output:
Exporting GPIO 17...
Setting GPIO direction to output...
Blinking LED 10 times...
LED ON
LED OFF
LED ON
LED OFF
...
Done!
Your LED should blink 10 times at 0.5-second intervals. 🎉

Step 4: Understanding What Just Happened
Let's break down the key concepts:
GPIO Export
cgpio_write(GPIO_EXPORT, GPIO_PIN);
This tells the Linux kernel to expose GPIO 17 to userspace by creating the directory /sys/class/gpio/gpio17/. Before this, the pin is managed exclusively by the kernel.
Direction Control
cgpio_write(GPIO_DIRECTION, "out");
GPIO pins are bidirectional by default. Writing "out" configures pin 17 as a digital output. You could write "in" to read a button or sensor instead.
Value Control
cgpio_write(GPIO_VALUE, "1"); // HIGH — LED on
gpio_write(GPIO_VALUE, "0"); // LOW — LED off
This is the actual toggle. Writing "1" drives the pin to 3.3V; writing "0" pulls it to 0V.

Step 5: Advanced — Memory-Mapped GPIO (Embedded Style)
If you want to understand how embedded systems really work — directly accessing hardware registers — here's a peek at the memory-mapped approach. This bypasses the Linux filesystem entirely and writes directly to the BCM2835 peripheral registers.

⚠️ Warning: This requires root (sudo) and is hardware-specific to Raspberry Pi's BCM2835/BCM2837 SoC. Incorrect memory access can cause system instability.

c#include

include

include

include

include

include

/* BCM2835 peripheral base address (Pi 3) */

define BCM2835_PERI_BASE 0x3F000000

define GPIO_BASE (BCM2835_PERI_BASE + 0x200000)

define PAGE_SIZE (4 * 1024)

define BLOCK_SIZE (4 * 1024)

/* GPIO register offsets */

define GPFSEL1 1 /* GPIO Function Select 1 (pins 10-19) */

define GPSET0 7 /* GPIO Pin Output Set 0 */

define GPCLR0 10 /* GPIO Pin Output Clear 0 */

volatile uint32_t *gpio_map;

void gpio_setup(void) {
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0) {
perror("Cannot open /dev/mem");
exit(EXIT_FAILURE);
}

gpio_map = (volatile uint32_t *)mmap(
    NULL,
    BLOCK_SIZE,
    PROT_READ | PROT_WRITE,
    MAP_SHARED,
    mem_fd,
    GPIO_BASE
);

close(mem_fd);

if (gpio_map == MAP_FAILED) {
    perror("mmap failed");
    exit(EXIT_FAILURE);
}
Enter fullscreen mode Exit fullscreen mode

}

void gpio17_output(void) {
/* GPFSEL1 controls GPIO 10-19. GPIO 17 uses bits 21-23 /
gpio_map[GPFSEL1] &= ~(7 << 21); /
Clear bits 21-23 /
gpio_map[GPFSEL1] |= (1 << 21); /
Set as output (001) */
}

void gpio17_set(int value) {
if (value)
gpio_map[GPSET0] = (1 << 17); /* Set GPIO 17 HIGH /
else
gpio_map[GPCLR0] = (1 << 17); /
Set GPIO 17 LOW */
}

int main(void) {
gpio_setup();
gpio17_output();

for (int i = 0; i < 10; i++) {
    gpio17_set(1);
    printf("LED ON\n");
    usleep(500000);

    gpio17_set(0);
    printf("LED OFF\n");
    usleep(500000);
}

munmap((void *)gpio_map, BLOCK_SIZE);
return EXIT_SUCCESS;
Enter fullscreen mode Exit fullscreen mode

}
bash# Compile
gcc -o blink_mmap blink_mmap.c

Run as root

sudo ./blink_mmap
This is the same pattern used in bare-metal microcontroller programming — writing directly to memory-mapped peripheral registers. If you've worked with STM32 or AVR, this will look very familiar.

Step 6: Troubleshooting
ProblemSolutionPermission denied on sysfsRun with sudo or add user to gpio groupLED doesn't light upCheck resistor value and LED polaritymmap failedEnsure you're running as root for memory-mapped approachWrong GPIO numberDouble-check BCM vs physical pin numbering

Note on Pin Numbering: Raspberry Pi has two numbering schemes. We use BCM numbering (GPIO 17 = physical pin 11). Always verify using pinout command on your Pi.

What's Next?
Now that you can blink an LED, you're ready to explore:

Reading GPIO inputs — connect a button and read its state in C
PWM control — vary LED brightness using software PWM
I2C/SPI in C — communicate with sensors using Linux's i2c-dev interface
Writing a Linux kernel module — go truly bare-metal with a custom driver

Embedded C on Linux is a powerful combination. The same skills you've learned here — file descriptors, memory mapping, register manipulation — are the foundations of professional embedded Linux development.

Have questions or ran into issues? Drop them in the comments — I read every one.

Top comments (0)