folkhack / raspberry-pi-pwm-fan-2

Raspberry Pi PWM Fan control in C and Python
30 stars 4 forks source link

Raspberry Pi PWM Fan Control in C


⚠ Major April 2024 Updates ⚠

This program expects a FULLY up-to-date Raspberry Pi OS (either current and legacy) and firmware

# Ensure OS up-to-date:
sudo apt update -y && sudo apt upgrade -y

# Ensure up-to-date firmware:
# - RUN AT YOUR OWN RISK!
# - Needed to get consistent `sysfs` inteface between all models
# - Needed to address https://forums.raspberrypi.com/viewtopic.php?t=367294#p2205138
sudo rpi-update

April 2024 Release Notes:


Wiring:

Pin # GPIO # Name Color Notes
4 5V Yellow
6 Ground Black
12 18 Duty cycle signal Blue PWM channel #0
18 24 Tachometer signal Green (optional) Any GPIO pin should work
17 Tachometer pull-up Orange (optional) bridge to "tachometer signal" with a 1k Ω resistor; this is an additional wire, not from the fan

Simplified Wiring Diagram:

Raspberry Pi PWM fan wiring diagram

Noctua Wiring Diagram:

Noctua docs wiring diagram

From Noctua General PWM Fan Whitepaper


PWM dtoverlay Configuration:

Base configuration is dtoverlay=pwm,pin=18,func=2 in /boot/config.txt (or /boot/firmware/config.txt):

# Add `dtoverlay=pwm,pin=18,func=2` devicetree overlay in boot configuration and restart:
# - NOTE: Configuratio may be at /boot/config.txt depending on OS version!
sudo nano /boot/firmware/config.txt
sudo shutdown -r now

PWN dtoverlay Notes:

See Enabling Hardware PWM on Raspberry Pi for more information (differnt pins, 2 channel support, etc.).


Scripted install.sh Install:

IMPORTANT!: The service is not started by default - you are expected to configure the GPIO pin, duty cycle hertz, and misc. configuration options to your fan spec + usecase. See: Environment Variables Config »

# From the Raspberry Pi (builds for installed OS variant)
sudo install.sh

# Display help and exit:
sudo pwm_fan_control2 --help

# Running with defaults:
# - IMPORTANT! Only for Noctua 5V PWM fans!
sudo pwm_fan_control2

# Running with debug logging enabled (meant for CLI):
sudo pwm_fan_control2 debug

# Running with debug logging + tachometer enabled:
#    sudo pwm_fan_control2 debug {gpio_tach_pin} {tachometer_pulse_per_rotation}
sudo pwm_fan_control2 debug 24 2

# For testing other fans configurations override environment variables by CLI with sudo:
#   export PWM_FAN_SLEEP_MS=60000 && \
#   export PWM_FAN_MIN_DUTY_CYCLE=20 && \
#   sudo -E bash -c './pwm_fan_control2'

# Configure the service:
# - May want to add environment variables to override configuration defaults
# - Not required for Noctua 5V PWM fans
sudo nano /etc/systemd/system/pwm_fan_control2.service
sudo systemctl daemon-reload

# Start the service:
sudo service pwm_fan_control2 start

# Enable service at boot:
sudo systemctl enable pwm_fan_control2.service

# Misc control:
#   sudo service pwm_fan_control2 status
#   sudo service pwm_fan_control2 stop
#   sudo service pwm_fan_control2 restart

# Uninstall - stops and removes installed system service + binary:
#   sudo make uninstall

Manual Build and Install:

Requirements: Git and basic build tooling.

# Install build tooling
sudo apt install git build-essential

# Get this project
git clone https://github.com/folkhack/raspberry-pi-pwm-fan-2.git
cd raspberry-pi-pwm-fan

# Build project (builds for installed OS variant)
# - Project binary builds in project root ./pwm_fan_control2
make compile

# Install binary
# - Installs to /usr/sbin/pwm_fan_control2
# - `sudo make uninstall` to stop service and remove binary
sudo make install

Running as a systemd Service:

After installation you can edit the service file:

# Edit system service:
sudo nano /etc/systemd/system/pwm_fan_control2.service

# Reload systemd and restart service:
sudo systemctl daemon-reload
sudo service pwm_fan_control2 restart

# Enable service at boot:
sudo systemctl enable pwm_fan_control2.service

Example systemd configuration:

NOTE: This is a good spot to load in an environment configuration as seen below with the commented out line.

[Unit]
Description=PWM fan speed control
After=sysinit.target

[Service]
# Environment=PWM_FAN_SLEEP_MS=60000
ExecStart=/usr/sbin/pwm_fan_control2
Type=simple
User=root
Group=root
Restart=always

[Install]
WantedBy=multi-user.target

Environment Varibale Reference:

NOTE: These environment variables are fully supported by both the Python 3 POC and the formal C implementation!

Variable Default Type Notes
PWM_FAN_BCM_GPIO_PIN_PWM 18 unsigned short BCM GPIO pin for PWM duty cycle signal
PWM_FAN_PWM_FREQ_HZ 2500 unsigned short PWM duty cycle target freqency Hz - from Noctua Spec at 25kHz
PWM_FAN_MIN_DUTY_CYCLE 20 unsigned short Minimum PWM duty cycle - from Noctua spec at 20%
PWM_FAN_MAX_DUTY_CYCLE 100 unsigned short Maximum PWM duty cycle
PWM_FAN_MIN_OFF_TEMP_C 38 float Turn fan off if is on and CPU temp falls below this value
PWM_FAN_MIN_ON_TEMP_C 40 float Turn fan on if is off and CPU temp rises above this value
PWM_FAN_MAX_TEMP_C 46 float Set fan duty cycle to PWM_FAN_MAX_DUTY_CYCLE if CPU temp rises above this value
PWM_FAN_FAN_OFF_GRACE_MS 60000 unsigned short Turn fan off if CPU temp stays below MIN_OFF_TEMP_C this for time period
PWM_FAN_SLEEP_MS 250 unsigned short Main loop check CPU and set PWM duty cycle delay

Notes:

Why C over Python:

When running the Python POC at full 25khz PWM frequency (Noctua Spec) CPU consumption can be upwards of 5-10%. With C it's at 0% on a Raspberry 4.

Easing Function:

A quartic bezier easing function was used to smooth fan speed at the upper/lower boundries of the configured temps PWM_FAN_MIN_OFF_TEMP_C and PWM_FAN_MAX_TEMP_C. At temps closer to the lower boundry, the fan speed is kept close to the PWM_FAN_MIN_DUTY_CYCLE, and at the higher boundry fan speed will stay closer to PWM_FAN_MAX_DUTY_CYCLE.

Links: