First expose the hardware PWM interface in /sys/class/pwm by appending a line in /boot/config.txt and reboot:
dtoverlay=pwm-2chan
Source an N-channel JFET and a PiFan. I am not affiliated with Amazon but here are example URLS:
https://www.amazon.com/gp/product/B074DJGZ2S - JEFTs i buy in bulk since I use them in low-current LED projects
https://www.amazon.com/gp/product/B0792BW2VH - PiFans
This is PERL code and that dates me and I'm assuming Raspian here. But you can poke /sys/class/pwm/* with the language of your choice.
I'm also assuming you are using physical rPi pin 12 and tweak code based on your cooling needs.
Here's one of mine: http://mayeses.com/temp/pi_cooling.jpg
Here's the daemon, must run as root since writing to /sys/class/pwm/*
Code: Select all
#!/usr/bin/perl
# Quick daemon to spin pifan up/down based on CPU temp
# 28 Jul 2019 - Lee Mayes <ntac@mayeses.com>
# NOTE: You MUST enable /sys/class/pwm interface in /boot/config.txt by adding 'dtoverlay=pwm-2chan'
# Wiring of nchannel JFET/Fan:
# PiFan+ - directly to 5V rail on rpi (Pin 2 or 4)
# PiFan- - Drain on JFET
# rPi phys pin 12/BCOM 18 - Gate on JEFT
# rPi GND (pin 6,14,etc.) - Source on JFET
use strict;
my $debug = $ARGV[0]; # pass any arg to enable debug on STDOUT
use IPC::SysV qw(IPC_CREAT);
use IPC::Semaphore;
my $fan_pin = 18; # rPi Physcial pin #12/BCOM 18/PWM0
my $pwmdir = "/sys/class/pwm/pwmchip0"; # head of PWM /sys/class filesystem struct
my $poll_int = 1; # poll temp every Xs
my $temp_thres = 130; # Temp(F) to spin fan up past idle
my $idle_pwm = .6; # Low speed/0 noise PWM duty_cycle pct
my $max_temp = 155; # Temp(F) to run fan at full speed
my $fast_pwm = 1; # Fan on full speed PWM level 100%
my $period = 10000000; # PWM period in ns (100Hz here)
my $base_cycle = int($idle_pwm * $period); # Min speed duty cycle
my $max_cycle = int($fast_pwm * $period); # Max speed duty cycle
my $delta_cycle = $max_cycle - $base_cycle; # How much wiggle room based on temp
my $sem = &get_sem(); # Create semaphore for fanmon
print "base = $base_cycle + delta = $delta_cycle == max = $max_cycle\n" if ($debug);
# Initialize - enable pwm0
open(P,">$pwmdir/export") ||
die "Cannot write to $pwmdir/export : $!\n";;
print P "0\n";
close(P);
sleep 1; # Allow hardware to set up
# Set PWM hardware clock rate in nanosecs
system "echo $period >$pwmdir/pwm0/period "; # cannot write in perl, inappro IOCTL? so fork()
# Enable it
system "echo 1 >$pwmdir/pwm0/enable";
while ( 1 ) { # Forever
my ($c,$f) = &read_temp; # Check temp
print "$c C, $f F\n" if ($debug);
my $speed = $base_cycle;
my $offset = 0;
if ($f >= $max_temp) { # overtemp, run @ max
$speed = int($period * $fast_pwm);
$offset = 1;
} elsif ($f >= $temp_thres) { # rPi has the vapors
$offset = 1 - ($max_temp - $f) / ($max_temp - $temp_thres);
$speed = $base_cycle + int($offset * $delta_cycle)
}
print " \$delta = $delta_cycle, \$offset = $offset, \$speed = $speed\n" if $debug;
open(P,">$pwmdir/pwm0/duty_cycle") ||
die "Cannot write to $pwmdir/pwm0/duty_cycle : $!\n";;
print P "$speed" if ($debug);
close(P);
$speed = int(100*($speed/$period));
$sem->setval(1,$speed); # poke speed in sem 1
sleep($poll_int);
}
sub read_temp { # Grab ARM chip temp
open(C,"/sys/class/thermal/thermal_zone0/temp");
my $c = <C>;
close(C);
chomp($c);
$c /= 1000; # Temp in C
my $f = ($c * 1.8) + 32; # Temp in F
$sem->setval(0,int($f)); # poke temp in sem 0
return ($c,$f);
}
sub get_sem {
my $keyfile = "/var/tmp/.keyfile";
my $key;
if (-f $keyfile) {
open(KEY,"$keyfile");
$key = <KEY>;
chomp($key);
close(KEY);
} else {
print "Creating $keyfile\n" if ($debug);
open(KEY,">$keyfile") ||
die "Cannot write to $keyfile : $!\n";
$key = IPC::SysV::ftok($keyfile,2);
print KEY "$key\n";
close(KEY);
}
my $flags = 0644;
my $sem = new IPC::Semaphore($key,2,$flags); # Open semaphore
unless ($sem) {
$flags = $flags | IPC_CREAT;
$sem = new IPC::Semaphore($key,2,$flags); # Create if it does not exist
print "Create semaphore with key $key\n" if ($debug);
}
return($sem);
}
Code: Select all
#!/usr/bin/perl
# monitor sem set by fan_daemon
use strict;
use IPC::SysV qw(IPC_RMID IPC_STAT IPC_CREAT); # SysV IPC
use IPC::Semaphore;
my $sem = &get_sem(); # Create semaphore
while ( 1 ) {
my ($temp,$speed) = $sem->getall();
print "Temp = $temp F, Speed = $speed %\n";
sleep(1);
}
sub get_sem {
my $node = shift; # Which sensor's sem?
my $keyfile = "/var/tmp/.keyfile";
unless (-r $keyfile) { die "cannot read $keyfile : $!\n" }
my $key = IPC::SysV::ftok($keyfile,1);
my $flags = 0444;
my $sem = new IPC::Semaphore($key,1,$flags); # Open semaphore
unless ($sem) { die "failed to open sem w/key $key : $!\n" }
return($sem);
}
leem@bench:~/bin $ gpio -v
gpio version: 2.50
Copyright (c) 2012-2018 Gordon Henderson
This is free software with ABSOLUTELY NO WARRANTY.
For details type: gpio -warranty
Raspberry Pi Details:
Type: Unknown17, Revision: 01, Memory: 0MB, Maker: Sony
* Device tree is enabled.
*--> Raspberry Pi 4 Model B Rev 1.1
* This Raspberry Pi supports user-level GPIO access.
leem@bench:~/bin $ sudo gpio readall
Oops - unable to determine board type... model: 17
leem@bench:~/bin $