A fan control script written in bash for the 2-pin PH2.0 12v fan connector of the NanoPi M4 SATA hat. By default, the script uses a bounded logistic model with a moving mid-point (based on the average temperature over time) to set the fan speed.
Many of the variables used in this fan controller can be modified directly from the CLI, such as setting custom temperature thresholds (-t
, -T
) or disabling temperature monitoring altogether (-f
). For a more detailed description, see Usage.
There's arguably more code here than necessary to run a fan controller. This was a hobby of mine (I wanted to revisit the first version which used a fixed table to set the speed) and an opportunity to learn more about bash and the sysfs interface. There are multiple comments in the script as well, which makes it easy to edit for other similar cases.
This is free. There is NO WARRANTY. Use at your own risk.
If you have any issues or suggestions, open an issue or send me an e-mail.
- Linux distro;
- Access to the pwm sysfs interface (run with
sudo
permission or asroot
); - GNU bash (recommend v5.x);
- GNU basic calculator;
- Standard GNU/Linux commands.
Besides bash, you don't need to check for any of these requisites manually. The script will automatically check for everything it needs to run and will let you know if there's any errors or missing access to important commands.
The controller was developed with Armbian OS but you should be able to run it on any other Linux distro for the NanoPi M4. For reference, this script was originally developed with the following hardware:
- NanoPi-M4 v2
- M4 SATA hat
- 12V (.08A-.2A) generic fan (do not try to use more than 200mA)
And software:
- Kernel: Linux 4.4.213-rk3399
- OS: Armbian Buster (21.08.8) stable
- GNU bash v5.0.3
- bc v1.07.1
To install the fan controller script, run the following commands either as root
or append sudo
to each command with a user that has sudo permission:
-
Install
git
andbc
(GNU Basic Calculator).apt update && apt install git bc
-
Create a new directory in
/opt
for the default branch (master
) of thenanopim4-satahat-fan
repository.cd /opt git clone https://github.com/cgomesu/nanopim4-satahat-fan.git cd nanopim4-satahat-fan/
-
Test the script.
./pwm-fan.sh -F 10
And if you run into any error messages, fix the issue and try again. Otherwise, press
Ctrl
+c
to send an interrupt signal and stop the script. -
Optional. Check Usage for non-default options that you might want to test before running the script in the background.
-
Optional. If using thermal controllers, take a look at Controllers to learn how to tune a few parameters to best fit your environment.
-
Lastly, see the Implementation section for information on how to run the script in the background.
./pwm-fan.sh -h
Usage:
./pwm-fan.sh [OPTIONS]
Options:
-c str Name of the PWM CHANNEL (e.g., pwm0, pwm1). Default: pwm0
-C str Name of the PWM CONTROLLER (e.g., pwmchip0, pwmchip1). Default: pwmchip1
-d int Lowest DUTY CYCLE threshold (in percentage of the period). Default: 25
-D int Highest DUTY CYCLE threshold (in percentage of the period). Default: 100
-f Fan runs at FULL SPEED all the time. If omitted (default), speed depends on temperature.
-F int TIME (in seconds) to run the fan at full speed during STARTUP. Default: 60
-h Show this HELP message.
-l int TIME (in seconds) to LOOP thermal reads. Lower means higher resolution but uses ever more resources. Default: 10
-m str Name of the DEVICE to MONITOR the temperature in the thermal sysfs interface. Default: soc
-o str Name of the THERMAL CONTROLLER. Options: logistic (default), pid.
-p int The fan PERIOD (in nanoseconds). Default (25kHz): 25000000.
-s int The MAX SIZE of the TEMPERATURE ARRAY. Interval between data points is set by -l. Default (store last 1min data): 6.
-t int Lowest TEMPERATURE threshold (in Celsius). Lower temps set the fan speed to min. Default: 25
-T int Highest TEMPERATURE threshold (in Celsius). Higher temps set the fan speed to max. Default: 75
-u int Fan-off TEMPERATURE threshold (in Celsius). Shuts off fan under the specified temperature. Default: 0
-U int Fan-on TEMPERATURE threshold (in Celsius). Turn on fan control above the specified temperature. Default: 1
If no options are provided, the script will run with default values.
Defaults have been tested and optimized for the following hardware:
- NanoPi-M4 v2
- M4 SATA hat
- Fan 12V (.08A and .2A)
And software:
- Kernel: Linux 4.4.213-rk3399
- OS: Armbian Buster (21.08.8) stable
- GNU bash v5.0.3
- bc v1.07.1
Author: cgomesu
Repo: https://github.com/cgomesu/nanopim4-satahat-fan
This is free. There is NO WARRANTY. Use at your own risk.
-
Run with a custom period and min/max temperature thresholds.
./pwm-fan.sh -p 25000000 -t 30 -T 60
-
Run with defaults, except that the minimum duty cycle threshold is 40%.
./pwm-fan.sh -d 40
-
Run in full speed mode all the time.
./pwm-fan.sh -f
-
Set fan startup to 15 sec.
./pwm-fan.sh -F 15
-
When using args
-u
and-U
(introduced by @araynard via #7), it is recommended to leave a difference of at least 5°C between them. In most cases,-u
can be set to a value slightly higher than the idle temperature with the fan, whereas-U
can be set to a value slightly higher than the idle temperature without the fan../pwm-fan.sh -u 45 -U 55
The default thermal controller is based on a logistic model that outputs the duty cycle in nanoseconds, owing to the constraint that L = upper duty cycle threshold, which can be specified via the argument -D
in percentage of the period.
The following plot illustrates how the logistic controller changes the duty cycle as a function of the current temperature using default parameter values and three different mean temperatures:
The parameters k (k = a/b), as well as the critical temperature (75°C) used as reference for the moving mid-point, can both be modified by editing the script. The following plot illustrates the effects of changing k while holding the mean temperature constant (everything else follows default):
Similarly, the following plot illustrates the effects of changing the critical temperature while holding the mean temperature constant (everything else follows default):
PID stands for proportional, integral, and derivative controller. In brief, the P component of a PID controller has to do with current values; the I component has to do with past values; and the D component has to do with future values.
The controller has one coefficient for each of the three components, namely Kp, Ki, and Kd, and currently, their default values are given by the variables DEFAULT_Kp
, DEFAULT_Ki
, and DEFAULT_Kd
, respectively. If using this thermal controller (see Usage), you might want to tune such default values by editing the script before runnig it. The default coefficients are loosely ordered as follows: Kd > Kp >> Ki. That is, sudden changes in temperature drive most of the changes in the fan duty cycle, followed by the difference between current and ideal temperature, and lastly, by the accumulation of the later over time.
As mentioned before, the proportional gain--and consequently, the integral gain--is closely related to an arbitrary ideal temperature. By default, the this ideal temperature is assigned after the fan startup via the variable THERMAL_INITIAL
by measuring the current temperature and adding 2°C to it. You can override that by uncommenting the PID_THERMAL_IDEAL
variable at the top of the script and then assigning a value to it (in Celsius).
If you're running options different than the default ones, first edit the systemd/pwm-fan.service
file to include those options into the ExecStart=
command execution. Then, run the following commands to enable and start the pwm-fan.service
:
- Enable the service and start it.
systemctl enable /opt/nanopim4-satahat-fan/systemd/pwm-fan.service # alternatively, copy the service file to '/lib/systemd/system/' and enable it via 'systemctl enable pwm-fan.service'. systemctl start pwm-fan.service
- Then, check the service status to make sure it's running without issues.
systemctl status pwm-fan.service