Software‑based PWM driver for Raspberry Pi – flexible, multi‑channel, user‑space configurable
simple-device-driver is a Linux kernel module that implements a custom PWM (Pulse‑Width Modulation) generator by bit‑banging GPIO pins on a Raspberry Pi. Unlike the limited hardware PWM channels built into the SoC, this driver creates PWM signals entirely in software using high‑resolution kernel timers and tight timing loops. The driver exposes a clean user‑space interface (via ioctl and a sysfs attribute directory) that lets applications set the PWM frequency and duty cycle on any GPIO pin at runtime. This makes it possible to drive dozens of independent PWM outputs simultaneously, which is invaluable for robotics, LED lighting, motor control, or any project where the number of hardware PWM channels is a bottleneck.
The core idea is simple: the driver schedules periodic timer callbacks that toggle the chosen GPIO pin high for a calculated “on‑time” and low for the remainder of the period. By adjusting the on‑time and the period length on the fly, the PWM characteristics can be changed without unloading the module. The implementation carefully balances CPU usage with timing accuracy, using hrtimer in nanosecond mode and locking mechanisms to avoid race conditions.
Because the driver lives in kernel space, it can achieve sub‑microsecond timing resolution that would be difficult to obtain from pure user‑space busy‑loops. At the same time, the user‑space API keeps development straightforward: a small C library or even shell scripts can open /dev/simple_pwm and issue ioctl commands, or write to /sys/class/simple_pwm/... files. The project is fully open‑source, well‑documented, and designed for easy extension to support additional features such as PWM chaining, polarity inversion, or per‑channel enable/disable flags.
- GPIO‑agnostic PWM – any GPIO pin can be turned into a PWM output, bypassing the limited hardware PWM resources.
- Dynamic configuration – change frequency (1 Hz – 20 kHz) and duty cycle (0 % – 100 %) on the fly via
ioctlor sysfs. - Multi‑channel support – instantiate multiple PWM channels concurrently, each with independent parameters.
- High‑resolution timing – uses Linux
hrtimerwith nanosecond granularity for precise edge placement. - Simple user‑space API –
/dev/simple_pwmcharacter device +ioctlcommands; optional sysfs entries for scripting. - Kernel‑safe operation – employs spinlocks and atomic variables to protect shared state, preventing deadlocks.
- Debug facilities – debugfs entries provide real‑time statistics (runtime, jitter, missed deadlines).
- Scalability – eliminates the hardware PWM channel limit; you can drive dozens of LEDs, servos, or fans simultaneously.
- Flexibility – adjust PWM parameters without recompiling or reloading the driver, ideal for prototyping and adaptive control loops.
- Portability – the driver is written against the generic GPIO API, making it easy to port to other SBCs (e.g., BeagleBone, Odroid).
- Educational value – showcases kernel‑timer programming, GPIO handling, and character‑device interfacing for developers learning kernel module development.
- Open source – freely available under the MIT license, encouraging community contributions and custom extensions.
- Raspberry Pi running a recent Debian‑based distro (e.g., Raspberry Pi OS) with kernel headers installed:
sudo apt-get install raspberrypi-kernel-headers build-essential. - A user with
sudoprivileges to load/unload kernel modules.
git clone https://github.com/yourname/simple-device-driver.git
cd simple-device-driver
makeThe Makefile automatically picks up the correct kernel source directory via $(KERNELDIR).
sudo insmod simple_pwm.ko
# Verify loading
dmesg | tailTo unload:
sudo rmmod simple_pwm#include <fcntl.h>
#include <sys/ioctl.h>
#include "simple_pwm.h" // generated header with ioctl numbers
int fd = open("/dev/simple_pwm", O_RDWR);
struct pwm_config cfg = { .gpio = 18, .freq_hz = 1000, .duty = 50 };
ioctl(fd, SIMPLE_PWM_SET_CONFIG, &cfg);
close(fd);# List available channels
ls /sys/class/simple_pwm/
# Set parameters
echo 18 > /sys/class/simple_pwm/pwm0/gpio
echo 2000 > /sys/class/simple_pwm/pwm0/frequency
echo 75 > /sys/class/simple_pwm/pwm0/duty_cycleBoth methods apply changes instantly.
Because PWM is generated in software, each active channel consumes CPU cycles proportional to its frequency. For low‑frequency signals (< 1 kHz) the impact is negligible, but high‑frequency or many concurrent channels can saturate a single CPU core. The driver mitigates this by using a single high‑resolution timer that schedules the next edge for the earliest pending channel, reducing context switches. Users should monitor the cpu_load debugfs entry and adjust the number of active channels or lower the frequency if the system becomes CPU‑bound.
Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request. Guidelines:
- Follow the Linux kernel coding style (
scripts/checkpatch.pl). - Add or update documentation in
README.mdanddoc/. - Include unit tests where possible (e.g., using
kselftest).
This project is released under the MIT License – see the LICENSE file for details.
- The Raspberry Pi Foundation for providing excellent GPIO documentation.
- The Linux kernel community for the robust
hrtimerand GPIO subsystems.
Happy hacking!