CC = gcc | |
RM = rm -f | |
INSTRUMENT_FOR_PROMETHEUS := false | |
ifeq ($(INSTRUMENT_FOR_PROMETHEUS),true) | |
CFLAGS = -Wall -DINSTRUMENT_FOR_PROMETHEUS | |
LIBS = -lbcm2835 -lprom -lpromhttp -lmicrohttpd | |
else | |
CFLAGS = -Wall | |
LIBS = -lbcm2835 | |
endif | |
TARGET = pi_fan_hwpwm | |
all: $(TARGET) | |
$(TARGET): $(TARGET).c Makefile | |
$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LIBS) | |
install: $(TARGET) | |
install $(TARGET) /usr/local/sbin | |
cp $(TARGET).service /etc/systemd/system/ | |
systemctl enable $(TARGET) | |
! systemctl is-active --quiet $(TARGET) || systemctl stop $(TARGET) | |
systemctl start $(TARGET) | |
uninstall: clean | |
systemctl stop $(TARGET) | |
systemctl disable $(TARGET) | |
$(RM) /usr/local/sbin/$(TARGET) | |
$(RM) /etc/systemd/system/$(TARGET).service | |
$(RM) /run/$(TARGET).* | |
@echo | |
@echo "To remove the source directory" | |
@echo " $$ cd && rm -rf ${CURDIR}" | |
@echo | |
clean: | |
$(RM) $(TARGET) |
/* | |
/ | |
/ pi_fan_hwpwm.c, alwynallan@gmail.com 12/2020, no license | |
/ latest version: https://gist.github.com/alwynallan/1c13096c4cd675f38405702e89e0c536 | |
/ | |
/ Need http://www.airspayce.com/mikem/bcm2835/index.html | |
/ | |
/ Compile $ gcc -Wall pi_fan_hwpwm.c -lbcm2835 -o pi_fan_hwpwm | |
/ | |
/ Disable $ sudo nano /boot/config.txt [Raspbian, or use GUI] | |
/ $ sudo nano /boot/firmware/usercfg.txt [Ubuntu] | |
/ # dtoverlay=gpio-fan,gpiopin=14,temp=80000 <- commented out, reboot | |
/ enable_uart=0 <- needed? not Ubuntu | |
/ dtparam=audio=off <- needed? not Ubuntu | |
/ dtparam=i2c_arm=off <- needed? not Ubuntu | |
/ dtparam=spi=off <- needed? not Ubuntu | |
/ | |
/ Run $ sudo ./pi_fan_hwpwm -v | |
/ | |
/ Forget $ sudo ./pi_fan_hwpwm & | |
/ $ disown -a | |
/ | |
*/ | |
#include <stdio.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#include <stdlib.h> | |
#include <fcntl.h> | |
#include <stdarg.h> | |
#include <stdarg.h> | |
#include <bcm2835.h> | |
//#define INSTRUMENT_FOR_PROMETHEUS do this in the Makefile | |
#ifdef INSTRUMENT_FOR_PROMETHEUS | |
// https://github.com/digitalocean/prometheus-client-c | |
#define PROM_PORT 8764 | |
#include <signal.h> | |
#include "microhttpd.h" | |
#include "prom.h" | |
#include "promhttp.h" | |
prom_counter_t *pi_fan_hwpwm_loops; | |
prom_gauge_t *pi_fan_hwpwm_temp; | |
prom_gauge_t *pi_fan_hwpwm_pwm; | |
#endif //INSTRUMENT_FOR_PROMETHEUS | |
#define PWM_PIN 0 // default, uses both GPIO 13 and GPIO 18 | |
#define HIGH_TEMP 80. | |
#define ON_TEMP 65. | |
#define OFF_TEMP 60. | |
#define MIN_FAN 150 | |
#define KICK_FAN 200 | |
#define MAX_FAN 480 | |
unsigned pin = PWM_PIN; | |
int verbose = 0; | |
int fan_state = 0; | |
double temp = 25.0; | |
pid_t global_pid; | |
int pwm_level = -555; | |
void usage() | |
{ | |
fprintf | |
(stderr, | |
"\n" \ | |
"Usage: sudo ./pi_fan_hwpwm [OPTION]...\n" \ | |
"\n" \ | |
" -g <n> Use GPIO n for fan's PWM input, default 0 (both).\n" \ | |
" Only hardware PWM capable GPIO 18 and GPIO 13 are present on\n" \ | |
" the RasPi 4B pin header, and only GPIO 18 can be used with\n" \ | |
" the unmodified case fan.\n" \ | |
" -v Verbose output\n" \ | |
"\n" | |
); | |
} | |
void fatal(int show_usage, char *fmt, ...) { | |
char buf[128]; | |
va_list ap; | |
va_start(ap, fmt); | |
vsnprintf(buf, sizeof(buf), fmt, ap); | |
va_end(ap); | |
fprintf(stderr, "%s\n", buf); | |
if (show_usage) usage(); | |
fflush(stderr); | |
exit(EXIT_FAILURE); | |
} | |
void run_write(const char *fname, const char *data) { | |
// https://opensource.com/article/19/4/interprocess-communication-linux-storage | |
struct flock lock; | |
lock.l_type = F_WRLCK; | |
lock.l_whence = SEEK_SET; | |
lock.l_start = 0; | |
lock.l_len = 0; | |
lock.l_pid = global_pid; | |
int fd; | |
if ((fd = open(fname, O_RDWR | O_CREAT, 0666)) < 0) | |
fatal(0, "failed to open %s for writing", fname); | |
if (fcntl(fd, F_SETLK, &lock) < 0) | |
fatal(0, "fcntl failed to get lock on %s", fname); | |
if (ftruncate(fd, 0) < 0) | |
fatal(0, "truncate failed to on %s", fname); | |
write(fd, data, strlen(data)); | |
close(fd); | |
} | |
void PWM_out(int level) { | |
if(level > pwm_level && (level - pwm_level) < 5) return; | |
if(level < pwm_level && (pwm_level - level) < 10) return; | |
if(level != pwm_level) { | |
if(pin == 0 || pin == 13) bcm2835_pwm_set_data(1, level); | |
if(pin == 0 || pin == 18) bcm2835_pwm_set_data(0, level); | |
pwm_level = level; | |
} | |
} | |
void fan_loop(void) { | |
if(!fan_state && (temp > ON_TEMP)) { | |
PWM_out(KICK_FAN); | |
fan_state = 1; | |
return; | |
} | |
if(fan_state && (temp < OFF_TEMP)) { | |
PWM_out(0); | |
fan_state = 0; | |
return; | |
} | |
if(fan_state) { | |
unsigned out = (double) MIN_FAN + (temp - OFF_TEMP) / (HIGH_TEMP - OFF_TEMP) * (double)(MAX_FAN - MIN_FAN); | |
if(out > MAX_FAN) out = MAX_FAN; | |
PWM_out(out); | |
} | |
} | |
#ifdef INSTRUMENT_FOR_PROMETHEUS | |
void ae1() { | |
prom_collector_registry_destroy(PROM_COLLECTOR_REGISTRY_DEFAULT); | |
} | |
struct MHD_Daemon *mhdDaemon; | |
void ae2() { | |
MHD_stop_daemon(mhdDaemon); | |
} | |
#endif //INSTRUMENT_FOR_PROMETHEUS | |
int main(int argc, char *argv[]) { | |
int opt; | |
unsigned loop = 0; | |
int t; | |
FILE *ft; | |
char buf[100]; | |
while ((opt = getopt(argc, argv, "g:v")) != -1) { | |
switch (opt) { | |
case 'g': | |
pin = atoi(optarg); | |
if(pin != 0 && pin != 13 && pin != 18) fatal(0, "Invalid GPIO"); | |
break; | |
case 'v': | |
verbose = 1; | |
break; | |
default: | |
usage(); | |
exit(EXIT_FAILURE); | |
} | |
} | |
if(optind != argc) fatal(1, "optind=%d argc=%d Unrecognized parameter %s", optind, argc, argv[optind]); | |
global_pid = getpid(); | |
sprintf(buf, "%d\n", global_pid); | |
run_write("/run/pi_fan_hwpwm.pid", buf); | |
if(!bcm2835_init()) fatal(0, "bcm2835_init() failed"); | |
if(pin==0 || pin==13) bcm2835_gpio_fsel(13, BCM2835_GPIO_FSEL_ALT0); | |
if(pin==0 || pin==18) bcm2835_gpio_fsel(18, BCM2835_GPIO_FSEL_ALT5); | |
bcm2835_pwm_set_clock(2); // 19.2 / 2 MHz | |
if(pin==0 || pin==13) bcm2835_pwm_set_mode(1, 1, 1); | |
if(pin==0 || pin==13) bcm2835_pwm_set_range(1, 480); | |
if(pin==0 || pin==18) bcm2835_pwm_set_mode(0, 1, 1); | |
if(pin==0 || pin==18) bcm2835_pwm_set_range(0, 480); | |
PWM_out(0); | |
#ifdef INSTRUMENT_FOR_PROMETHEUS | |
prom_collector_registry_default_init(); | |
pi_fan_hwpwm_loops = prom_collector_registry_must_register_metric( | |
prom_counter_new("pi_fan_hwpwm_loops", "Control loop counter.", 0, NULL)); | |
pi_fan_hwpwm_temp = prom_collector_registry_must_register_metric( | |
prom_gauge_new("pi_fan_hwpwm_temp", "Core temperature in Celsius.", 0, NULL)); | |
pi_fan_hwpwm_pwm = prom_collector_registry_must_register_metric( | |
prom_gauge_new("pi_fan_hwpwm_pwm", "Fan speed PWM in percent.", 0, NULL)); | |
promhttp_set_active_collector_registry(NULL); | |
atexit(ae1); | |
mhdDaemon = promhttp_start_daemon(MHD_USE_SELECT_INTERNALLY, PROM_PORT, NULL, NULL); | |
if (mhdDaemon == NULL) exit(EXIT_FAILURE); | |
else atexit(ae2); | |
#endif //INSTRUMENT_FOR_PROMETHEUS | |
while(1) { | |
loop++; | |
ft = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); | |
fscanf(ft, "%d", &t); | |
fclose(ft); | |
temp = 0.0001 * (double)t + 0.9 * temp; | |
if((loop%4) == 0) { // every second | |
fan_loop(); | |
sprintf(buf, "%u, %.2f, %.1f\n", loop/4, temp, (float)pwm_level/(float)MAX_FAN*100.); | |
run_write("/run/pi_fan_hwpwm.state", buf); | |
if(verbose) fputs(buf, stdout); | |
#ifdef INSTRUMENT_FOR_PROMETHEUS | |
prom_counter_inc(pi_fan_hwpwm_loops, NULL); | |
prom_gauge_set(pi_fan_hwpwm_temp, temp, NULL); | |
prom_gauge_set(pi_fan_hwpwm_pwm, (double)pwm_level/(double)MAX_FAN*100., NULL); | |
#endif //INSTRUMENT_FOR_PROMETHEUS | |
} | |
usleep(250000); | |
} | |
exit(EXIT_SUCCESS); | |
} |
[Unit] | |
Description=Hardware PWM control for Raspberry Pi 4 Case Fan | |
After=syslog.target | |
[Service] | |
Type=simple | |
User=root | |
WorkingDirectory=/run | |
PIDFile=/run/pi_fan_hwpwm.pid | |
ExecStart=/usr/local/sbin/pi_fan_hwpwm | |
Restart=on-failure | |
[Install] | |
WantedBy=multi-user.target |
This Gist is easy to install:
$ sudo apt install -y build-essential git stress-ng
$ cd
$ wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz
$ tar zxvf bcm2835-1.68.tar.gz
$ cd bcm2835-1.68
$ ./configure
$ make
$ sudo make install
$ cd
$ git clone https://gist.github.com/1c13096c4cd675f38405702e89e0c536.git
$ cd 1c13096c4cd675f38405702e89e0c536
$ make
$ sudo make install
Then watch it work:
$ stress-ng -c 4 -t 3m -q & watch -n1 cat /run/pi_fan_hwpwm.state
And Ctrl+C
when done.
I tested it on the latest Ubuntu 20.04 64-bit and Raspbian Pi OS Lite 32-bit. Let me know about others. The /boot configuration changes suggested in the .c source are not necessary.
Hi there,
I installed this according to the instructions and rebooted my pi. The fan still just runs no matter what the temperature is, is that expected behavior?
When I run sudo ./pi_fan_hwpwm -v
I get this:
My guess is that the second column is temperature, so is this the stress test I'm running here?
Should I put the run command somewhere that runs it upon boot?
Yes it is exactly like that. And it does work when I run sudo ./pi_fan_hwpwm -v
, but that also starts the test and stresses the cpu. It seems that it doesn't start correctly at boot, perhaps a permissions issue?
Hi, I tried this on a raspberry 4b 8GB with fedora 64bit. I have only little experience with gpio and this would be a great solution for controlling my adda ad0205dx-k59 fans, but I was not able to get it running.
I have downloaded and unpacked your tar file:
[tedsluis@fed127 fan]$ wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz
--2021-01-17 11:45:55-- http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz
Resolving www.airspayce.com (www.airspayce.com)... 192.185.48.187
Connecting to www.airspayce.com (www.airspayce.com)|192.185.48.187|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 272796 (266K) [application/x-gzip]
Saving to: ‘bcm2835-1.68.tar.gz’
bcm2835-1.68.tar.gz 100%[=============================>] 266.40K 375KB/s in 0.7s
2021-01-17 11:45:56 (375 KB/s) - ‘bcm2835-1.68.tar.gz’ saved [272796/272796]
[tedsluis@fed127 fan]$ tar zxvf bcm2835-1.68.tar.gz
I have installed gcc using the fedora dnf package manager:
[tedsluis@fed127 bcm2835-1.68]$ sudo dnf install gcc -y
Next I did run:
[tedsluis@fed127 bcm2835-1.68]$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking whether make supports the include directive... yes (GNU style)
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking dependency style of gcc... gcc3
checking for clock_gettime in -lrt... yes
checking for doxygen... no
configure: WARNING: Doxygen not found - continuing without Doxygen support
checking for ranlib... ranlib
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking whether gcc understands -c and -o together... (cached) yes
checking dependency style of gcc... (cached) gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating doc/Makefile
config.status: creating config.h
config.status: executing depfiles commands
Next I have run make:
[tedsluis@fed127 bcm2835-1.68]$ make
make all-recursive
make[1]: Entering directory '/home/tedsluis/fan/bcm2835-1.68'
Making all in src
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/src'
gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT bcm2835.o -MD -MP -MF .deps/bcm2835.Tpo -c -o bcm2835.o bcm2835.c
mv -f .deps/bcm2835.Tpo .deps/bcm2835.Po
rm -f libbcm2835.a
ar cru libbcm2835.a bcm2835.o
ranlib libbcm2835.a
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/src'
Making all in doc
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68'
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68'
make[1]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68'
[tedsluis@fed127 bcm2835-1.68]$ sudo make install
Making install in src
make[1]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/src'
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/src'
/usr/bin/mkdir -p '/usr/local/lib'
/usr/bin/install -c -m 644 libbcm2835.a '/usr/local/lib'
( cd '/usr/local/lib' && ranlib libbcm2835.a )
/usr/bin/mkdir -p '/usr/local/include'
/usr/bin/install -c -m 644 bcm2835.h '/usr/local/include'
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/src'
make[1]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/src'
Making install in doc
make[1]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[2]: Nothing to be done for 'install-exec-am'.
make[2]: Nothing to be done for 'install-data-am'.
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[1]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68/doc'
make[1]: Entering directory '/home/tedsluis/fan/bcm2835-1.68'
make[2]: Entering directory '/home/tedsluis/fan/bcm2835-1.68'
make[2]: Nothing to be done for 'install-exec-am'.
make[2]: Nothing to be done for 'install-data-am'.
make[2]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68'
make[1]: Leaving directory '/home/tedsluis/fan/bcm2835-1.68'
Next I installed the packages using dnf (fedora package manager):
[tedsluis@fed127 bcm2835-1.68]$ cd ~/fan
[tedsluis@fed127 fan]$ sudo dnf install -y git stress-ng
Last metadata expiration check: 2:37:40 ago on Sun 17 Jan 2021 09:11:53 AM CET.
Package git-2.29.2-3.fc33.aarch64 is already installed.
Dependencies resolved.
================
Package Architecture Version Repository Size
=============
Installing:
stress-ng aarch64 0.12.01-1.fc33 updates 518 k
Installing dependencies:
Judy aarch64 1.0.5-24.fc33 fedora 131 k
lksctp-tools aarch64 1.0.18-6.fc33 fedora 94 k
Transaction Summary
=============
Install 3 Packages
Total download size: 743 k
Installed size: 2.3 M
Downloading Packages:
(1/3): Judy-1.0.5-24.fc33.aarch64.rpm 418 kB/s | 131 kB 00:00
(2/3): lksctp-tools-1.0.18-6.fc33.aarch64.rpm 293 kB/s | 94 kB 00:00
(3/3): stress-ng-0.12.01-1.fc33.aarch64.rpm 1.5 MB/s | 518 kB 00:00
-----------------------------------------------------------------
Total 678 kB/s | 743 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : lksctp-tools-1.0.18-6.fc33.aarch64 1/3
Running scriptlet: lksctp-tools-1.0.18-6.fc33.aarch64 1/3
Installing : Judy-1.0.5-24.fc33.aarch64 2/3
Installing : stress-ng-0.12.01-1.fc33.aarch64 3/3
Running scriptlet: stress-ng-0.12.01-1.fc33.aarch64 3/3
Verifying : stress-ng-0.12.01-1.fc33.aarch64 1/3
Verifying : Judy-1.0.5-24.fc33.aarch64 2/3
Verifying : lksctp-tools-1.0.18-6.fc33.aarch64 3/3
Installed:
Judy-1.0.5-24.fc33.aarch64 lksctp-tools-1.0.18-6.fc33.aarch64 stress-ng-0.12.01-1.fc33.aarch64
Complete!
Next I cloned your files and run the make and make install:
[tedsluis@fed127 fan]$ git clone https://gist.github.com/1c13096c4cd675f38405702e89e0c536.git
Cloning into '1c13096c4cd675f38405702e89e0c536'...
remote: Enumerating objects: 75, done.
remote: Total 75 (delta 0), reused 0 (delta 0), pack-reused 75
Receiving objects: 100% (75/75), 9.77 KiB | 1.63 MiB/s, done.
Resolving deltas: 100% (28/28), done.
[tedsluis@fed127 fan]$ cd 1c13096c4cd675f38405702e89e0c536/
✔ tedsluis@fed127 ~/fan/1c13096c4cd675f38405702e89e0c536 [master|✔] $ ls -l
total 16
4 -rw-r--r--. 1 tedsluis tedsluis 685 Jan 17 11:50 Makefile
8 -rw-r--r--. 1 tedsluis tedsluis 5025 Jan 17 11:50 pi_fan_hwpwm.c
4 -rw-r--r--. 1 tedsluis tedsluis 269 Jan 17 11:50 pi_fan_hwpwm.service
✔ tedsluis@fed127 ~/fan/1c13096c4cd675f38405702e89e0c536 [master|✔] $ make
gcc -Wall -o pi_fan_hwpwm pi_fan_hwpwm.c -lbcm2835
✔ tedsluis@fed127 ~/fan/1c13096c4cd675f38405702e89e0c536 [master|…1] $ ll
total 212
4 -rw-r--r--. 1 tedsluis tedsluis 685 Jan 17 11:50 Makefile
196 -rwxr-xr-x. 1 tedsluis tedsluis 228464 Jan 17 11:50 pi_fan_hwpwm
8 -rw-r--r--. 1 tedsluis tedsluis 5025 Jan 17 11:50 pi_fan_hwpwm.c
4 -rw-r--r--. 1 tedsluis tedsluis 269 Jan 17 11:50 pi_fan_hwpwm.service
✔ tedsluis@fed127 ~/fan/1c13096c4cd675f38405702e89e0c536 [master|…1] $ ls -l pi_fan_hwpwm
196 -rwxr-xr-x. 1 tedsluis tedsluis 228464 Jan 17 11:50 pi_fan_hwpwm
✔ tedsluis@fed127 ~/fan/1c13096c4cd675f38405702e89e0c536 [master|…1] $ sudo make install
install pi_fan_hwpwm /usr/local/sbin
cp pi_fan_hwpwm.service /etc/systemd/system/
systemctl enable pi_fan_hwpwm
Created symlink /etc/systemd/system/multi-user.target.wants/pi_fan_hwpwm.service → /etc/systemd/system/pi_fan_hwpwm.service.
! systemctl is-active --quiet pi_fan_hwpwm || systemctl stop pi_fan_hwpwm
systemctl start pi_fan_hwpwm
The install went well, but the executable won't run (as root):
[root@fed127 ~]# systemctl status pi_fan_hwpwm
● pi_fan_hwpwm.service - Hardware PWM control for Raspberry Pi 4 Case Fan
Loaded: loaded (/etc/systemd/system/pi_fan_hwpwm.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Sun 2021-01-17 11:51:14 CET; 38min ago
Process: 111394 ExecStart=/usr/local/sbin/pi_fan_hwpwm (code=exited, status=1/FAILURE)
Main PID: 111394 (code=exited, status=1/FAILURE)
CPU: 9ms
Jan 17 11:51:14 fed127 systemd[1]: pi_fan_hwpwm.service: Scheduled restart job, restart counter is at 5.
Jan 17 11:51:14 fed127 systemd[1]: Stopped Hardware PWM control for Raspberry Pi 4 Case Fan.
Jan 17 11:51:14 fed127 systemd[1]: pi_fan_hwpwm.service: Start request repeated too quickly.
Jan 17 11:51:14 fed127 systemd[1]: pi_fan_hwpwm.service: Failed with result 'exit-code'.
Jan 17 11:51:14 fed127 systemd[1]: Failed to start Hardware PWM control for Raspberry Pi 4 Case Fan.
It has execute permissions:
[root@fed127 ~]# ll /usr/local/sbin/pi_fan_hwpwm
196 -rwxr-xr-x. 1 root root 228464 Jan 17 11:51 /usr/local/sbin/pi_fan_hwpwm
Here the journal:
[root@fed127 ~]# journalctl --unit pi_fan_hwpwm
-- Logs begin at Sat 2020-11-21 19:53:17 CET, end at Sun 2021-01-17 12:19:29 CET. --
Jan 17 11:51:12 fed127 systemd[1]: Started Hardware PWM control for Raspberry Pi 4 Case Fan.
Jan 17 11:51:12 fed127 pi_fan_hwpwm[111361]: bcm2835_init: gpio mmap failed: Operation not permitted
Jan 17 11:51:12 fed127 pi_fan_hwpwm[111361]: bcm2835_init() failed
Jan 17 11:51:12 fed127 systemd[1]: pi_fan_hwpwm.service: Main process exited, code=exited, status=1/FAILURE
Jan 17 11:51:12 fed127 systemd[1]: pi_fan_hwpwm.service: Failed with result 'exit-code'.
Jan 17 11:51:13 fed127 systemd[1]: pi_fan_hwpwm.service: Scheduled restart job, restart counter is at 1.
Jan 17 11:51:13 fed127 pi_fan_hwpwm[111390]: bcm2835_init: gpio mmap failed: Operation not permitted
Jan 17 11:51:13 fed127 pi_fan_hwpwm[111390]: bcm2835_init() failed
Jan 17 11:51:13 fed127 systemd[1]: Stopped Hardware PWM control for Raspberry Pi 4 Case Fan.
Jan 17 11:51:13 fed127 pi_fan_hwpwm[111392]: bcm2835_init: gpio mmap failed: Operation not permitted
Jan 17 11:51:13 fed127 pi_fan_hwpwm[111392]: bcm2835_init() failed
Jan 17 11:51:13 fed127 systemd[1]: Started Hardware PWM control for Raspberry Pi 4 Case Fan.
Jan 17 11:51:13 fed127 systemd[1]: pi_fan_hwpwm.service: Main process exited, code=exited, status=1/FAILURE
Jan 17 11:51:13 fed127 systemd[1]: pi_fan_hwpwm.service: Failed with result 'exit-code'.
I am not able to execute it manual:
[root@fed127 ~]# /usr/local/sbin/pi_fan_hwpwm
bcm2835_init: gpio mmap failed: Operation not permitted
bcm2835_init() failed
Also the with -v option the same error is returned.
Using strace I see this:
[root@fed127 ~]# strace /usr/local/sbin/pi_fan_hwpwm
execve("/usr/local/sbin/pi_fan_hwpwm", ["/usr/local/sbin/pi_fan_hwpwm"], 0xffffcdb10840 /* 21 vars */) = 0
brk(NULL) = 0x128c6000
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=32146, ...}) = 0
mmap(NULL, 32146, PROT_READ, MAP_PRIVATE, 3, 0) = 0xffff9acfb000
close(3) = 0
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\360J\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\351\32\275\235t2\376Ww\37\0~\370\373\257<"..., 68, 768) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=3125592, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff9acf9000
mmap(NULL, 1596208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xffff9ab41000
mprotect(0xffff9aca8000, 90112, PROT_NONE) = 0
mmap(0xffff9acbe000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16d000) = 0xffff9acbe000
mmap(0xffff9acc4000, 11056, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff9acc4000
close(3) = 0
mprotect(0xffff9acbe000, 12288, PROT_READ) = 0
mprotect(0x41f000, 4096, PROT_READ) = 0
mprotect(0xffff9ad06000, 4096, PROT_READ) = 0
munmap(0xffff9acfb000, 32146) = 0
getpid() = 113109
openat(AT_FDCWD, "/run/pi_fan_hwpwm.pid", O_RDWR|O_CREAT, 0666) = 3
fcntl(3, F_SETLK, {l_type=F_WRLCK, l_whence=SEEK_SET, l_start=0, l_len=0}) = 0
ftruncate(3, 0) = 0
write(3, "113109\n", 7) = 7
close(3) = 0
brk(NULL) = 0x128c6000
brk(0x128e7000) = 0x128e7000
brk(NULL) = 0x128e7000
openat(AT_FDCWD, "/proc/device-tree/soc/ranges", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=48, ...}) = 0
read(3, "~\0\0\0\0\0\0\0\376\0\0\0\1\200\0\0|\0\0\0\0\0\0\0\374\0\0\0\2\0\0\0"..., 4096) = 48
close(3) = 0
geteuid() = 0
openat(AT_FDCWD, "/dev/mem", O_RDWR|O_SYNC) = 3
mmap(NULL, 25165824, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0xfe000000) = -1 EPERM (Operation not permitted)
write(2, "bcm2835_init: gpio mmap failed: "..., 56bcm2835_init: gpio mmap failed: Operation not permitted
) = 56
close(3) = 0
write(2, "bcm2835_init() failed\n", 22bcm2835_init() failed
) = 22
exit_group(1) = ?
+++ exited with 1 +++
Do you have any idea what can be wrong? I very much appreciate any help!
I found out why I am not able to use this solution. It uses the legacy interface /sys/class/gpio, still available in raspios and some other distros. Several other Linux distros, like Fedora 32+ are already compiled without this legacy interface, so there’s no /sys/class/gpio on the system. They use the new character device /dev/gpiochipN provided by the upstream kernel.
I found a python module that is using the new /dev/gpiochipN interface. check my solution here: https://github.com/tedsluis/raspberry-pi-pwm-fan-control
Anyway, thanks for all the provided info!
Thanks very much for this. So far it works perfectly well with a Noctua 41x10 fan just connected to the +5/gnd/gpio18 pins with no extra fiddling.
I find myself wondering about paying with the transfer function and using hysterisis curves and reading the cpu usage stats to predicitvely spool up the fan... another rabbit hole to plunge into some day. In the meantime, it will make life a lot quieter; those Noctua fans on full power are surprisingly loud.
Thank you so much! spent quite a while looking for something like this. Works great on my pi 4 with ubuntu 18.04. Installation was easy and everything is much quieter. Thanks again!
this may be a dumb question but is there an easy way to change the temperature targets?
Ah, problem. There seems to be no control over the fan when running on the 64bit Raspbian OS. Fan is running flat out and the 3rd item in the run file is always 0.0
I guess this might take some fiddling.
{Later}
OK, not a software issue, happily. Turns out that gpio13 - at least on my Pi 4 running 64 bit OS and as soldered to a prototyping board - does not seem to provide the PWM signal. As soon as I moved the connection to gpio18 it works perfectly.
I don't have one of these fans. I cobbled together a 2n2222a transistor to the pwm pin. (With a flyback diode, etc.) For my basic 2 wire fan, (which appears to be a brushless DC Fan Model GF3007BS 5V 0.1A) the pwm frequency is too high for the fan to start. The pwm does work as expected and the signal is correct at the fan. If one increases the divider on bcm2835_pwm_set_clock(1024) it works for more plebian fans. Unfortunately this is in hearing range. Setting the pwm frequency around 100 Hz isn't too objectionable.
Unfortunately, I have to report that now both PWM pins, GPIO#13 and GPIO#18 are dead. Drawing 2.6mA=(3.3V-0.7V)/980ohms has resulted in both GPIO pins being stuck at 0. This seems pretty fragile. My adapter only used GPIO#18, so I am at a loss as to why GPIO#13 isn't functional. Pins and functionality were measured with a Siglent SDS1202X-E oscilloscope.
So I can report the background service appears to be running, but the GPIO pins are no longer functional. Guess, I'll have to revert to powering the fan @100% off the 3.3V, which was where I started.
I may try an open loop PWM script (in the bcm2835-1.68 directory) to confirm if the pwm pins are destroyed.
Just tested pwm.c - the GPIO pin#18 is fine. For some reason the PWM frequency doesn't match what is stated. pwm.c claims that it will generate a 1.1 KHz waveform. Instead it runs at 3.3 KHz, with CLOCK_DIVIDER_16. When the duty cycle is long enough, the fan starts up. Now need to dig into your main routine - there is some sort of bug that my setup has found.
FYI, I am running on an RPI4B-2GB with Raspberry Pi OS 32bit.
No bug. The service didn't restart correctly. Killed that and all of the above works, save for changing the line in the code to lower the pwm clock rate. bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_512);
A very nice piece of work! There are some subtleties in the choice of duty factor and temperatures that I didn't initially appreciate.
But the service is not starting correctly after a reboot :(
If I stop and start it using sudo systemctl stop pi_fan_hwpwm followed by a start, I get no pwm control
If I then try running pwm, the pwm pin works. Then if I stop and start the service, it finally works (controls the pwm pin).
Can't figure out why the service isn't coming up correctly after a reboot. I'd like to put this on a RPI4 based NAS, but so far this setup hasn't earned my trust. I've built two HW modules - they both work the same. The SW sometimes fails to deliver a pwm signal to the pin - even though cat /run/pi_fan_hwpwm.state is reporting a PWM signal!
After a cold start #systemctl shows pi_fan_hwpwm.service is loaded active and running. However there is no physical pwm pin active.
pwm state is 89.6, but no activity at the pin, it is 0 volts. Not good.
If the service is running what would interfere with the pwm? Could it be the audio defaulting to analog which might take over the pin? At the moment I am testing headless.
The following are services that might be audio related.
alsa-restore.service is loaded active exited Save/Restore Sound card state
also-state.service is load active running Manage Sound Card State (restore and store)
sys-devices-platform-soc-fe00b840.mailbox-bcm2835_audio-sound-card0.device is loaded active and plugged (what does that mean?)
pwm does not survive reboot, nor shutdown on my system.
Found the problem. bcm2835 is silently failing after a restart. If one deletes all references to pwm1 in the source and recompiles only using pwm0 (GPIO#18), the service and functionality do survive reboot. With both GPIO#18 and GPIO#13 enabled (as in the source code above) the fan does not come on after a reboot (& hot condition).
Oblique reference to silent failure in bcm2835.h
If the library runs with any other effective UID (ie not root), then
bcm2835_init() will attempt to open /dev/gpiomem, and, if
successful, will only permit GPIO operations. In particular,
bcm2835_spi_begin() and bcm2835_i2c_begin() will return false and all
other non-gpio operations may fail silently or crash.
To me, this indicates that bcm2835 has been known to silently fail...
I want to change
#define ON_TEMP 65.
#define OFF_TEMP 60. pls
@alwynallan Thank you so much for your program. I was so annoyed by the "official" configuration of the fan. The high pitched fan nosie hunted me in my dreams. Though I still hear the fan, when the CPU is under moderate load it's still much better than before.
I followed your instruction and everything worked.
Here are my sys Info:
Linux rbpi 5.10.17-v8+ #1421 SMP PREEMPT Thu May 27 14:01:37 BST 2021 aarch64 GNU/Linux
One freature request would be a config file for the temperatures and rpms. So that you can change them without recompiling the program. Because I think the pi can run up to 80 degrees Celsius without throttling the CPU.
It would be great if your solution could find its way into the official raspi-config. But I don't know if your solution fits their idea of what "should" be in the program.
Thanks a lot! Worked for me!
Thank you, works perfectly on Ubuntu 20.04 on RPI4
I added code to instrument this tiny service for monitoring with Prometheus. Unless you're already using Prometheus/Grafana it's probably best to ignore this, and the default is to not use the code in this this update. To use it, get it running using my previous recipe, then
$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
$ sudo apt install cmake libmicrohttpd-dev
$ cd
$ git clone https://github.com/digitalocean/prometheus-client-c.git
$ cd prometheus-client-c
$ ./auto build
$ sudo make
$ sudo make install
$ cd ~/1c13096c4cd675f38405702e89e0c536
$ nano Makefile
INSTRUMENT_FOR_PROMETHEUS := true
$ make
$ sudo make install
$ curl localhost:8764/metrics
Thank you so much for this! Worked perfectly for a 64-bit Ubuntu server Raspberry Pi 8GB. As for the Prometheus integration, should I first get Prometheus running via its docker image and target the port you specify here (8764) or is this prometheus-client-c a standalone version?
EDIT: It seems I can't build the Prometheus C client, it gave me a Make error on the step GOPATH in the Dockerfile:
+ GOPATH=/gopath /usr/local/go/bin/go get github.com/prometheus/prom2json
/bin/sh: 1: /usr/local/go/bin/go: Exec format error
The command '/bin/sh -c set -x && apt-get update && ... && chmod +x /entrypoint && GOPATH=/gopath /usr/local/go/bin/go get github.com/prometheus/prom2json && ... && rm -rf /var/lib/apt/lists/*' returned a non-zero code: 2
make: *** [Makefile:2: docker] Error 2
Check this to fix it
hi,
I am using RPI 4 8GB model headless with Raspberry Pi OS Lite 64bit installed. I have this fan installed and connected to GPIO 18 pin.
My system is up to date.
I can follow your guide up to this point
$ sudo apt install -y build-essential git stress-ng
$ cd
$ wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz
$ tar zxvf bcm2835-1.68.tar.gz
$ cd bcm2835-1.68
$ ./configure
$ make
$ sudo make install
$ cd
$ git clone https://gist.github.com/1c13096c4cd675f38405702e89e0c536.git
$ cd 1c13096c4cd675f38405702e89e0c536
But when I follow the next step of make, I get the following error - " Makefile:19: *** missing separator (did you mean TAB instead of 8 spaces?). Stop."
I have tried a few times but I am kind of lost here on what to do next.
Is there an issue with the file/format or do you think I don't have the right hardware to follow this through?
Please let me know.
Best,
V
But when I follow the next step of make, I get the following error - " Makefile:19: *** missing separator (did you mean TAB instead of 8 spaces?). Stop."
@vincentkenny01 I've had the same error, the Makefile here is erroneously indented with spaces instead of a tab. You must replace manually those spaces with tabs on the Makefile.
@vincentkenny010 You can check my fork of this gist, I have indented correctly the Makefile there.
I think a project such as this deserves its own repo on Github, don't you think @alwynallan ? It's a feature highly requested on the internet, and there's so few working and efficient implementations of this. A repo would allow for pull requests and for issues discussions such as these.
I've just updated a Pi4 to Bookworm and can't compile this because bcm2835.h is not found. Searching with
find / -mount -name bcm28*.h -print
says it isn't there for reals.
WTF?
Did I forget to install some prerequisite?
D'OH! Yes. Missed the entire wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz thing etc. All done.
[For context see Jeff Geerling's Blog.]
Here is a comparison of the thermal response under stress using no fan, firmware control, and PWM control.

Stress script: https://gist.github.com/geerlingguy/91d4736afe9321cbfc1062165188dda4