Low-overhead Raspberry Pi fan controller written in Rust. It reads CPU temperature from thermal sysfs and drives a PWM signal with an asymmetric control curve (faster ramp-up, slower ramp-down).
Tested on: Raspberry Pi 4 Model B Rev 1.5 (
aarch64), Debian 13 (Trixie), kernel6.18.29+rpt-rpi-v8, with Noctua NF-A4x10 5V PWM; validated via both manual run andsystemdservice mode.
This project targets 4-wire PWM fans such as the Noctua NF-A4x10 5V PWM.
- Power fan VCC/GND from a stable 5V source.
- Drive the fan PWM input using open-drain/transistor style wiring from the selected GPIO pin.
- Fan tach wiring is optional. RPM logging can use
hwmonor a direct tach GPIO input. - Verify common ground between Raspberry Pi and fan power source.
- Software PWM at 25 kHz can consume significant CPU on Raspberry Pi.
- Hardware PWM keeps timing in hardware, so controller CPU usage stays much lower.
Supported hardware PWM pins:
- PWM0: BCM
12or18 - PWM1: BCM
13or19
Minimal boot configuration:
- Edit
/boot/firmware/config.txt - Add:
[all]
dtoverlay=pwm-2chan- Reboot
Verification tips:
- Confirm PWM sysfs exists:
ls -l /sys/class/pwm/pwmchip0 - Watch startup logs:
journalctl -u rpi-fan-control -n 50 --no-pager - Check runtime footprint:
systemd-cgtop
Fallback behavior:
- If hardware PWM init fails (overlay missing, pin conflict, etc.), the app falls back to software PWM automatically.
At startup, the app loads config from:
--config <path>when provided on the command line/etc/rpi-fan-control/config.toml./config.toml- built-in defaults
Reference example: packaging/config.toml.example.
Important keys:
gpio_pin: BCM pin used for PWM output.12/18(PWM0) and13/19(PWM1) use hardware PWM (preferred, low CPU). Other pins work via automatic software-PWM fallback (higher CPU, warning logged).thermal_path: Linux sysfs temperature source path.fan_speed_path: optional Linux sysfs tach path for RPM status logging (first choice).tach_gpio_pin: optional BCM GPIO input for tach RPM fallback (uses internal pull-up).loop_interval_ms: control loop period (default1000).status_interval_secs: interval forinfostatus logs injournalctl(default60).target_temp_c: desired cooling target used to derive the fan curve.min_duty: minimum duty used once fan turns on.max_duty: maximum duty cap.response: one ofquiet,balanced,aggressive.dry_run: skips physical PWM writes.
The controller derives detailed curve/smoothing internals from this simple config. response adjusts hysteresis, deadband, and ramp dynamics without exposing low-level tuning knobs.
cargo runRun with a custom config file:
cargo run -- --config ./config.toml runTach auto-discovery (probe candidate GPIO pins and report RPM):
cargo run -- --config ./config.toml discover-tachShow CLI version:
cargo run -- --versionEnable debug telemetry:
RUST_LOG=debug cargo runRun benchmark smoke target:
cargo run --release --bin control_benchQuick install from GitHub Releases:
curl -fsSL https://raw.githubusercontent.com/zazabe/rpi-fan-controller/main/install.sh | sudo bashInstall a specific release tag:
curl -fsSL https://raw.githubusercontent.com/zazabe/rpi-fan-controller/main/install.sh | sudo bash -s -- v0.1.1Manual install:
sudo install -D -m 0755 target/release/rpi-fan-control /usr/local/bin/rpi-fan-control
sudo install -D -m 0644 packaging/rpi-fan-control.service /etc/systemd/system/rpi-fan-control.service
sudo install -D -m 0644 packaging/config.toml.example /etc/rpi-fan-control/config.toml
sudo install -D -m 0644 packaging/rpi-fan-control.default /etc/default/rpi-fan-control
sudo systemctl daemon-reload
sudo systemctl enable --now rpi-fan-controlUseful commands:
sudo systemctl status rpi-fan-control
journalctl -u rpi-fan-control -f- Idle temp: fan stays off or at low stable duty.
- Heat-up test: fan ramps quickly as temp rises.
- Cooldown test: fan decays smoothly, no abrupt drops.
- Reboot test: service starts automatically and applies duty.
- Footprint check: validate low CPU/memory usage via
topandsystemd-cgtop.
- Start with conservative thresholds and verify thermals under your workload.
- Keep
target_temp_cbelow your thermal throttle zone and validate under sustained load. - Use
dry_run = truewhile validating config logic without hardware attached.
- CI workflow (
.github/workflows/ci.yml) runs fmt, clippy, tests, and benchmark smoke. - Release workflow (
.github/workflows/release.yml) publishes ARM tarballs for tags matchingv*.*.*. - Recommended convention: semantic tags like
v0.1.0. - Release tags must match
Cargo.tomlversion (e.g.version = "0.1.2"-> tagv0.1.2). - Release notes template:
.github/release_template.md.
To bump a release, create and push a new semantic tag:
git tag v0.1.1
git push origin v0.1.1Or use the helper command to update Cargo.toml, commit, and create a matching tag:
make release-tag VERSION=0.1.1
git push && git push --tagsPick the release asset that matches your Pi OS architecture:
*aarch64-unknown-linux-gnu.tar.gzfor 64-bit OS (uname -mshowsaarch64).*armv7-unknown-linux-gnueabihf.tar.gzfor 32-bit OS (uname -mshowsarmv7lor similar).
This project is licensed under the MIT License. See LICENSE.
Parts of this repository may include AI/LLM-generated content. Always review and validate generated code/configuration before using it in production or safety-critical environments.