💾 Archived View for home.gegeweb.org › rpi › pi_fan_hwpwm.gmi captured on 2023-05-24 at 17:47:33. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-11-30)

-=-=-=-=-=-=-

ContrĂ´leur PWM pour le ventilateur du boitier Raspberry Pi4

Ce programme [1] pilote le ventilateur du boîtier Raspberry Pi 4 [2] avec une modulation matérielle à largeur d'impulsion (PWM [3]).

Si vous utilisez le boitier officiel du Raspberry Pi 4 [4], le ventilateur officiel pour ce boitier est une bonne solution de refroidissement.

Cependant… dans ce cas le ventilateur fonctionne en permanence (et n'est pas silencieux !) sauf à utiliser l'option de microprogramme suivante dans /boot/config.txt, le ventilateur connecté comme préconisé sur la broche GPIO 14 (fil bleu).

dtoverlay=gpio-fan,gpiopin=14,temp=80000

Avec l'option ci-dessus, le ventilateur se met en route lorsque la température du processeur atteint 80°C.

Avec ce programme, le ventilateur fonctionne donc plus silencieusement et l'unité centrale connaît des cycles thermiques plus doux qu'avec l'option de microprogramme.

Ce programme consomme mois de 0,1 % d'un cœur en fonctionnement, contre 12 % pour le logiciel PWM. Seules deux broches GPIO exposées sont capables de PWM matériel (GPIO 13 et GPIO 18) et une d'entre elles (GPIO 13) interfère avec le support du ventilateur, donc la broche GPIO 18 est le meilleur choix.

Le fil bleu du ventilateur doit être déplacé de deux emplacements sur la broche GPIO 18.

Le programme fonctionne en tant que root et crée deux fichiers pour la surveillance :

/run/pi_fan_hwpwm.pid 

et

/run/pi_fan_hwpwm.state

Ce dernier fichier contient trois nombres, par exemple "1337, 68.95, 74.3", qui représentent les boucles d'environ une seconde depuis le démarrage, la température du processeur lissée et le niveau de PWM en pourcentage. Le PWM fonctionne à une fréquence de 20 kHz, ce qui est généralement acceptable pour les ventilateurs et n'est pas gênant pour les animaux de compagnie ou les enfants.

Le programme a été testé par l'auteur du logiciel avec un Raspberry pi 4 sous Ubuntu 20.04 64 bits et Raspbian Pi OS Lite 32 bits. Pour ma part je l'utilise sous Raspberry Pi OS (buster) 64 bits, machine utilisée en « desktop ».

Les options pour /boot/config.txt suggérées dans le code source ne sont pas nécessaires.

Installation

Ce programme utilise la librairie C pour Broadcom BCM 2835 [5] utilisée dans les Raspberry Pi, son installation est relativement simple :

1. Téléchargez et installez la librairie Broadcom BCM 2835

cd
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz
tar zxvf bcm2835-1.68.tar.gz
cd bcm2835-1.68
./configure
make
sudo make install

2. Téléchargez et installez le le programme de test et le programme en lui même

cd
sudo apt install -y git stress-ng
git clone https://gist.github.com/1c13096c4cd675f38405702e89e0c536.git
cd 1c13096c4cd675f38405702e89e0c536
make
sudo make install

3. Testez et vérifiez que ça fonctionne

stress-ng -c 4 -t 3m -q & watch -n1 cat /run/pi_fan_hwpwm.state

Sources

Makefile

CC = gcc
RM = rm -f

CFLAGS  = -Wall
LIBS    = -lbcm2835

TARGET = pi_fan_hwpwm

all: $(TARGET)

$(TARGET): $(TARGET).c
	$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LIBS)

install: $(TARGET)
	install $(TARGET) /usr/local/sbin
	cp $(TARGET).service /etc/systemd/system/
	systemctl enable $(TARGET)
	! systemctl is-active --quiet $(TARGET) || systemctl stop $(TARGET)
	systemctl start $(TARGET)

uninstall: clean
	systemctl stop $(TARGET)
	systemctl disable $(TARGET)
	$(RM) /usr/local/sbin/$(TARGET)
	$(RM) /etc/systemd/system/$(TARGET).service
	$(RM) /run/$(TARGET).*
	@echo
	@echo "To remove the source directory"
	@echo "    $ cd && rm -rf ${CURDIR}"
	@echo

clean:
	$(RM) $(TARGET)

pi_fan_hwpwm.c

/*
/
/ pi_fan_hwpwm.c, alwynallan@gmail.com 12/2020, no license
/ latest version: https://gist.github.com/alwynallan/1c13096c4cd675f38405702e89e0c536
/
/ Need    http://www.airspayce.com/mikem/bcm2835/index.html
/
/ Compile $ gcc -Wall pi_fan_hwpwm.c -lbcm2835 -o pi_fan_hwpwm
/
/ 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
/             enable_uart=0                              <- needed? not Ubuntu
/             dtparam=audio=off                          <- needed? not Ubuntu
/             dtparam=i2c_arm=off                        <- needed? not Ubuntu
/             dtparam=spi=off                            <- needed? not Ubuntu
/
/ Run     $ sudo ./pi_fan_hwpwm -v
/
/ Forget  $ sudo ./pi_fan_hwpwm &
/         $ 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 <bcm2835.h>

#define PWM_PIN   0  // default, uses both GPIO 13 and GPIO 18
#define HIGH_TEMP 80.
#define ON_TEMP   65.
#define OFF_TEMP  60.
#define MIN_FAN   150
#define KICK_FAN  200
#define MAX_FAN   480

unsigned pin = PWM_PIN;
int verbose = 0;
int fan_state = 0;
double temp = 25.0;
pid_t global_pid;
int pwm_level = -555;

void usage()
{
   fprintf
   (stderr,
      "\n" \
      "Usage: sudo ./pi_fan_hwpwm [OPTION]...\n" \
      "\n" \
      "  -g <n> Use GPIO n for fan's PWM input, default 0 (both).\n" \
      "         Only hardware PWM capable GPIO 18 and GPIO 13 are present on\n" \
      "         the RasPi 4B pin header, and only GPIO 18 can be used with\n" \
      "         the unmodified case fan.\n" \
      "  -v     Verbose output\n" \
      "\n"
   );
}

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 PWM_out(int level) {
  if(level > pwm_level && (level - pwm_level) < 5) return;
  if(level < pwm_level && (pwm_level - level) < 10) return;
  if(level != pwm_level) {
    if(pin == 0 || pin == 13) bcm2835_pwm_set_data(1, level);
    if(pin == 0 || pin == 18) bcm2835_pwm_set_data(0, level);
    pwm_level = level;
  }
}

void fan_loop(void) {
  if(!fan_state && (temp > ON_TEMP)) {
    PWM_out(KICK_FAN);
    fan_state = 1;
    return;
  }
  if(fan_state && (temp < OFF_TEMP)) {
    PWM_out(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 > MAX_FAN) out = MAX_FAN;
    PWM_out(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 != 0 && pin != 13 && pin != 18) 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_hwpwm.pid", buf);

  if(!bcm2835_init()) fatal(0, "bcm2835_init() failed");
  if(pin==0 || pin==13) bcm2835_gpio_fsel(13, BCM2835_GPIO_FSEL_ALT0);
  if(pin==0 || pin==18) bcm2835_gpio_fsel(18, BCM2835_GPIO_FSEL_ALT5);
  bcm2835_pwm_set_clock(2); // 19.2 / 2 MHz
  if(pin==0 || pin==13) bcm2835_pwm_set_mode(1, 1, 1);
  if(pin==0 || pin==13) bcm2835_pwm_set_range(1, 480);
  if(pin==0 || pin==18) bcm2835_pwm_set_mode(0, 1, 1);
  if(pin==0 || pin==18) bcm2835_pwm_set_range(0, 480);
  PWM_out(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, %.1f\n", loop/4, temp, (float)pwm_level/(float)MAX_FAN*100.);
      run_write("/run/pi_fan_hwpwm.state", buf);
      if(verbose) fputs(buf, stdout);
    }
    usleep(250000);
  }

  exit(EXIT_SUCCESS);
}

pi_fan_hwpwm.service

[Unit]
Description=Hardware PWM control for Raspberry Pi 4 Case Fan
After=syslog.target

[Service]
Type=simple
User=root
WorkingDirectory=/run
PIDFile=/run/pi_fan_hwpwm.pid
ExecStart=/usr/local/sbin/pi_fan_hwpwm
Restart=on-failure

[Install]
WantedBy=multi-user.target

Références

1. Hardware PWM Controller for the Raspberry Pi 4 Case Fan

2. Raspberry Pi 4 Case Fan

3. Modulation de largeur d'impulsion (Wikipedia)

4. Raspberry Pi 4 Case

5. C library for Broadcom BCM 2835 as used in Raspberry Pi

________________________________________________________________________________

Raspberry Pi

Accueil