History of Raspberry Pi
The Raspberry Pi story begins at the University of Cambridge's Computer Laboratory. In the mid-2000s, Eben Upton, Rob Mullins, Jack Lang, and Alan Mycroft noticed a troubling decline in the number and skill level of students applying to study computer science. Where previous generations had grown up programming BBC Micros and Amigas, the new applicants had primarily used computers as appliances — browsing the web and writing documents, but never writing code or understanding hardware.
Upton and his colleagues set out to build an affordable, programmable computer that could reignite the spirit of hands-on computing. The Raspberry Pi Foundation was established as a UK-registered charity in 2009, and development began on what would become the original Raspberry Pi Model B. The first prototype used an Atmel ATmega644 microcontroller, but the team quickly pivoted to Broadcom's BCM2835 system-on-chip (SoC), which offered a 700 MHz ARM11 core, a VideoCore IV GPU capable of 1080p video, and — critically — a price point that could hit the target of $35.
On February 29, 2012, the Raspberry Pi Model B went on sale. Demand was so overwhelming that both the RS Components and Farnell/element14 websites crashed within minutes. The initial production run of 10,000 units sold out in hours. Within its first year, over one million Pi boards had shipped — a staggering number for what was conceived as a niche educational tool.
Evolution of the Platform
The evolution of the Raspberry Pi tracks the rapid advancement of ARM-based SoCs. The original Model B (2012) featured a single-core ARM11 at 700 MHz with 256 MB of RAM. The Pi 2 Model B (February 2015) jumped to a quad-core Cortex-A7 at 900 MHz with 1 GB RAM — a dramatic leap in compute power. The Pi 3 Model B (February 2016) upgraded to a 64-bit Cortex-A53 and added integrated WiFi and Bluetooth, eliminating the need for USB dongles.
The Pi 4 Model B (June 2019) was a generational leap: a Cortex-A72 quad-core at 1.5 GHz, up to 8 GB RAM, USB 3.0, Gigabit Ethernet with no shared bus, and dual micro-HDMI outputs supporting 4K. For the first time, a Raspberry Pi could genuinely serve as a desktop replacement for everyday tasks.
In October 2023, the Raspberry Pi 5 arrived with a custom-designed RP1 I/O controller chip — the first time the Raspberry Pi Foundation had designed its own silicon for I/O management. The Pi 5 features a quad-core Cortex-A76 at 2.4 GHz, delivering 2-3x the performance of the Pi 4, with PCIe 2.0 x1 for NVMe storage, a dedicated UART connector for debugging, and a real-time clock with battery backup support.
Meanwhile, the Raspberry Pi Pico (January 2021) marked the Foundation's entry into microcontrollers with the custom-designed RP2040 chip — dual Cortex-M0+ cores at 133 MHz with programmable I/O (PIO) state machines. At $4, it opened up real-time, low-power applications that Linux-based Pis cannot efficiently address.
| Milestone | Date | Significance |
|---|---|---|
| Foundation established | 2009 | UK charity formed to promote CS education |
| Model B launch | Feb 2012 | First $35 single-board computer, 10K units sold out in hours |
| Model A launch | Feb 2013 | $25 stripped-down version, 256 MB RAM |
| Pi 2 Model B | Feb 2015 | Quad-core Cortex-A7, 1 GB RAM |
| Pi Zero | Nov 2015 | $5 computer, single-core 1 GHz |
| Pi 3 Model B | Feb 2016 | 64-bit Cortex-A53, onboard WiFi/BLE |
| Pi 4 Model B | Jun 2019 | Cortex-A72, up to 8 GB, USB 3.0, 4K dual display |
| Pi Pico | Jan 2021 | RP2040 microcontroller, $4, PIO state machines |
| Pi Zero 2 W | Oct 2021 | Quad-core Cortex-A53 at $15 |
| Pi 5 | Oct 2023 | Cortex-A76, custom RP1 southbridge, PCIe 2.0 |
| 60M units sold | 2024 | One of the best-selling computers of all time |
The Raspberry Pi's impact extends far beyond education. It has become the de facto prototyping platform for IoT products, a popular choice for home automation servers, a staple in industrial edge computing, and even found its way into space. Its combination of Linux capability, GPIO access, camera interfaces, and community support makes it uniquely suited for embedded prototyping where rapid iteration matters more than real-time determinism.
Hardware Overview
Choosing the right Raspberry Pi model is the first decision in any embedded project. Each model targets a different balance of processing power, I/O capability, power consumption, and cost. Understanding the hardware differences lets you match the board to your application requirements.
Model Comparison
| Feature | Pi 5 | Pi 4 Model B | Pi Zero 2 W | Pi Pico / Pico W |
|---|---|---|---|---|
| CPU | Cortex-A76 x4 @ 2.4 GHz | Cortex-A72 x4 @ 1.5 GHz | Cortex-A53 x4 @ 1 GHz | Cortex-M0+ x2 @ 133 MHz |
| RAM | 4/8 GB LPDDR4X | 1/2/4/8 GB LPDDR4 | 512 MB LPDDR2 | 264 KB SRAM |
| GPIO Pins | 40 | 40 | 40 | 26 (30 total) |
| USB | 2x USB 3.0, 2x USB 2.0 | 2x USB 3.0, 2x USB 2.0 | 1x micro-USB (OTG) | 1x micro-USB |
| Networking | GbE, WiFi 5, BLE 5.0 | GbE, WiFi 5, BLE 5.0 | WiFi 4, BLE 4.2 | None / WiFi 4 (W) |
| Storage | microSD + PCIe NVMe | microSD | microSD | 2 MB Flash |
| Display | 2x HDMI (4Kp60) | 2x micro-HDMI (4Kp30) | mini-HDMI | None (SPI/I2C displays) |
| Camera | 2x MIPI CSI-2 | 1x MIPI CSI-2 | 1x MIPI CSI-2 (mini) | None |
| Power | 5V/5A USB-C (PD) | 5V/3A USB-C | 5V/2.5A micro-USB | 1.8-5.5V via VSYS |
| Price | $60 / $80 | $35-$75 | $15 | $4 / $6 |
| Best For | Edge AI, desktop, NVMe | General prototyping | Compact IoT, wearables | Real-time, low-power, sensors |
The 40-Pin GPIO Header
All full-sized Raspberry Pi models share a standardized 40-pin GPIO header (the Pi 1 Model A/B had 26 pins). This header provides access to:
- 28 GPIO pins — configurable as digital input/output with internal pull-up/down resistors
- 2x SPI buses — SPI0 (CE0, CE1) and SPI1 (CE0, CE1, CE2)
- 2x I2C buses — I2C1 (GPIO 2/3) for user devices, I2C0 (GPIO 0/1) for HAT EEPROM
- 2x UART — UART0 (GPIO 14/15) and UART2-5 via alt functions
- 2x PWM channels — hardware PWM on GPIO 12/13/18/19
- Power pins — 5V (pins 2/4), 3.3V (pins 1/17), Ground (pins 6/9/14/20/25/30/34/39)
Power Architecture
Understanding power delivery is critical for reliable embedded projects. The Pi 5 uses a dedicated PMIC (power management IC) for the first time — the DA9091. This enables proper power sequencing, voltage monitoring, and the ability to power via the new PCIe connector. The Pi 4 and earlier use discrete regulators.
A fully loaded Pi 5 with NVMe drive, camera, and USB peripherals can draw up to 25W. The official 27W USB-C PD power supply is recommended. For battery-powered applications, the Pi Zero 2 W at approximately 350 mA typical (1.75W) or the Pi Pico at under 50 mA is far more practical.
GPIO Programming
GPIO (General Purpose Input/Output) programming is the foundation of all Raspberry Pi embedded projects. The Pi ecosystem offers multiple libraries at different abstraction levels, from high-level Python wrappers to direct register manipulation in C.
Library Comparison
| Library | Language | Level | Pi 5 Support | Notes |
|---|---|---|---|---|
| gpiozero | Python | High | Yes (via lgpio) | Official, beginner-friendly, device-oriented API |
| RPi.GPIO | Python | Medium | No (deprecated) | Legacy, direct /dev/mem access, Pi 4 and earlier only |
| lgpio | Python/C | Medium | Yes | Uses /dev/gpiochip, Pi 5 compatible, replaces RPi.GPIO |
| pigpio | Python/C | Low | No (daemon-based) | Sub-microsecond timing via daemon, Pi 4 and earlier |
| libgpiod | C/Python | Low | Yes | Linux kernel GPIO character device interface |
| WiringPi | C | Low | Unofficial fork | Original deprecated 2019, community fork exists |
Digital Output with gpiozero
The gpiozero library is the official recommended approach for Python GPIO programming. It provides a device-oriented abstraction where you work with LEDs, buttons, motors, and sensors rather than raw pin numbers.
#!/usr/bin/env python3
"""LED blink example using gpiozero - works on Pi 3/4/5."""
from gpiozero import LED, Button, PWMLED
from time import sleep
from signal import pause
# Simple LED blink on GPIO 17
led = LED(17)
print("Blinking LED on GPIO 17...")
for i in range(10):
led.on()
sleep(0.5)
led.off()
sleep(0.5)
# PWM LED (fade in/out) on GPIO 18
pwm_led = PWMLED(18)
print("Fading LED on GPIO 18...")
for brightness in range(0, 101, 5):
pwm_led.value = brightness / 100.0
sleep(0.05)
for brightness in range(100, -1, -5):
pwm_led.value = brightness / 100.0
sleep(0.05)
# Button with callback on GPIO 27
button = Button(27, pull_up=True, bounce_time=0.05)
def on_press():
print("Button pressed!")
led.toggle()
def on_release():
print("Button released!")
button.when_pressed = on_press
button.when_released = on_release
print("Waiting for button presses... (Ctrl+C to exit)")
pause()
Digital I/O with lgpio (Pi 5 Compatible)
For Pi 5, lgpio is the recommended low-level library. It accesses GPIO through the Linux /dev/gpiochip interface rather than direct memory mapping, which is necessary because the Pi 5's RP1 chip manages GPIO.
#!/usr/bin/env python3
"""GPIO programming with lgpio - Pi 5 compatible."""
import lgpio
import time
# Open gpiochip4 on Pi 5 (gpiochip0 on Pi 4 and earlier)
CHIP = 4 # Change to 0 for Pi 4/3/Zero
h = lgpio.gpiochip_open(CHIP)
# Configure GPIO 17 as output
LED_PIN = 17
lgpio.gpio_claim_output(h, LED_PIN)
# Configure GPIO 27 as input with pull-up
BUTTON_PIN = 27
lgpio.gpio_claim_input(h, BUTTON_PIN, lgpio.SET_PULL_UP)
# Blink LED
print("Blinking LED...")
for i in range(10):
lgpio.gpio_write(h, LED_PIN, 1)
time.sleep(0.3)
lgpio.gpio_write(h, LED_PIN, 0)
time.sleep(0.3)
# Read button state
print("Reading button (press Ctrl+C to stop)...")
try:
while True:
state = lgpio.gpio_read(h, BUTTON_PIN)
if state == 0: # Active low with pull-up
print("Button pressed!")
lgpio.gpio_write(h, LED_PIN, 1)
else:
lgpio.gpio_write(h, LED_PIN, 0)
time.sleep(0.05)
except KeyboardInterrupt:
pass
finally:
lgpio.gpiochip_close(h)
print("GPIO cleanup complete.")
Hardware PWM in C
For precise PWM control — servo motors, motor speed control, or LED dimming — C provides direct access to the hardware PWM peripheral. The Pi has two hardware PWM channels that can be mapped to specific GPIO pins.
/* hardware_pwm.c - Direct hardware PWM control via sysfs
* Compile: gcc -o hardware_pwm hardware_pwm.c
* Run: sudo ./hardware_pwm
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define PWM_CHIP "/sys/class/pwm/pwmchip0"
#define PWM_CHANNEL "0" /* PWM0 = GPIO 18 */
static void write_to_file(const char *path, const char *value) {
int fd = open(path, O_WRONLY);
if (fd < 0) { perror(path); exit(1); }
write(fd, value, strlen(value));
close(fd);
}
int main(void) {
char path[256];
/* Export PWM channel */
snprintf(path, sizeof(path), "%s/export", PWM_CHIP);
write_to_file(path, PWM_CHANNEL);
usleep(100000); /* Wait for sysfs to create files */
/* Set period to 20ms (50Hz - standard servo) */
snprintf(path, sizeof(path), "%s/pwm%s/period", PWM_CHIP, PWM_CHANNEL);
write_to_file(path, "20000000"); /* 20ms in nanoseconds */
/* Set duty cycle to 1.5ms (servo center position) */
snprintf(path, sizeof(path), "%s/pwm%s/duty_cycle", PWM_CHIP, PWM_CHANNEL);
write_to_file(path, "1500000"); /* 1.5ms in nanoseconds */
/* Enable PWM */
snprintf(path, sizeof(path), "%s/pwm%s/enable", PWM_CHIP, PWM_CHANNEL);
write_to_file(path, "1");
printf("PWM active on GPIO 18 (50Hz, 1.5ms pulse)\n");
printf("Sweeping servo...\n");
/* Sweep servo from 1ms to 2ms */
for (int duty_us = 1000; duty_us <= 2000; duty_us += 50) {
char duty_str[32];
snprintf(duty_str, sizeof(duty_str), "%d", duty_us * 1000);
snprintf(path, sizeof(path), "%s/pwm%s/duty_cycle",
PWM_CHIP, PWM_CHANNEL);
write_to_file(path, duty_str);
usleep(100000); /* 100ms between steps */
}
/* Disable and unexport */
snprintf(path, sizeof(path), "%s/pwm%s/enable", PWM_CHIP, PWM_CHANNEL);
write_to_file(path, "0");
snprintf(path, sizeof(path), "%s/unexport", PWM_CHIP);
write_to_file(path, PWM_CHANNEL);
printf("PWM disabled.\n");
return 0;
}
Interrupts and Edge Detection
Polling GPIO pins wastes CPU cycles. Edge-triggered interrupts allow the kernel to wake your program only when a pin changes state. The lgpio library supports callbacks on rising, falling, or both edges.
#!/usr/bin/env python3
"""GPIO interrupt handling with lgpio - efficient edge detection."""
import lgpio
import time
CHIP = 4 # Pi 5 (use 0 for Pi 4)
BUTTON_PIN = 27
LED_PIN = 17
h = lgpio.gpiochip_open(CHIP)
lgpio.gpio_claim_output(h, LED_PIN)
lgpio.gpio_claim_alert(h, BUTTON_PIN, lgpio.FALLING_EDGE, lgpio.SET_PULL_UP)
press_count = 0
last_press_time = 0
def button_callback(chip, gpio, level, timestamp):
global press_count, last_press_time
now = time.time()
# Software debounce: ignore presses within 200ms
if now - last_press_time < 0.2:
return
last_press_time = now
press_count += 1
print(f"Button press #{press_count} at {timestamp}")
# Toggle LED
current = lgpio.gpio_read(h, LED_PIN)
lgpio.gpio_write(h, LED_PIN, 1 - current)
# Register callback
cb = lgpio.callback(h, BUTTON_PIN, lgpio.FALLING_EDGE, button_callback)
print("Interrupt-driven button handler active. Press Ctrl+C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
cb.cancel()
lgpio.gpiochip_close(h)
print(f"\nTotal presses: {press_count}")
SPI, I2C & UART Communication
The Raspberry Pi's serial interfaces — SPI, I2C, and UART — connect it to the vast ecosystem of embedded sensors, displays, memory chips, and communication modules. Each protocol has distinct characteristics that make it suited for different applications.
Enabling Interfaces
# Enable SPI, I2C, and serial via raspi-config
sudo raspi-config nonint do_spi 0 # Enable SPI
sudo raspi-config nonint do_i2c 0 # Enable I2C
sudo raspi-config nonint do_serial_hw 0 # Enable hardware UART
# Or edit /boot/firmware/config.txt directly (Pi 5)
# dtparam=spi=on
# dtparam=i2c_arm=on
# enable_uart=1
# Verify interfaces are active
ls /dev/spidev* # Should show /dev/spidev0.0, /dev/spidev0.1
ls /dev/i2c* # Should show /dev/i2c-1
ls /dev/ttyAMA* # Should show /dev/ttyAMA0 (Pi 5) or /dev/ttyS0
# Install Python libraries
pip install spidev smbus2 pyserial
# Scan I2C bus for connected devices
sudo i2cdetect -y 1
I2C: Reading a BME280 Sensor
I2C (Inter-Integrated Circuit) is the most common protocol for connecting sensors to the Pi. It uses just two wires (SDA + SCL) and supports multiple devices on the same bus, each with a unique 7-bit address.
#!/usr/bin/env python3
"""BME280 temperature, pressure, humidity sensor via I2C.
Wiring: VCC->3.3V, GND->GND, SDA->GPIO2, SCL->GPIO3
"""
import smbus2
import time
import struct
BME280_ADDR = 0x76 # or 0x77 if SDO is pulled high
class BME280:
def __init__(self, bus_num=1, addr=BME280_ADDR):
self.bus = smbus2.SMBus(bus_num)
self.addr = addr
self._read_calibration()
# Set oversampling: temp x2, pressure x16, humidity x1
self.bus.write_byte_data(addr, 0xF2, 0x01) # ctrl_hum
self.bus.write_byte_data(addr, 0xF4, 0x57) # ctrl_meas (normal mode)
self.bus.write_byte_data(addr, 0xF5, 0xA0) # config (1000ms standby)
def _read_calibration(self):
"""Read factory calibration data from sensor registers."""
cal1 = self.bus.read_i2c_block_data(self.addr, 0x88, 26)
cal2 = self.bus.read_i2c_block_data(self.addr, 0xE1, 7)
self.dig_T1 = struct.unpack_from('<H', bytes(cal1), 0)[0]
self.dig_T2 = struct.unpack_from('<h', bytes(cal1), 2)[0]
self.dig_T3 = struct.unpack_from('<h', bytes(cal1), 4)[0]
self.dig_P1 = struct.unpack_from('<H', bytes(cal1), 6)[0]
self.dig_P2 = struct.unpack_from('<h', bytes(cal1), 8)[0]
self.dig_H1 = cal1[25]
self.dig_H2 = struct.unpack_from('<h', bytes(cal2), 0)[0]
self.dig_H3 = cal2[2]
def read(self):
"""Read compensated temperature, pressure, humidity."""
data = self.bus.read_i2c_block_data(self.addr, 0xF7, 8)
raw_p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
raw_t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
raw_h = (data[6] << 8) | data[7]
# Temperature compensation
var1 = ((raw_t / 16384.0) - (self.dig_T1 / 1024.0)) * self.dig_T2
var2 = (((raw_t / 131072.0) - (self.dig_T1 / 8192.0)) ** 2) * self.dig_T3
t_fine = var1 + var2
temperature = t_fine / 5120.0
# Pressure compensation (simplified)
var1 = t_fine / 2.0 - 64000.0
var2 = var1 * var1 * self.dig_P2 / 32768.0 + var1 * self.dig_P2 * 2.0
pressure = raw_p / (1048576.0 - self.dig_P1) if self.dig_P1 != 0 else 0
pressure = max(pressure, 300.0) # Sanity clamp
return {
'temperature': round(temperature, 2),
'pressure': round(pressure, 2),
'humidity': round(min(max(raw_h / 1024.0, 0), 100), 2)
}
if __name__ == '__main__':
sensor = BME280()
print("BME280 sensor readings (Ctrl+C to stop):\n")
while True:
data = sensor.read()
print(f" Temp: {data['temperature']}C "
f"Press: {data['pressure']} hPa "
f"Humid: {data['humidity']}%")
time.sleep(2)
SPI: Driving an MCP3008 ADC
SPI (Serial Peripheral Interface) is faster than I2C and commonly used for ADCs, DACs, displays, and high-speed sensors. The MCP3008 is a popular 10-bit ADC that provides 8 analog input channels — something the Pi lacks natively.
#!/usr/bin/env python3
"""MCP3008 10-bit ADC via SPI - 8 analog input channels.
Wiring: VDD/VREF->3.3V, AGND/DGND->GND,
CLK->GPIO11(SCLK), DOUT->GPIO9(MISO),
DIN->GPIO10(MOSI), CS->GPIO8(CE0)
"""
import spidev
import time
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, CE0
spi.max_speed_hz = 1350000 # 1.35 MHz (MCP3008 max at 3.3V)
spi.mode = 0 # CPOL=0, CPHA=0
def read_adc(channel):
"""Read single-ended value from MCP3008 channel (0-7).
Returns 10-bit value (0-1023).
"""
if channel < 0 or channel > 7:
raise ValueError("Channel must be 0-7")
# MCP3008 protocol: start bit, single/diff, channel bits
cmd = [0x01, (0x08 | channel) << 4, 0x00]
resp = spi.xfer2(cmd)
# Extract 10-bit result from response
value = ((resp[1] & 0x03) << 8) | resp[2]
return value
def read_voltage(channel, vref=3.3):
"""Read voltage from MCP3008 channel."""
return read_adc(channel) * vref / 1023.0
# Read all 8 channels
print("MCP3008 ADC Readings:")
print("-" * 40)
for ch in range(8):
raw = read_adc(ch)
voltage = read_voltage(ch)
print(f" CH{ch}: {raw:4d} ({voltage:.3f}V)")
# Continuous monitoring of channel 0 (e.g., potentiometer)
print("\nMonitoring CH0 (Ctrl+C to stop):")
try:
while True:
v = read_voltage(0)
bar = '#' * int(v / 3.3 * 40)
print(f"\r CH0: {v:.3f}V [{bar:<40s}]", end='')
time.sleep(0.1)
except KeyboardInterrupt:
spi.close()
print("\nDone.")
UART: Serial Communication
#!/usr/bin/env python3
"""UART serial communication - GPS module example.
Wiring: GPS TX -> GPIO 15 (RXD), GPS RX -> GPIO 14 (TXD)
"""
import serial
import time
# Open UART - /dev/ttyAMA0 on Pi 5, /dev/ttyS0 on Pi 3/4
ser = serial.Serial(
port='/dev/ttyAMA0',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
def parse_gpgga(sentence):
"""Parse NMEA GPGGA sentence for position data."""
parts = sentence.split(',')
if len(parts) < 15 or parts[0] != '$GPGGA':
return None
try:
lat = float(parts[2][:2]) + float(parts[2][2:]) / 60
if parts[3] == 'S': lat = -lat
lon = float(parts[4][:3]) + float(parts[4][3:]) / 60
if parts[5] == 'W': lon = -lon
alt = float(parts[9]) if parts[9] else 0
sats = int(parts[7]) if parts[7] else 0
return {'lat': lat, 'lon': lon, 'alt': alt, 'satellites': sats}
except (ValueError, IndexError):
return None
print("Reading GPS data via UART (Ctrl+C to stop)...")
try:
while True:
line = ser.readline().decode('ascii', errors='replace').strip()
if line.startswith('$GPGGA'):
pos = parse_gpgga(line)
if pos and pos['satellites'] > 0:
print(f" Lat: {pos['lat']:.6f} Lon: {pos['lon']:.6f} "
f"Alt: {pos['alt']:.1f}m Sats: {pos['satellites']}")
except KeyboardInterrupt:
ser.close()
print("\nUART closed.")
Camera & Display Interfaces
The Raspberry Pi's CSI (Camera Serial Interface) and DSI (Display Serial Interface) connectors provide high-bandwidth, low-latency connections to camera modules and touchscreen displays. The Pi 5 adds a second CSI/DSI port, enabling dual-camera setups for stereo vision.
Camera Module with picamera2
The picamera2 library replaced the legacy picamera and provides a Python interface to the libcamera stack. It supports the official Camera Module 3 (12.3 MP, autofocus), Camera Module 2 (8 MP), and the HQ Camera (12.3 MP, interchangeable lenses).
#!/usr/bin/env python3
"""Camera capture and streaming with picamera2."""
from picamera2 import Picamera2, Preview
from libcamera import controls
import time
import numpy as np
picam2 = Picamera2()
# Configure for still capture (full resolution)
still_config = picam2.create_still_configuration(
main={"size": (4056, 3040), "format": "RGB888"},
controls={"AfMode": controls.AfModeEnum.Continuous}
)
# Configure for video/preview (lower resolution)
video_config = picam2.create_video_configuration(
main={"size": (1920, 1080), "format": "RGB888"},
controls={"FrameRate": 30}
)
# --- Still Capture ---
picam2.configure(still_config)
picam2.start()
time.sleep(2) # Allow auto-exposure to settle
# Capture high-resolution still
picam2.capture_file("capture.jpg")
print("Saved capture.jpg (4056x3040)")
# Capture as numpy array for processing
array = picam2.capture_array()
print(f"Array shape: {array.shape}, dtype: {array.dtype}")
avg_brightness = np.mean(array)
print(f"Average brightness: {avg_brightness:.1f}")
picam2.stop()
# --- Video Recording ---
picam2.configure(video_config)
encoder = picam2.create_encoder("h264")
picam2.start_recording(encoder, "video.h264", duration=10)
print("Recording 10 seconds of 1080p video...")
time.sleep(10)
picam2.stop_recording()
print("Saved video.h264")
# --- Time-lapse ---
picam2.configure(still_config)
picam2.start()
print("Starting time-lapse (1 frame every 30 seconds)...")
for i in range(10):
filename = f"timelapse_{i:04d}.jpg"
picam2.capture_file(filename)
print(f" Captured {filename}")
time.sleep(30)
picam2.stop()
print("Time-lapse complete.")
Framebuffer Display Output
For headless embedded projects, you can drive small SPI/I2C displays through the Linux framebuffer or directly via SPI. Common choices include 128x64 OLED displays (SSD1306 via I2C) and 320x240 or 480x320 TFT displays (ILI9341 via SPI).
#!/usr/bin/env python3
"""SSD1306 OLED display via I2C using Pillow + adafruit_ssd1306."""
import board
import busio
import adafruit_ssd1306
from PIL import Image, ImageDraw, ImageFont
import psutil
import time
# Initialize I2C and OLED
i2c = busio.I2C(board.SCL, board.SDA)
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
# Clear display
oled.fill(0)
oled.show()
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
def draw_system_stats():
"""Draw CPU, memory, temperature, and disk usage on OLED."""
image = Image.new("1", (128, 64))
draw = ImageDraw.Draw(image)
cpu = psutil.cpu_percent(interval=0.5)
mem = psutil.virtual_memory().percent
disk = psutil.disk_usage('/').percent
# Read CPU temperature
with open('/sys/class/thermal/thermal_zone0/temp') as f:
temp = float(f.read()) / 1000.0
draw.text((0, 0), f"CPU: {cpu:5.1f}%", font=font, fill=255)
draw.text((0, 14), f"Mem: {mem:5.1f}%", font=font, fill=255)
draw.text((0, 28), f"Disk: {disk:5.1f}%", font=font, fill=255)
draw.text((0, 42), f"Temp: {temp:5.1f}C", font=font, fill=255)
# CPU usage bar
draw.rectangle((80, 2, 127, 10), outline=255)
draw.rectangle((80, 2, 80 + int(cpu * 0.47), 10), fill=255)
oled.image(image)
oled.show()
print("Displaying system stats on OLED (Ctrl+C to stop)...")
try:
while True:
draw_system_stats()
time.sleep(2)
except KeyboardInterrupt:
oled.fill(0)
oled.show()
print("Display cleared.")
Linux Device Drivers on Pi
The Raspberry Pi runs a full Linux kernel, and its peripherals are managed through the Linux device driver framework. Understanding device trees, kernel modules, and the /dev filesystem gives you precise control over hardware that high-level libraries abstract away.
Device Tree Overlays
The device tree is a data structure that describes hardware to the Linux kernel. Instead of hardcoding peripheral addresses and configurations in the kernel, the device tree tells the kernel what hardware exists, where it is mapped, and which driver to use. Overlays let you modify the device tree at boot to enable or configure peripherals.
# List available overlays
ls /boot/firmware/overlays/*.dtbo | head -20
# View current device tree configuration
cat /boot/firmware/config.txt | grep -E "^dtoverlay|^dtparam"
# Common overlays for embedded projects
# Add to /boot/firmware/config.txt:
# Enable SPI with additional chip selects
dtoverlay=spi0-2cs
# Enable I2C with custom bus speed (400 kHz fast mode)
dtparam=i2c_arm=on
dtparam=i2c_arm_baudrate=400000
# Enable hardware PWM on GPIO 18 and 19
dtoverlay=pwm-2chan,pin=18,func=2,pin2=19,func2=2
# Enable a DS18B20 one-wire temperature sensor on GPIO 4
dtoverlay=w1-gpio,gpiopin=4
# Enable an additional UART on GPIO 0/1
dtoverlay=uart2
# Enable SPI1 with 3 chip selects
dtoverlay=spi1-3cs
# Verify device tree at runtime
dtc -I fs /proc/device-tree 2>/dev/null | head -50
# Check which overlays are loaded
vcdbg log msg 2>&1 | grep -i overlay
The /dev/gpiochip Interface
Modern Linux GPIO access uses the GPIO character device interface (/dev/gpiochipN) rather than the deprecated /sys/class/gpio sysfs interface. On the Pi 5, /dev/gpiochip4 controls the 40-pin header; on Pi 4 and earlier, it is /dev/gpiochip0.
# List available GPIO chips
gpiodetect
# Output on Pi 5:
# gpiochip0 [pinctrl-bcm2712] (32 lines)
# gpiochip4 [pinctrl-rp1] (54 lines) <-- 40-pin header
# Show GPIO line info
gpioinfo gpiochip4 | head -20
# Read a GPIO line (e.g., button on line 27)
gpioget gpiochip4 27
# Set a GPIO line (e.g., LED on line 17)
gpioset gpiochip4 17=1 # Turn on
gpioset gpiochip4 17=0 # Turn off
# Monitor GPIO events (edge detection)
gpiomon --falling-edge gpiochip4 27
Writing a Simple Kernel Module
/* gpio_led_module.c - Minimal kernel module to blink an LED.
* Build: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
* Load: sudo insmod gpio_led_module.ko
* Remove: sudo rmmod gpio_led_module
*/
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#define LED_GPIO 17
#define BLINK_PERIOD_MS 500
static struct timer_list blink_timer;
static bool led_state = false;
static void blink_callback(struct timer_list *t)
{
led_state = !led_state;
gpio_set_value(LED_GPIO, led_state);
mod_timer(&blink_timer,
jiffies + msecs_to_jiffies(BLINK_PERIOD_MS));
}
static int __init led_init(void)
{
int ret;
ret = gpio_request(LED_GPIO, "blink_led");
if (ret) {
pr_err("gpio_led: failed to request GPIO %d\n", LED_GPIO);
return ret;
}
gpio_direction_output(LED_GPIO, 0);
timer_setup(&blink_timer, blink_callback, 0);
mod_timer(&blink_timer,
jiffies + msecs_to_jiffies(BLINK_PERIOD_MS));
pr_info("gpio_led: blinking LED on GPIO %d\n", LED_GPIO);
return 0;
}
static void __exit led_exit(void)
{
del_timer_sync(&blink_timer);
gpio_set_value(LED_GPIO, 0);
gpio_free(LED_GPIO);
pr_info("gpio_led: module removed\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wasil Zafar");
MODULE_DESCRIPTION("GPIO LED blink kernel module");
Bare-Metal Pi Programming
Bare-metal programming means running code directly on the processor without an operating system. On a Raspberry Pi, this involves writing to hardware registers directly, managing your own interrupt vector table, and bootstrapping the ARM core from the GPU bootloader. While impractical for most projects (use the Pi Pico instead), bare-metal Pi programming is an excellent educational exercise for understanding ARM architecture.
The Boot Process
The Raspberry Pi boot sequence is unique. Unlike most ARM boards where the CPU starts first, the Pi's VideoCore GPU boots first:
- GPU ROM loads
bootcode.binfrom the SD card's FAT32 partition bootcode.binloadsstart.elf(the GPU firmware)start.elfreadsconfig.txtfor configuration parametersstart.elfloadskernel8.img(orkernel.img) to memory address 0x80000 (Pi 3/4) or 0x0 (Pi 1/2)- GPU releases the ARM CPU from reset, which begins executing at the kernel load address
Bare-Metal UART Output
/* bare_metal_uart.c - Bare-metal UART on Raspberry Pi 3/4.
* Cross-compile: aarch64-none-elf-gcc -ffreestanding -nostdlib
* -o kernel8.img bare_metal_uart.c boot.S
*/
#include <stdint.h>
/* BCM2837 peripheral base (Pi 3) - use 0xFE000000 for Pi 4 */
#define MMIO_BASE 0x3F000000
/* GPIO registers */
#define GPFSEL1 (*(volatile uint32_t *)(MMIO_BASE + 0x00200004))
#define GPPUD (*(volatile uint32_t *)(MMIO_BASE + 0x00200094))
#define GPPUDCLK0 (*(volatile uint32_t *)(MMIO_BASE + 0x00200098))
/* Mini UART (UART1) registers */
#define AUX_ENABLES (*(volatile uint32_t *)(MMIO_BASE + 0x00215004))
#define AUX_MU_IO_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215040))
#define AUX_MU_IER_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215044))
#define AUX_MU_IIR_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215048))
#define AUX_MU_LCR_REG (*(volatile uint32_t *)(MMIO_BASE + 0x0021504C))
#define AUX_MU_MCR_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215050))
#define AUX_MU_LSR_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215054))
#define AUX_MU_CNTL_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215060))
#define AUX_MU_BAUD_REG (*(volatile uint32_t *)(MMIO_BASE + 0x00215068))
static void delay(uint32_t count) {
while (count--) { asm volatile("nop"); }
}
void uart_init(void) {
AUX_ENABLES = 1; /* Enable mini UART */
AUX_MU_CNTL_REG = 0; /* Disable TX/RX during config */
AUX_MU_IER_REG = 0; /* Disable interrupts */
AUX_MU_LCR_REG = 3; /* 8-bit mode */
AUX_MU_MCR_REG = 0; /* RTS high */
AUX_MU_BAUD_REG = 270; /* 115200 baud at 250 MHz */
/* Map GPIO 14/15 to alt5 (mini UART) */
uint32_t ra = GPFSEL1;
ra &= ~(7 << 12); /* Clear GPIO 14 */
ra |= (2 << 12); /* Alt5 for GPIO 14 */
ra &= ~(7 << 15); /* Clear GPIO 15 */
ra |= (2 << 15); /* Alt5 for GPIO 15 */
GPFSEL1 = ra;
/* Disable pull-up/down for GPIO 14, 15 */
GPPUD = 0;
delay(150);
GPPUDCLK0 = (1 << 14) | (1 << 15);
delay(150);
GPPUDCLK0 = 0;
AUX_MU_CNTL_REG = 3; /* Enable TX and RX */
}
void uart_putc(char c) {
while (!(AUX_MU_LSR_REG & 0x20)) {} /* Wait for TX ready */
AUX_MU_IO_REG = (uint32_t)c;
}
void uart_puts(const char *s) {
while (*s) {
if (*s == '\n') uart_putc('\r');
uart_putc(*s++);
}
}
/* Entry point called from boot.S */
void kernel_main(void) {
uart_init();
uart_puts("Hello from bare-metal Raspberry Pi!\n");
uart_puts("No OS, no drivers, just raw hardware.\n");
while (1) {
uart_putc(AUX_MU_IO_REG & 0xFF); /* Echo received chars */
}
}
IoT Project Walkthroughs
Project 1: Weather Station with BME280 + MQTT
This project combines a BME280 environmental sensor with MQTT messaging to create a wireless weather station that publishes data to a broker (e.g., Mosquitto) for dashboarding with Grafana or Home Assistant.
#!/usr/bin/env python3
"""IoT Weather Station - BME280 sensor + MQTT publishing.
Publishes temperature, humidity, and pressure every 60 seconds.
"""
import paho.mqtt.client as mqtt
import smbus2
import json
import time
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s')
# MQTT Configuration
MQTT_BROKER = "192.168.1.100"
MQTT_PORT = 1883
MQTT_TOPIC = "home/weather/outdoor"
MQTT_CLIENT_ID = "pi-weather-station"
# Sensor Configuration
BME280_ADDR = 0x76
I2C_BUS = 1
PUBLISH_INTERVAL = 60 # seconds
class WeatherStation:
def __init__(self):
self.bus = smbus2.SMBus(I2C_BUS)
self.client = mqtt.Client(client_id=MQTT_CLIENT_ID)
self.client.on_connect = self._on_connect
self.client.on_disconnect = self._on_disconnect
self.client.will_set(
f"{MQTT_TOPIC}/status", "offline", qos=1, retain=True
)
def _on_connect(self, client, userdata, flags, rc):
logging.info(f"Connected to MQTT broker (rc={rc})")
client.publish(f"{MQTT_TOPIC}/status", "online",
qos=1, retain=True)
def _on_disconnect(self, client, userdata, rc):
logging.warning(f"Disconnected from broker (rc={rc})")
def read_sensor(self):
"""Read BME280 and return dict with readings."""
# Force a measurement
self.bus.write_byte_data(BME280_ADDR, 0xF4, 0x25)
time.sleep(0.05)
data = self.bus.read_i2c_block_data(BME280_ADDR, 0xF7, 8)
raw_t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
raw_h = (data[6] << 8) | data[7]
raw_p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temp = raw_t / 5120.0 - 4.0 # Simplified compensation
return {
"temperature": round(temp, 1),
"humidity": round(raw_h / 1024.0, 1),
"pressure": round(raw_p / 25600.0 + 900, 1),
"timestamp": datetime.utcnow().isoformat() + "Z",
"station_id": MQTT_CLIENT_ID
}
def run(self):
self.client.connect(MQTT_BROKER, MQTT_PORT, keepalive=120)
self.client.loop_start()
logging.info("Weather station started.")
try:
while True:
reading = self.read_sensor()
payload = json.dumps(reading)
result = self.client.publish(
MQTT_TOPIC, payload, qos=1, retain=True
)
if result.rc == mqtt.MQTT_ERR_SUCCESS:
logging.info(f"Published: {reading['temperature']}C, "
f"{reading['humidity']}%, "
f"{reading['pressure']} hPa")
else:
logging.error(f"Publish failed: {result.rc}")
time.sleep(PUBLISH_INTERVAL)
except KeyboardInterrupt:
logging.info("Shutting down...")
finally:
self.client.publish(f"{MQTT_TOPIC}/status", "offline",
qos=1, retain=True)
self.client.loop_stop()
self.client.disconnect()
if __name__ == "__main__":
station = WeatherStation()
station.run()
Project 2: Security Camera with Motion Detection
#!/usr/bin/env python3
"""Motion-detecting security camera with email alerts.
Uses picamera2 + OpenCV for motion detection.
"""
from picamera2 import Picamera2
import cv2
import numpy as np
import time
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from datetime import datetime
import os
MOTION_THRESHOLD = 5000 # Minimum contour area to trigger
SENSITIVITY = 25 # Pixel difference threshold
SAVE_DIR = "/home/pi/security_captures"
COOLDOWN = 30 # Seconds between alerts
os.makedirs(SAVE_DIR, exist_ok=True)
picam2 = Picamera2()
config = picam2.create_video_configuration(
main={"size": (1280, 720), "format": "RGB888"}
)
picam2.configure(config)
picam2.start()
time.sleep(2)
prev_frame = None
last_alert_time = 0
motion_count = 0
print("Security camera active. Monitoring for motion...")
try:
while True:
frame = picam2.capture_array()
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
if prev_frame is None:
prev_frame = gray
continue
# Compute frame difference
delta = cv2.absdiff(prev_frame, gray)
thresh = cv2.threshold(delta, SENSITIVITY, 255,
cv2.THRESH_BINARY)[1]
thresh = cv2.dilate(thresh, None, iterations=2)
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
motion_detected = False
for c in contours:
if cv2.contourArea(c) > MOTION_THRESHOLD:
motion_detected = True
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x+w, y+h),
(0, 255, 0), 2)
if motion_detected:
now = time.time()
if now - last_alert_time > COOLDOWN:
motion_count += 1
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{SAVE_DIR}/motion_{ts}.jpg"
cv2.imwrite(filename,
cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
print(f"[{ts}] Motion detected! Saved: {filename}")
last_alert_time = now
prev_frame = gray
time.sleep(0.1)
except KeyboardInterrupt:
picam2.stop()
print(f"\nTotal motion events: {motion_count}")
Project 3: Home Automation Controller
{
"homeassistant": true,
"mqtt": {
"broker": "192.168.1.100",
"port": 1883,
"base_topic": "home/pi_controller"
},
"relays": [
{"name": "Living Room Light", "gpio": 17, "type": "toggle"},
{"name": "Fan", "gpio": 27, "type": "toggle"},
{"name": "Garage Door", "gpio": 22, "type": "momentary", "pulse_ms": 500}
],
"sensors": [
{"name": "Temperature", "type": "bme280", "i2c_addr": "0x76"},
{"name": "Motion PIR", "type": "pir", "gpio": 4},
{"name": "Door Contact", "type": "contact", "gpio": 5}
]
}
Performance & Power Management
Embedded applications often need to balance computational performance against thermal limits and power budgets. The Raspberry Pi provides several mechanisms for managing both.
CPU Governor and Frequency Scaling
# Check current CPU frequency and governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# Available governors
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# ondemand performance powersave conservative schedutil
# Set performance governor (max clock always)
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
# Set conservative governor (slowly ramp up)
echo conservative | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
# Check thermal throttling status
vcgencmd measure_temp # Current temperature
vcgencmd get_throttled # Throttle flags (0x0 = no throttling)
# Bit 0: under-voltage detected
# Bit 1: ARM frequency capped
# Bit 2: currently throttled
# Bit 3: soft temperature limit active
# Monitor temperature and frequency in real-time
watch -n 1 'echo "Temp: $(vcgencmd measure_temp) | Freq: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) kHz | Throttle: $(vcgencmd get_throttled)"'
Power Budget Reference
| Configuration | Idle Power | Load Power | Notes |
|---|---|---|---|
| Pi 5 (no peripherals) | 3.5W | 12W | Active cooling recommended |
| Pi 5 + NVMe + Camera | 5W | 15-18W | 27W PD supply required |
| Pi 4 (no peripherals) | 2.7W | 6.4W | Passive heatsink sufficient for light loads |
| Pi Zero 2 W | 0.4W | 1.8W | Good for battery projects |
| Pi Pico | 0.005W | 0.15W | Excellent for battery/solar |
| Pi Pico (dormant) | 0.0006W | N/A | RTC wake with 1 year on 2xAA |
For battery-powered projects, disable unused interfaces to reduce power consumption:
# Disable HDMI (saves ~25mA on Pi 4)
sudo tvservice -o
# Disable onboard LEDs
echo 0 | sudo tee /sys/class/leds/ACT/brightness
echo none | sudo tee /sys/class/leds/ACT/trigger
# Disable WiFi and Bluetooth
sudo rfkill block wifi
sudo rfkill block bluetooth
# Disable USB controller (Pi 4 - saves ~100mA)
echo '1-1' | sudo tee /sys/bus/usb/drivers/usb/unbind
# Reduce CPU frequency
echo 600000 | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
Case Studies
Case Study 1: NASA JPL Ingenuity Mars Helicopter
Perhaps the most extraordinary Raspberry Pi deployment is aboard NASA's Ingenuity helicopter, which flew on Mars starting April 19, 2021. While the flight computer uses a Qualcomm Snapdragon processor, the navigation camera and image processing pipeline rely on a Linux-based compute element built on the same architecture principles as the Raspberry Pi ecosystem — specifically, the use of standard Linux, Python scripts, and open-source computer vision libraries on ARM hardware.
The JPL team chose to use commercial off-the-shelf (COTS) ARM processors running Linux because the development velocity was dramatically higher than traditional radiation-hardened space computers. They managed radiation exposure through software redundancy, watchdog timers, and ECC memory rather than specialized hardware. The Ingenuity project demonstrated that the embedded Linux + ARM approach — exactly what Raspberry Pi popularized — is viable even for interplanetary missions.
Case Study 2: Industrial Pi Deployments at Festo and Siemens
Festo, the German industrial automation company, developed the Festo CPX-IoT Gateway powered by a Raspberry Pi Compute Module. It bridges OT (operational technology) networks running Profinet, EtherNet/IP, and IO-Link to IT cloud platforms. In production facilities, it collects real-time sensor data from pneumatic actuators and predictive maintenance sensors, processes it locally, and forwards aggregated insights to Festo's cloud analytics dashboard.
Siemens uses Raspberry Pi-class hardware in its SIMATIC IOT2050 gateway, which runs a TI AM6548 (similar ARM Cortex-A53 architecture). However, many Siemens partners prototype their industrial IoT solutions on standard Raspberry Pis before porting to the IOT2050 for production. The software compatibility between the Pi's Debian-based OS and the IOT2050's Debian-based OS makes this transition straightforward.
Case Study 3: Oracle Pi Supercomputer
At Oracle OpenWorld 2019, Oracle demonstrated a Raspberry Pi supercomputer cluster consisting of 1,060 Raspberry Pi 3B+ boards. The cluster ran Oracle Autonomous Linux and was designed to demonstrate the scalability of Oracle's cloud-native infrastructure stack. Each Pi ran a Kubernetes node, collectively forming a cluster that could schedule and manage containerized workloads.
While primarily a demonstration piece, Pi clusters have genuine educational and research value. Universities like the University of Southampton (where Professor Simon Cox built one of the first documented Pi clusters with his son in 2012 using 64 Pi Model Bs and Lego cases) use them to teach distributed computing, MPI programming, and cluster management at a fraction of the cost of traditional server hardware.
Exercises
LED Traffic Light Controller
Build a traffic light simulator using three LEDs (red, yellow, green) connected to GPIO pins. Program the standard traffic light sequence: green (30s) -> yellow (5s) -> red (30s) -> green. Add a pedestrian button on a fourth GPIO that triggers an interrupt to change the light to red after completing the current green phase. Use gpiozero for the implementation, then rewrite it using lgpio for Pi 5 compatibility.
I2C Multi-Sensor Data Logger
Connect a BME280 (temperature/pressure/humidity) and an MPU6050 (accelerometer/gyroscope) to the I2C bus. Write a Python program that reads both sensors at 10 Hz, stores data in a SQLite database, and serves a real-time web dashboard using Flask with Chart.js graphs. Implement proper I2C error handling with retry logic and bus recovery. Add MQTT publishing so Home Assistant can subscribe to the sensor feeds.
Computer Vision Object Counter
Build a real-time object counting system using the Camera Module 3 and TensorFlow Lite on a Pi 5. Load a pre-trained MobileNet SSD model and count specific objects (e.g., people, cars, or packages) passing through a defined region of interest in the camera frame. Implement a line-crossing algorithm to count objects moving in each direction. Log counts to InfluxDB and display on a Grafana dashboard. The system should handle at least 15 fps at 720p resolution and survive network disconnections by buffering data locally.
Raspberry Pi Project Plan Generator
Use this tool to document your Raspberry Pi embedded project plan — board selection, GPIO assignments, peripheral connections, operating system, and power requirements. Download as Word, Excel, PDF, or PowerPoint for project documentation, team handoff, or hardware procurement.
Document your Raspberry Pi project configuration for review and export. All data stays in your browser — nothing is sent to any server.
All data stays in your browser. Nothing is sent to or stored on any server.
Conclusion & Resources
The Raspberry Pi has transformed embedded prototyping from a specialized discipline requiring expensive development kits and proprietary tools into an accessible field where anyone with a $35 board and a text editor can build sophisticated embedded systems. From GPIO basics to Linux device drivers, from camera-based computer vision to MQTT-connected IoT networks, the Pi provides a complete platform for learning and building.
- Choose the right Pi model — Pi 5 for compute-heavy tasks, Pi Zero 2 W for compact IoT, Pi Pico for real-time and battery-powered
- Use gpiozero/lgpio for Python GPIO programming (Pi 5 requires lgpio or gpiozero with lgpio backend)
- I2C, SPI, and UART connect the Pi to thousands of sensors, displays, and communication modules
- Device tree overlays configure hardware interfaces without recompiling the kernel
- For production IoT, combine Pi as gateway with ESP32/Pico as sensor endpoints
- Always account for power budgets and thermal management in embedded designs
Further Resources
- Official Documentation: raspberrypi.com/documentation
- gpiozero Docs: gpiozero.readthedocs.io
- picamera2 Manual: Picamera2 PDF
- Books: "Programming the Raspberry Pi" (Simon Monk), "Exploring Raspberry Pi" (Derek Molloy)
- Community: Raspberry Pi Forums, r/raspberry_pi on Reddit