Last active
September 23, 2024 16:31
-
-
Save alwynallan/68b6a9405de3ea493bbf53d2b0e9d52b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Deprecated, see https://gist.github.com/alwynallan/1c13096c4cd675f38405702e89e0c536 | |
If you have to use software PWM, it's still here. | |
*/ | |
/* | |
/ | |
/ pi_fan_pwm.c, alwynallan@gmail.com 12/2020, no license | |
/ | |
/ Need http://abyz.me.uk/rpi/pigpio/download.html | |
/ | |
/ Compile $ gcc -Wall pi_fan_pwm.c -lpigpio -lpthread -o pi_fan_pwm | |
/ | |
/ 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 | |
/ | |
/ Run $ sudo ./pi_fan_pwm -v | |
/ | |
/ Forget $ sudo ./pi_fan_pwm & | |
/ $ 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 <pigpio.h> | |
#define PWM_PIN 14 | |
#define HIGH_TEMP 80. | |
#define ON_TEMP 65. | |
#define OFF_TEMP 60. | |
#define MIN_FAN 235 | |
#define KICK_FAN 450 | |
#define MAX_FAN 1000 | |
unsigned pin = PWM_PIN; | |
int verbose = 0; | |
int fan_state = 0; | |
double temp = 25.0; | |
pid_t global_pid; | |
void usage() | |
{ | |
fprintf | |
(stderr, | |
"\n" \ | |
"Usage: sudo ./pi_fan_pwm [OPTION]...\n" \ | |
"\n" \ | |
" -g <n> Use GPIO n for fan's PWM input, default %d\n" \ | |
" -v Verbose output\n" \ | |
"\n" | |
, PWM_PIN | |
); | |
} | |
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 fan_loop(void) { | |
if(!fan_state && (temp > ON_TEMP)) { | |
gpioPWM(pin, KICK_FAN); | |
fan_state = 1; | |
return; | |
} | |
if(fan_state && (temp < OFF_TEMP)) { | |
gpioPWM(pin, 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 > 1000) out = 1000; | |
gpioPWM(pin, out); | |
} | |
} | |
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 > 31) 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_pwm.pid", buf); | |
gpioCfgClock(2, 1, 1); | |
if(gpioInitialise()<0) fatal(0, "gpioInitialise() failed"); | |
if(gpioSetPWMfrequency(pin, 20000) != 20000) fatal(0, "PWM freqency error"); | |
gpioSetPWMrange(pin, 1000); | |
gpioPWM(pin, 0); | |
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, %d\n", loop/4, temp, gpioGetPWMdutycycle(pin)); | |
run_write("/run/pi_fan_pwm.state", buf); | |
if(verbose) fputs(buf, stdout); | |
} | |
usleep(250000); | |
} | |
gpioTerminate(); | |
exit(EXIT_SUCCESS); | |
} |
Deprecated!
I have implemented hardware PWM, and started a new Gist for it https://gist.github.com/alwynallan/1c13096c4cd675f38405702e89e0c536 . It uses 50x less CPU resources than this.
Very nice!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
[For context see Jeff Geerling's Blog.]
This works better, and quieter, than the raspberrypi.org firmware controller, and obviously better than no fan. It currently uses 12% of one core, which is an unfortunate side effect of using the pigpio library, but I'm still looking for an alternative. Also needs proper installation under systemd.
Stress script: https://gist.github.com/geerlingguy/91d4736afe9321cbfc1062165188dda4