Skip to content

Commit 11ed8e9

Browse files
authored
Merge pull request #5 from ar51an/3.0
pigpio update
2 parents 4ea7476 + 0a62f1c commit 11ed8e9

2 files changed

Lines changed: 75 additions & 76 deletions

File tree

README.md

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,43 @@
1818
<div align="justify">
1919

2020
### Intro
21-
Service to adjust RP4 PWM (Pulse Width Modulation) fan speed automatically based on CPU temperature. It will help in reducing fan noise and power consumption. It is written in C. **Main objective is to keep it fast and use minimum CPU and memory resources**.
21+
Raspberry Pi fan-control service to adjust PWM fan speed automatically based on CPU temperature. It will help in reducing fan noise and power consumption. It is written in C. **Main objective is to keep it fast and use minimum CPU and memory resources**.
2222
<br/>
2323

2424
<div align="center">
25-
<img src="https://user-images.githubusercontent.com/11185794/202808642-ba7d83c2-aec5-4079-92e5-48765c693dc3.png?raw=true" alt="resource-usage"/>
25+
<img src="https://github.com/ar51an/raspberrypi-fan-control/assets/11185794/c16ae27c-ea3b-46ad-9397-ea79e5471a84?raw=true" alt="resource-usage"/>
2626
</div>
2727
<br/>
2828

29-
If you just want PWM fan On/Off based on CPU temperature. Connect fan's `PWM, ground and +5V wires` directly to the GPIO pins. Enable fan **either** from raspi-config **or** UI. Set the PWM pin and CPU temperature in the setup. Lowest temperature you can specify from setup is 60°C. This limit can be bypassed by editing `/boot/config.txt` manually. Search for `dtoverlay=gpio-fan` entry and change the `temp=60000` value to your desired temperature. Fan will start at the specified CPU temperature and it will stop 10°C below that. The downside is fan will run at full speed and bit noisy, specially if you are using an open RP4 case.
29+
If you just want PWM fan On/Off based on CPU temperature. Connect fan's `PWM, ground and +5V wires` directly to the GPIO pins. Enable fan **either** from raspi-config **or** UI. Set the PWM pin and CPU temperature in the setup. Lowest temperature limit of 60°C can be bypassed by editing `/boot/firmware/config.txt` manually. Find `dtoverlay=gpio-fan` entry and change the `temp=60000` to the desired temperature. Fan will start at the specified CPU temperature and it will stop 10°C below that. The downside is fan will run at full speed and bit noisy, specially if you are using an open RP4 case.
3030
<br/>
3131

32-
This service is specifically written for `Noctua 5V PWM` fan and `Raspberry Pi 4`. It may work for other PWM fans and RP models. You should know the intended fan's specifications, like max / min `RPM` and `target frequency`. Adjust these values in code/config and rebuild the binary (if needed). Raspberry Pi crystal oscillator clock frequency on RPi4B is 54MHz and on all earlier models it is 19.2MHz. `WiringPi` function `pwmSetClock` requires a divisor of clock frequency to set the target frequency of fan. The process is well documented in the fan-control source code.
32+
This service is specifically written for `Noctua 5V PWM` fan and `Raspberry Pi 4`. It may work for other PWM fans and RP models. You should know the intended fan's specifications, like max / min `RPM` and `target frequency`. Adjust these values in code/config and rebuild the binary (if needed).
3333
<br/>
3434

35-
I connected Noctua fan wires directly to the RP4 GPIO pins. It's been almost 2 years without any issue, your mileage may vary. If your fan does not support PWM or you want to safeguard hardware **either** build your own circuit **or** buy a pre-built PCB with transistor and diode like [EZ RP Fan Controller](https://www.tindie.com/products/jeremycook/ez-fan2-tiny-raspberry-pi-fan-controller/).
36-
***WARNING: I accept no responsibility if you damage your Raspberry Pi or fan.***
35+
I connected Noctua fan wires directly to the RP4 GPIO pins. It's been almost 3 years without any issue, your mileage may vary. If your fan does not support PWM or you want to safeguard hardware **either** build your own circuit **or** buy a pre-built PCB with transistor and diode like [EZ RP Fan Controller](https://www.tindie.com/products/jeremycook/ez-fan2-tiny-raspberry-pi-fan-controller/).
36+
37+
> `⚠️` **WARNING:** ***I accept no responsibility if you damage your Raspberry Pi or fan.***
38+
3739
<br/>
3840

3941
#### Specs:
40-
> |Noctua Fan |HW |OS |WiringPi C Lib Ver|
41-
> |:--------------------|:-----------------------|:----------------------------|:-----------------|
42-
> |`NF-A4x10 5V PWM Fan`|`Raspberry Pi 4 Model B`|`raspios-bullseye-arm64-lite`|`2.70` |
42+
> |Noctua Fan |HW |OS |pigpio C Lib Ver|
43+
> |:--------------------|:-----------------------|:----------------------------|:---------------|
44+
> |`NF-A4x10 5V PWM Fan`|`Raspberry Pi 4 Model B`|`raspios-bookworm-arm64-lite`|`79` |
4345
#
4446

4547
### Hardware Prep
4648
* The default noctua fan connector will not connect directly to GPIO header. You need to do some modifications. There are multiple options:
4749
> **1 - Dupont Jumper Wires Male to Female:**
48-
> Noctua's existing wire is pretty long. Get short jumper wires somewhere between 2-4" long. Male part of jumper wire will connect to Noctua connector and Female part will connect to GPIO. This is what I used in the beginning for few months.
50+
> Noctua's existing wire is pretty long. Get short jumper wires somewhere between 2-4" long. Male part of jumper wire will connect to Noctua connector and Female part will connect to GPIO. I used it for few months.
4951
5052
> **2 - Dupont Female Pin Connectors 2.54mm Pitch:**
51-
> This is the one I am using now, as shown in the preview. It is the cleanest option. It could be the most expensive option if you do not have all the tools. You need dupont Female pin connectors, crimping tool, wire cutter/stripper, heat shrink tube/heat gun **or** dupont connector housing.
52-
> You can **either** buy connectors and housing kit **or** buy dupont connectors separately. Better options for dupont connectors are `Molex Crimp Terminals Series: 70058 Part No: 16020098` or `Harwin Series: M20 Part No: 1180042`. I used Molex connectors. For crimping I used IWISS `IWS-2820` crimping tool. Cover these connectors with **either** heat shrink tube (I used 3.00mm diameter tube) **or** dupont connector housing.
53-
> If you go with this route. You have to cut the noctua fan wire to the required length. **Make sure you calculate required wire length properly before cutting**. If you cut it too short you will end up inserting wire joints. Strip wires (practice some stripping on the unused end). Attach dupont connectors to the stripped wires, crimp them with crimpping tool. Add heat shrink tubes to the connectors and shrink them with heatgun or attach connector housing.
54-
53+
> It is the cleanest option, as shown in the preview. You need dupont Female pin connectors, crimping tool, wire cutter & stripper, heat-shrink-tube & heat-gun **or** dupont connector housing.
54+
> Better options for dupont connectors are `Molex Crimp Terminals Series: 70058 Part No: 16020098` or `Harwin Series: M20 Part No: 1180042`. I used Molex connectors and IWISS `IWS-2820` crimping tool. Cover these connectors with **either** heat shrink tube (3.00mm diameter tube) **or** dupont connector housing.
55+
5556
> **3 - Use Wires from Old Fan:**
56-
> If you have some old fan laying around that has dupont connector wires and you have no plan of using it. Cut the wires from that fan, cut the Noctua connector and do some wire joining. Noctua fan comes with 4 OmniJoin adaptors, you can use them as well for joining wires.
57+
> If you have some old unused fan laying around that has dupont connector wires. Cut the wires from that fan, cut the Noctua connector and do some wire joining. Noctua fan's OmniJoin adaptors can be used for joining wires.
5758
5859
* Complete specification of Noctua fan is available at [Noctua Whitepaper](https://noctua.at/pub/media/wysiwyg/Noctua_PWM_specifications_white_paper.pdf). Details of Raspberry Pi GPIO pin layout is available at [GPIO Pinout](https://pinout.xyz/). Screenshots attached for quick reference.
5960

@@ -70,19 +71,19 @@ I connected Noctua fan wires directly to the RP4 GPIO pins. It's been almost 2 y
7071
|Blue PWM Signal |Physical Pin 12|
7172
|Green RPM Signal|Physical Pin 16|
7273

73-
Fan's PWM signal wire is connected to the RP4 `Physical/Board pin 12 - GPIO/BCM pin 18`. This fan-control code uses GPIO 18 as default. There are 4 pins on RP4 that support hardware PWM `GPIO 12/13/18/19`. If you are going to use a different GPIO pin make sure you change the `PWM_PIN` in `params.conf` with the one you use.
74-
The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fan's RPM signal wire to the RP4 `Physical/Board pin 16 - GPIO/BCM pin 23`. By default, tacho output is disabled in `params.conf`. (refer to `Points to Note`)
74+
Fan's PWM signal wire is connected to the RP4 `Physical pin 12 - GPIO pin 18`. This fan-control code uses GPIO 18 as default. There are 4 pins on RP4 that support hardware PWM `GPIO 12/13/18/19`. If you are going to use a different GPIO pin make sure you change the `PWM_PIN` in `params.conf` with the one you use.
75+
The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fan's RPM signal wire to the RP4 `Physical pin 16 - GPIO pin 23`. By default, tacho output is disabled in `params.conf`. (refer to `Points to Note`)
7576

7677
#
7778
### Steps
78-
#### Install WiringPi
79-
* Install `WiringPi C` library. You can download it from [WiringPi](https://github.com/WiringPi/WiringPi). Installation instructions are available at [Install](https://github.com/WiringPi/WiringPi/blob/master/INSTALL).
79+
#### Install pigpio
80+
* Install `pigpio C` library. You can download it from [pigpio](https://codeload.github.com/joan2937/pigpio/zip/refs/heads/master).
8081

81-
> **Quick Reference:**
82-
> Unzip `sudo unzip -o WiringPi-master.zip -d WiringPi`
83-
> Build and Install `sudo ./build`
82+
> `unzip -o pigpio-master.zip`
83+
> `make`
84+
> `sudo make install`
8485
85-
#### Install FanControl
86+
#### Install FanControl
8687
* 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`.**
8788

8889
> **Create folder:**
@@ -113,7 +114,7 @@ The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fa
113114
> **Check Journal Logs:**
114115
> `sudo journalctl -u fan-control`
115116
116-
> **_NOTE:_**
117+
> `ℹ️` **Note:**
117118
> 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.
118119
119120
#
@@ -149,20 +150,9 @@ The green tachometer wire on Noctua fan is used to calculate RPM. Connect the fa
149150

150151
#
151152
### Build
152-
* Install `libsystemd-dev`. It is required if you are going to build fan-control source code. If you **do not** want journal logging at all from fan-control service you can skip `libsystemd-dev` package installation and remove journal logging from code, explained below.
153-
> **Install Package:**
153+
* Install `libsystemd-dev`. It is required for compiling fan-control source code.
154154
> `sudo apt install libsystemd-dev`
155155
156-
* Binary is available in the release. If for any reason you want to rebuild.
157-
> **Build command:**
158-
> `sudo gcc -Wall -O2 fan-control.c -o fan-control -lwiringPi -lsystemd`
159-
160-
* Test binary after build.
161-
> **Run binary:**
162-
> `sudo ./fan-control`
163-
> Ctrl+C to exit
164-
165-
* If you are not interested in journal logging. Comment out the include header `sd-journal.h` and journal logging lines starting with `sd_journal_print`
166-
> **Build command without journal logging:**
167-
> `sudo gcc -Wall -O2 fan-control.c -o fan-control -lwiringPi`
156+
* Binary is available in the release. If for any reason you want to recompile.
157+
> `sudo gcc -Wall -O2 fan-control.c -o fan-control -lpigpio -lsystemd`
168158
</div>

src/fan-control.c

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* PWM Fan Controller:
3-
* PWM fan controller is developed using WiringPi C library for RPi4. It is
3+
* PWM fan controller is developed using pigpio C library for RPi4. It is
44
* intended for "Noctua NF-A4x10 5V PWM" fan, it may work for any PWM fan
55
* with slight adjustment according to the fan specs.
66
* Copyright (c) 2021 - ar51an
@@ -9,21 +9,22 @@
99
#include <stdio.h>
1010
#include <time.h>
1111
#include <signal.h>
12-
#include <wiringPi.h>
12+
#include <pigpio.h>
1313
#include <systemd/sd-journal.h>
1414

15-
int PWM_PIN = 18; // HW PWM works at GPIO [12, 13, 18 & 19] on RPi4B
15+
int PWM_PIN = 18; // HW PWM works at GPIO [12, 13, 18 & 19] on RPi4B
1616
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]
17+
int FREQUENCY = 25000; // Noctua Specs: Target_Frequency=25kHz
18+
int RPM_MAX = 5000; // Noctua Specs: Max=5000
19+
int RPM_MIN = 1500; // Noctua Specs: Min=1000 [Kept 1500 as Min]
1920
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
21+
int TEMP_MAX = 55; // Above this temperature [FAN=ON At Max speed]
22+
int TEMP_LOW = 40; // Below this temperature [FAN=OFF]
23+
int WAIT = 5000; // Milliseconds before adjusting RPM
24+
int TACHO_ENABLED = 0; // TACHO Specific [Enable Tacho: 0=Disable 1=Enable]
25+
const int PULSE = 2; // TACHO Specific [Noctua fan puts out 2 pulses per revolution]
26+
volatile int intCount = 0; // TACHO Specific [Interrupt Counter]
27+
int getRpmStartTime = 0; // TACHO Specific
2728
int origPwmPinMode = -1;
2829
int origTachoPinMode = -1;
2930
float tempLimitDiffPct = 0.0f;
@@ -57,18 +58,24 @@ void initFanControl () {
5758
if (TACHO_ENABLED && TACHO_ENABLED != 1) TACHO_ENABLED = 0;
5859
}
5960

60-
void initWiringPi () {
61-
/* Initialize wiringPi, calling 1 of 4 setup methods */
62-
wiringPiSetupGpio(); // Defaults to GPIO/BCM pin numbers
61+
int initPigpio () {
62+
int config = gpioCfgGetInternals();
63+
config |= PI_CFG_NOSIGHANDLER;
64+
gpioCfgSetInternals(config);
65+
if (gpioInitialise() < 0) {
66+
sd_journal_print(LOG_ERR, "pigpio initialization failed ...");
67+
return -1;
68+
}
69+
return 0;
6370
}
6471

6572
int getPinMode (int pin) {
6673
/* Mode Name Mapping: INPUT=0, OUTPUT=1, ALT0=4, ALT1=5, ALT2=6, ALT3=7, ALT4=3, ALT5=2 */
67-
return getAlt(pin);
74+
return gpioGetMode(pin);
6875
}
6976

7077
void setFanSpeed (int pin, int speed) {
71-
pwmWrite(pin, speed);
78+
gpioPWM(pin, speed);
7279
}
7380

7481
int getCurrTemp () {
@@ -77,24 +84,16 @@ int getCurrTemp () {
7784
thermalFile = fopen(thermalFilename, "r");
7885
fscanf(thermalFile, "%d", &currTemp);
7986
fclose(thermalFile);
80-
currTemp = ((float) currTemp/1000) + 0.5;
87+
currTemp = ((float) currTemp/1000)+0.5;
8188
return currTemp;
8289
}
8390

8491
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;
9192
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
93+
gpioSetMode(PWM_PIN, PI_OUTPUT);
94+
gpioSetPWMfrequency(PWM_PIN, FREQUENCY);
95+
gpioSetPWMrange(PWM_PIN, RPM_MAX); // Set PWM range to Max RPM
96+
setFanSpeed(PWM_PIN, RPM_OFF); // Set Fan speed to 0 initially
9897
//printf("[PWM] GPIO:Mode | %d:%d\n", PWM_PIN, origPwmPinMode);
9998
return;
10099
}
@@ -107,10 +106,10 @@ void interruptHandler () {
107106

108107
void setupTacho () {
109108
origTachoPinMode = getPinMode(TACHO_PIN);
110-
pinMode(TACHO_PIN, INPUT);
111-
pullUpDnControl(TACHO_PIN, PUD_UP);
109+
gpioSetMode(TACHO_PIN, PI_INPUT);
110+
gpioSetPullUpDown(TACHO_PIN, PI_PUD_UP);
112111
getRpmStartTime = time(NULL);
113-
wiringPiISR(TACHO_PIN, INT_EDGE_FALLING, interruptHandler);
112+
gpioSetISRFunc(TACHO_PIN, FALLING_EDGE, 0, interruptHandler);
114113
return;
115114
}
116115

@@ -146,6 +145,15 @@ void setFanRpm () {
146145
return;
147146
}
148147

148+
void delay (unsigned int waitMillisec)
149+
{
150+
const int msInSec = 1000;
151+
struct timespec sleepInterval;
152+
sleepInterval.tv_sec = (time_t) (waitMillisec/msInSec);
153+
sleepInterval.tv_nsec = (long) (waitMillisec%msInSec)*1000000L;
154+
nanosleep (&sleepInterval, NULL);
155+
}
156+
149157
void start () {
150158
if (TACHO_ENABLED) {
151159
setupTacho();
@@ -167,11 +175,12 @@ static void signalHandler (int _) {
167175
void cleanup () {
168176
// PWM pin cleanup
169177
setFanSpeed(PWM_PIN, RPM_OFF);
170-
pinMode(PWM_PIN, origPwmPinMode);
171-
//pullUpDnControl(PWM_PIN, PUD_DOWN);
178+
gpioSetMode(PWM_PIN, origPwmPinMode);
179+
//gpioSetPullUpDown(PWM_PIN, PUD_DOWN);
172180
// TACHO pin cleanup
173-
if (TACHO_ENABLED) pinMode(TACHO_PIN, origTachoPinMode);
174-
//pullUpDnControl(TACHO_PIN, PUD_DOWN);
181+
if (TACHO_ENABLED) gpioSetMode(TACHO_PIN, origTachoPinMode);
182+
//gpioSetPullUpDown(TACHO_PIN, PUD_DOWN);
183+
gpioTerminate();
175184
sd_journal_print(LOG_INFO, "Cleaned up - Exiting ...");
176185
return;
177186
}
@@ -180,7 +189,7 @@ int main (void)
180189
{
181190
signal(SIGINT, signalHandler);
182191
initFanControl();
183-
initWiringPi();
192+
if (initPigpio() < 0) return 1;
184193
setupPwm();
185194
sd_journal_print(LOG_INFO, "Initialized and running ...");
186195
start();

0 commit comments

Comments
 (0)