Skip to content

Commit e15d562

Browse files
authored
Merge pull request #7 from ar51an/3.1
3.1
2 parents b29e701 + 80887cd commit e15d562

File tree

3 files changed

+225
-18
lines changed

3 files changed

+225
-18
lines changed

README.md

+29-14
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Raspberry Pi fan-control service to adjust PWM fan speed automatically based on
2222
<br/>
2323

2424
<div align="center">
25-
<img src="https://github.com/ar51an/raspberrypi-fan-control/assets/11185794/c16ae27c-ea3b-46ad-9397-ea79e5471a84?raw=true" alt="resource-usage"/>
25+
<img src="https://github.com/ar51an/temp/assets/11185794/964e5520-89ff-44f1-ab09-0eddf0d11706?raw=true" alt="resource-usage"/>
2626
</div>
2727
<br/>
2828

@@ -36,9 +36,9 @@ I connected Noctua fan wires directly to the RP4 GPIO pins. It's been almost 3 y
3636
> `⚠️` **WARNING:** ***I accept no responsibility if you damage your Raspberry Pi or fan.***
3737
3838
#### Specs:
39-
> |Noctua Fan |HW |OS |pigpio C Lib Ver|
40-
> |:--------------------|:-----------------------|:----------------------------|:---------------|
41-
> |`NF-A4x10 5V PWM Fan`|`Raspberry Pi 4 Model B`|`raspios-bookworm-arm64-lite`|`79` |
39+
> |Noctua Fan |HW |OS |WiringPi|pigpio|
40+
> |:--------------------|:-----------------------|:----------------------------|:-------|:-----|
41+
> |`NF-A4x10 5V PWM Fan`|`Raspberry Pi 4 Model B`|`raspios-bookworm-arm64-lite`|`3.1` |`79` |
4242
#
4343

4444
### Hardware Prep
@@ -73,15 +73,30 @@ The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fa
7373

7474
#
7575
### Steps
76-
#### ❯ Install pigpio
77-
* Install `pigpio C` library. You can download it from [pigpio](https://codeload.github.com/joan2937/pigpio/zip/refs/heads/master).
76+
#### ❯ Install C Library
77+
* Install **either** `WiringPi` **or** `pigpio` C library.
7878

79-
> `unzip -o pigpio-master.zip`
80-
> `make`
81-
> `sudo make install`
79+
* **WiringPi**
80+
[Download](https://github.com/WiringPi/WiringPi/releases/download/3.1/wiringpi_3.1_arm64.deb) and install `WiringPi` C library.
81+
> Install:
82+
> `sudo dpkg -i wiringpi_3.1_arm64.deb`
83+
84+
> `ℹ️` **Note:**
85+
> WiringPi has been revived. Latest release supports RasberryPi OS Bookworm. Check the github [repo](https://github.com/WiringPi/WiringPi) for updates.
86+
87+
* **Pigpio**
88+
[Download](https://codeload.github.com/joan2937/pigpio/zip/refs/heads/master) and install `pigpio` C library.
89+
> Install:
90+
> `unzip -o pigpio-master.zip`
91+
> `make`
92+
> `sudo make install`
93+
94+
> `ℹ️` **Note:**
95+
> Uninstall Pigpio:
96+
> If pigpio was installed using the previous step, manually remove all files under `/usr/local/*` mentioned [here](https://abyz.me.uk/rpi/pigpio/faq.html#Library_update_fails). To remove the distro provided pigpio package run cmd `sudo apt --purge autoremove pigpio`
8297
8398
#### ❯ Install FanControl
84-
* Create folder `/opt/gpio/fan`. Copy `fan-control` and `params.conf` from the latest release under `build` folder to this newly created folder `/opt/gpio/fan`. Make sure both files are under the ownership of root and `fan-control` is executable. **Fan-control will work with default values without `params.conf`.**
99+
* Download the latest fan-control [release](https://github.com/ar51an/raspberrypi-fan-control/releases) for the library that was installed in the previous step. Create folder `/opt/gpio/fan`. Copy `fan-control` and `params.conf` from the latest release under `build` folder to this newly created folder `/opt/gpio/fan`. Make sure both files are under the ownership of root and `fan-control` is executable. **Fan-control will work with default values without `params.conf`.**
85100

86101
> **Create folder:**
87102
> `sudo mkdir -p /opt/gpio/fan/`
@@ -111,9 +126,6 @@ The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fa
111126
> **Check Journal Logs:**
112127
> `sudo journalctl -u fan-control`
113128
114-
> `ℹ️` **Note:**
115-
> Default service starts fan-control early in the boot process. It works fine with `lite RaspiOS`. In case any issue or warning with fan-control startup at boot, you can modify the service to start late in the boot process. Edit `fan-control.service`. Uncomment `After=multi-user.target` and `WantedBy=multi-user.target`. Comment out `WantedBy=sysinit.target`. Save and reboot.
116-
117129
#
118130
### Points to Note
119131
* Noctua's green RPM signal wire (aka tacho) connectivity is optional. It generates tacho output signal. It does not add any value in controlling fan speed. That is why it is disabled by default in `params.conf`. To retrieve RPM periodically through tacho output signal in the logs, connect the wire as mentioned under `Hardware Prep` and enable it in `params.conf`. Change `TACHO_ENABLED` to `1`.
@@ -151,5 +163,8 @@ The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fa
151163
> `sudo apt install libsystemd-dev`
152164
153165
* Binary is available in the release. If for any reason you want to recompile.
154-
> `sudo gcc -Wall -O2 fan-control.c -o fan-control -lpigpio -lsystemd`
166+
> WiringPi:
167+
> `sudo gcc -Wall -O2 fan-control-wiringpi.c -o fan-control -lwiringPi -lsystemd`
168+
> Pigpio:
169+
> `sudo gcc -Wall -O2 fan-control-pigpio.c -o fan-control -lpigpio -lsystemd`
155170
</div>

src/fan-control.c renamed to src/fan-control-pigpio.c

+5-4
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ void delay (unsigned int waitMillisec)
151151
struct timespec sleepInterval;
152152
sleepInterval.tv_sec = (time_t) (waitMillisec/msInSec);
153153
sleepInterval.tv_nsec = (long) (waitMillisec%msInSec)*1000000L;
154-
nanosleep (&sleepInterval, NULL);
154+
nanosleep(&sleepInterval, NULL);
155155
}
156156

157157
void start () {
@@ -166,10 +166,9 @@ void start () {
166166
}
167167

168168
static void signalHandler (int _) {
169-
// Exit controller on Ctrl+C
169+
// Exit controller
170170
(void)_;
171171
keepRunning = 0;
172-
//printf("\r");
173172
}
174173

175174
void cleanup () {
@@ -187,7 +186,9 @@ void cleanup () {
187186

188187
int main (void)
189188
{
190-
signal(SIGINT, signalHandler);
189+
struct sigaction act = {0};
190+
act.sa_handler = signalHandler;
191+
sigaction(SIGTERM, &act, NULL);
191192
initFanControl();
192193
if (initPigpio() < 0) return 1;
193194
setupPwm();

src/fan-control-wiringpi.c

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* PWM Fan Controller:
3+
* PWM fan controller is developed using WiringPi C library for RPi4. It is
4+
* intended for "Noctua NF-A4x10 5V PWM" fan, it may work for any PWM fan
5+
* with slight adjustment according to the fan specs.
6+
* Copyright (c) 2021 - ar51an
7+
*/
8+
9+
#include <stdio.h>
10+
#include <time.h>
11+
#include <signal.h>
12+
#include <wiringPi.h>
13+
#include <systemd/sd-journal.h>
14+
15+
int PWM_PIN = 18; // HW PWM works at GPIO [12, 13, 18 & 19] on RPi4B
16+
int TACHO_PIN = 23;
17+
int RPM_MAX = 5000; // Noctua Specs: Max=5000
18+
int RPM_MIN = 1500; // Noctua Specs: Min=1000 [Kept 1500 as Min]
19+
int RPM_OFF = 0;
20+
int TEMP_MAX = 55; // Above this temperature [FAN=ON At Max speed]
21+
int TEMP_LOW = 40; // Below this temperature [FAN=OFF]
22+
int WAIT = 5000; // MilliSecs before adjusting RPM
23+
int TACHO_ENABLED = 0; // TACHO Specific [Enable Tacho: 0=Disable 1=Enable]
24+
const int PULSE = 2; // TACHO Specific [Noctua fan puts out 2 pulses per revolution]
25+
volatile int intCount = 0; // TACHO Specific [Interrupt Counter]
26+
int getRpmStartTime = 0; // TACHO Specific
27+
int origPwmPinMode = -1;
28+
int origTachoPinMode = -1;
29+
float tempLimitDiffPct = 0.0f;
30+
char thermalFilename[] = "/sys/class/thermal/thermal_zone0/temp";
31+
static volatile sig_atomic_t keepRunning = 1;
32+
33+
void logConfParams () {
34+
char logFormat[] = "Config values loaded: PWM_PIN=%d | TACHO_PIN=%d | RPM_MAX=%d | RPM_MIN=%d | RPM_OFF=%d "
35+
"| TEMP_MAX=%d | TEMP_LOW=%d | WAIT=%d | TACHO_ENABLED=%d | THERMAL_FILE=%s";
36+
sd_journal_print(LOG_INFO, logFormat, PWM_PIN, TACHO_PIN, RPM_MAX, RPM_MIN, RPM_OFF, \
37+
TEMP_MAX, TEMP_LOW, WAIT, TACHO_ENABLED, thermalFilename);
38+
}
39+
40+
void initFanControl () {
41+
/* Assign global vars with config file (if provided) values */
42+
FILE *confFile;
43+
char confFilename[] = "/opt/gpio/fan/params.conf";
44+
confFile = fopen(confFilename, "r");
45+
if (confFile != NULL) {
46+
char confFormat[] = "PWM_PIN=%d TACHO_PIN=%d RPM_MAX=%d RPM_MIN=%d RPM_OFF=%d TEMP_MAX=%d "
47+
"TEMP_LOW=%d WAIT=%d TACHO_ENABLED=%d THERMAL_FILE=%s";
48+
fscanf(confFile, confFormat, &PWM_PIN, &TACHO_PIN, &RPM_MAX, &RPM_MIN, &RPM_OFF, \
49+
&TEMP_MAX, &TEMP_LOW, &WAIT, &TACHO_ENABLED, thermalFilename);
50+
logConfParams();
51+
fclose(confFile);
52+
}
53+
else
54+
sd_journal_print(LOG_WARNING, "params.conf not found - Default values loaded");
55+
/* Calculate values of global vars */
56+
tempLimitDiffPct = (float) (TEMP_MAX-TEMP_LOW)/100;
57+
if (TACHO_ENABLED && TACHO_ENABLED != 1) TACHO_ENABLED = 0;
58+
}
59+
60+
void initWiringPi () {
61+
/* Initialize wiringPi, calling 1 of 4 setup methods */
62+
wiringPiSetupGpio(); // Defaults to GPIO/BCM pin numbers
63+
}
64+
65+
int getPinMode (int pin) {
66+
/* Mode Name Mapping: INPUT=0, OUTPUT=1, ALT0=4, ALT1=5, ALT2=6, ALT3=7, ALT4=3, ALT5=2 */
67+
return getAlt(pin);
68+
}
69+
70+
void setFanSpeed (int pin, int speed) {
71+
pwmWrite(pin, speed);
72+
}
73+
74+
int getCurrTemp () {
75+
int currTemp = 0;
76+
FILE *thermalFile;
77+
thermalFile = fopen(thermalFilename, "r");
78+
fscanf(thermalFile, "%d", &currTemp);
79+
fclose(thermalFile);
80+
currTemp = ((float) currTemp/1000)+0.5;
81+
return currTemp;
82+
}
83+
84+
void setupPwm () {
85+
/* Set pwm fan freq=25kHz (Noctua whitepaper stated as Intel's recommendation for PWM FANs)
86+
* PWM crystal oscillator clock base frequency: RPI3=19.2MHz & RPI4=54MHz. pwmSetClock()
87+
* takes a divisor of base fequency: "19200000/768=25kHz". It adjusts the divisor for RPI4
88+
* 54MHz in the code itself as "divisor=540*divisor/192"
89+
*/
90+
int pwmClock = 768;
91+
origPwmPinMode = getPinMode(PWM_PIN);
92+
pinMode(PWM_PIN, PWM_OUTPUT);
93+
// Using default balanced mode instead of mark:space mode
94+
//pwmSetMode(PWM_MODE_MS);
95+
pwmSetClock(pwmClock);
96+
pwmSetRange(RPM_MAX); // Set PWM range to Max RPM
97+
setFanSpeed(PWM_PIN, RPM_OFF); // Set Fan speed to 0 initially
98+
//printf("[PWM] GPIO:Mode | %d:%d\n", PWM_PIN, origPwmPinMode);
99+
return;
100+
}
101+
102+
void interruptHandler () {
103+
/* Number of interrupts generated between call to getFanRpm */
104+
intCount++;
105+
return;
106+
}
107+
108+
void setupTacho () {
109+
origTachoPinMode = getPinMode(TACHO_PIN);
110+
pinMode(TACHO_PIN, INPUT);
111+
pullUpDnControl(TACHO_PIN, PUD_UP);
112+
getRpmStartTime = time(NULL);
113+
wiringPiISR(TACHO_PIN, INT_EDGE_FALLING, interruptHandler);
114+
return;
115+
}
116+
117+
void getFanRpm () {
118+
int duration = 0;
119+
float frequency = 0.0f;
120+
int rpm = RPM_OFF;
121+
duration = (time(NULL)-getRpmStartTime);
122+
frequency = (intCount/duration);
123+
rpm = (int) (frequency*60)/PULSE;
124+
getRpmStartTime = time(NULL);
125+
intCount = 0;
126+
//if (rpm) printf("\e[30;38;5;67m[TACHO] Frequency: %.1fHz | RPM: %d\n\e[0m", frequency, rpm);
127+
if (rpm) sd_journal_print(LOG_DEBUG, "[TACHO] Frequency: %.1fHz | RPM: %d", frequency, rpm);
128+
return;
129+
}
130+
131+
void setFanRpm () {
132+
int rpm = RPM_OFF;
133+
static int lastRpm = 0;
134+
float currTempDiffPct = 0.0f;
135+
int currTemp = getCurrTemp();
136+
int tempDiff = (currTemp-TEMP_LOW);
137+
if (tempDiff > 0) {
138+
currTempDiffPct = (tempDiff/tempLimitDiffPct);
139+
rpm = (int) (currTempDiffPct*RPM_MAX)/100;
140+
rpm = rpm < RPM_MIN ? RPM_MIN : rpm > RPM_MAX ? RPM_MAX : rpm;
141+
//printf("\e[30;38;5;139m[PWM] Temp: %d | TempDiff: %.1f%% | RPM: %d\n\e[0m", currTemp, currTempDiffPct, rpm);
142+
sd_journal_print(LOG_DEBUG, "[PWM] Temp: %d | TempDiff: %.1f%% | RPM: %d", currTemp, currTempDiffPct, rpm);
143+
}
144+
if (lastRpm != rpm) setFanSpeed(PWM_PIN, rpm);
145+
lastRpm = rpm;
146+
return;
147+
}
148+
149+
void start () {
150+
if (TACHO_ENABLED) {
151+
setupTacho();
152+
while (keepRunning) { setFanRpm(); getFanRpm(); delay(WAIT); }
153+
}
154+
else {
155+
while (keepRunning) { setFanRpm(); delay(WAIT); }
156+
}
157+
return;
158+
}
159+
160+
static void signalHandler (int _) {
161+
// Exit controller on Ctrl+C
162+
(void)_;
163+
keepRunning = 0;
164+
//printf("\r");
165+
}
166+
167+
void cleanup () {
168+
// PWM pin cleanup
169+
setFanSpeed(PWM_PIN, RPM_OFF);
170+
pinMode(PWM_PIN, origPwmPinMode);
171+
//pullUpDnControl(PWM_PIN, PUD_DOWN);
172+
// TACHO pin cleanup
173+
if (TACHO_ENABLED) pinMode(TACHO_PIN, origTachoPinMode);
174+
//pullUpDnControl(TACHO_PIN, PUD_DOWN);
175+
sd_journal_print(LOG_INFO, "Cleaned up - Exiting ...");
176+
return;
177+
}
178+
179+
int main (void)
180+
{
181+
struct sigaction act = {0};
182+
act.sa_handler = signalHandler;
183+
sigaction(SIGINT, &act, NULL);
184+
initFanControl();
185+
initWiringPi();
186+
setupPwm();
187+
sd_journal_print(LOG_INFO, "Initialized and running ...");
188+
start();
189+
cleanup();
190+
return 0;
191+
}

0 commit comments

Comments
 (0)