まこと の ブログ

MaKoTo no burogu — Journal de bord…

Aller au contenu | Aller au menu | Aller à la recherche

Piloter un ventilateur pour Odroid-C2

Le µ-ordinateur Odroid-C2 vient avec un bon dissipateur thermique, mais sans ventilateur.
Il a tendance à fleurter avec les 50°C, surtout par temps caniculaire et d'autant plus s'il est disposé dans un boîtier, ce qui m'a amené à vouloir lui adjoindre un ventilo.

Les vendeurs de boîtiers (le design de l'Odoid-C2 est compatible avec les boîtiers Raspberry-pi 2 et 3) avec ventilateur ont tendance à y mettre un ventilo de 30 mm assez faiblard, quand ce n'est pas simplement un ventilo 12V qui sera donc alimenté en 5V par l'appareil, réduisant sa vitesse de rotation.
L'avantage indéniable c'est qu'il ne fait alors pas trop de bruit.

  • En choisissant comme je l'ai fait un ventilateur 5V, je ne m'attendais pour autant pas à autant d'efficacité pour autant de bruit très gênant.

Nous allons donc voir comment réguler ça.

Au démarrage de l'appareil, avec 26°C de température ambiante, nous avons 36°C pour le CPU ''ARMv'8'. Nous garderons cette température en référence à atteindre.

Au bout de quelques minutes sans rien demander à la machine nous sommes déjà à 42°C. La faire travailler montera encore indéniablement la température.

L'électronique :

Un simple transistor sera utilisé pour interfacer le ventilateur à une pin GPIO de l'appareil.
Elle sera en charge de piloter le transistor avec une PWM logicielle. (oui, impossible de faire fonctionner la PWM matérielle, nous y reviendrons plus loin).

Notez qu'en principe il aurait fallu ajouter une diode de roue-libre aux bornes du ventilateur.

  • Nous avons donc un simple ventilateur à deux broches, et un transistor costaud bien que sur-dimensionné IRLZ44N (un IRL540N fonctionne aussi). J'ai fait avec ce que j'avais sous la main.

C'est ici un transistor CMOS, mais ça fonctionnera aussi bien avec un classique NPN, cependant il faut vérifier sur la datasheet la valeur max du courant iD (Drain Current) ou iC (Collector Current) qu'il est capable d'encaisser.
Un classique BC547 qui encaisse 100 mA serait insuffisant car le ventilateur consomme 120 mA.
Je m'étais alors orienté vers un BS170 qui prend allégrement 500 mA, sauf que !
Il est utile de rappeler que la logique GPIO fonctionne en 0-3,3V, et qu'il faut alors que le transistor soit capable de saturer complètement à 3,3V.
Nous pouvons consulter la valeur de VGS(th) (Gate threshold voltage), et pour le BS170 c'est entre 0,8V et 3,0V, et c'est donc bien au delà de cette dernière valeur qu'il va conduire pleinement.
Expérimentation à l'appui, ce n'est en effet pas suffisant, certes ça déclenche, mais le ventilo ne tourne pas à fond.
Alors qu'avec l'IRLZ44 qui à son VGS(th) à 2V, aucun problème, ça ventile à pleine vitesse.


La PWM sur GPIO :

Je ne vais pas expliquer ici ce que c'est, mais comment en générer une, en 2025 sur un Odroid-C2, matériel qui n'est plus supporté par le fabriquant Hardkernel.

  • En effet plus aucun système d'exploitation n'est disponible officiellement, et ce n'est que grâce à la communauté DietPi qu'il est possible d'avoir un OS à jour avec des possibilités très intéressantes permettant de faire revivre ce µ-ordinateur… Formidable !

Oubliez tout ce que vous aviez appris à l'époque par contre, j'y ai passé 3h pour comprendre que les librairies permettant de piloter les GPIO sur un OS récent, telles que WiringPi ou RPi.GPIO sont dépréciées, cassées et inutilisables. Ça m'apprendra à ne plus retourner en premier lieu sur mes notes et à interroger l'état de l'art à l'instant présent.

  • La solution que j'ai pour l'heure adoptée est l'utilisation de la librairie libgpiod, qui donne entière satisfaction en langage C. Cependant je n'ai pas réussi à faire fonctionner son pendant en Python gpiod.

Mais elle ne permet malheureusement pas d'exploiter les deux broches PWM matérielles embarquées dans l'Odroid-C2, tant pis, nous allons en créer une logicielle.

  • Pour l'installation de la librairie, c'est très facile :
sudo apt install libgpiod-dev g++ gcc

Ensuite j'ai demandé à un outil moderne, Le Chat de me générer le code via ce prompt :

un code en C qui fait varier la vitesse de la pwm en fonction de la température du CPU

Un code qui fonctionne (presque) directement, et sans erreur ! Incroyable… (ci-après)
(Par contre oubliez ChatGPT, son code ne fonctionne jamais… on préférera des outils plus spécialisés en la matière qu'une LLM généraliste.)

  • Il faut donc tout de même spécifier la broche sur laquelle est connectée notre transistor, et aussi sur quel contrôleur « gpiochip ».
#define GPIO_CHIP "gpiochip???"
#define GPIO_PIN ???

On trouve des explications très utiles et claires sur le blog de Christophe Blaess, à propos de la manière de déterminer ces deux éléments cruciaux.

  • En effet la commande :
sudo gpioinfo
  • Nous retourne un tas d'information, dont :
[…]
gpiochip1 - 119 lines:
[…]
	line  98: "J2 Header Pin33" unused output active-high 
[…]

Que nous pouvons corréler à l'implantation du connecteur J2 de l'Odroid-C2 :
C'est donc le chip 1 qui adresse le connecteur J2 qu'on doit utiliser, et le nombre 98 qu'on doit indiquer, correspondant à la pin33.

Notez bien que j'utilise la pin33, qui correspond à la PWM matérielle, qui ne sera pas usitée en tant que telle (cf. plus haut), et que j'aurais pu utiliser une autre pin, puisque la PWM est générée logiciellement, par le code C ci-après.




  • On peut maintenant créer le fichier ventilateur.c qui contiendra le programme :
sudo nano /root/ventilateur.c
  • Et y copier le code C suivant :
#include <stdio.h>
#include <stdlib.h>
#include <gpiod.h>
#include <unistd.h>
#include <time.h>

#define TEMPERATURE_FILE "/sys/class/thermal/thermal_zone0/temp"
#define GPIO_CHIP "gpiochip1"
#define GPIO_PIN 98

void simulate_pwm(struct gpiod_chip *chip, int pin, int duty_cycle) {
    struct gpiod_line *line;
    struct timespec sleep_time_on, sleep_time_off;
    int ret;

    line = gpiod_chip_get_line(chip, pin);
    if (!line) {
        perror("Récupération de la ligne GPIO échouée");
        return;
    }

    ret = gpiod_line_request_output(line, "pwm_sim", 0);
    if (ret < 0) {
        perror("Configuration de la ligne GPIO en sortie échouée");
        gpiod_line_release(line);
        return;
    }

    sleep_time_on.tv_sec = 0;
    sleep_time_on.tv_nsec = (long)(duty_cycle * 1000000L);
    sleep_time_off.tv_sec = 0;
    sleep_time_off.tv_nsec = (long)((100 - duty_cycle) * 1000000L);

    for (int i = 0; i < 100; i++) {
        gpiod_line_set_value(line, 1);
        nanosleep(&sleep_time_on, NULL);
        gpiod_line_set_value(line, 0);
        nanosleep(&sleep_time_off, NULL);
    }

    gpiod_line_release(line);
}

int read_cpu_temperature() {
    FILE *temperature_file;
    int temperature;

    temperature_file = fopen(TEMPERATURE_FILE, "r");
    if (temperature_file == NULL) {
        perror("Erreur lors de l'ouverture du fichier de température");
        return -1;
    }

    if (fscanf(temperature_file, "%d", &temperature) != 1) {
        perror("Erreur lors de la lecture de la température");
        fclose(temperature_file);
        return -1;
    }

    fclose(temperature_file);
    return temperature;
}

int main(void) {
    struct gpiod_chip *chip;
    int temperature, duty_cycle;

    chip = gpiod_chip_open_by_name(GPIO_CHIP);
    if (!chip) {
        perror("Ouverture de la puce GPIO échouée");
        return EXIT_FAILURE;
    }

    while (1) {
        temperature = read_cpu_temperature();
        if (temperature == -1) {
            gpiod_chip_close(chip);
            return EXIT_FAILURE;
        }

        // Convertir la température en degrés Celsius
        float temperature_c = temperature / 1000.0;
        printf("Température du CPU : %.2f °C\n", temperature_c);

        // Calculer le cycle de travail en fonction de la température
        // Par exemple, augmenter le cycle de travail de 20% à 100% en fonction de la température
        duty_cycle = 20 + (int)((temperature_c / 80.0) * 80);
        if (duty_cycle > 100) {
            duty_cycle = 100;
        }

        printf("Cycle de travail PWM : %d%%\n", duty_cycle);
        simulate_pwm(chip, GPIO_PIN, duty_cycle);
    }

    gpiod_chip_close(chip);
    return EXIT_SUCCESS;
}


  • Pour compiler le code on entre cette commande :
sudo gcc -o /root/ventilateur /root/ventilateur.c -lgpiod
  • Et pour l'exécuter :
sudo /root/ventilateur
  • Ce qui au fil du temps renvoie ceci au terminal :
Température du CPU : 43.00 °C
Cycle de travail PWM : 63%
Température du CPU : 42.00 °C
Cycle de travail PWM : 62%
Température du CPU : 41.00 °C
Cycle de travail PWM : 61%
Température du CPU : 40.00 °C
Cycle de travail PWM : 60%
Température du CPU : 39.00 °C
Cycle de travail PWM : 59%
Température du CPU : 38.00 °C
Cycle de travail PWM : 58%
Température du CPU : 38.00 °C
Cycle de travail PWM : 58%
Température du CPU : 37.00 °C
Cycle de travail PWM : 57%
Température du CPU : 36.00 °C
Cycle de travail PWM : 56%
Température du CPU : 35.00 °C
Cycle de travail PWM : 55%
Température du CPU : 35.00 °C
Cycle de travail PWM : 55%
Température du CPU : 34.00 °C
Cycle de travail PWM : 54%
Température du CPU : 33.00 °C
Cycle de travail PWM : 53%



Automatisation :

Si tout à bien fonctionné à l'étape précédente, maintenant il s'agit d'exécuter le programme à chaque démarrage du DietPi, ce que nous allons faire en créant un service systemd :

  • Écrire le fichier suivant :
sudo nano /etc/systemd/system/ventilateur.service
  • Contenant ceci :
[Unit]
Description=Démarre le programme « ventilateur »

[Service]
ExecStart=/root/ventilateur
Restart=on-failure
User=root

[Install]
WantedBy=multi-user.target
  • Puis exécuter ces commandes pour respectivement recharger systemd, installer le service au démarrage, démarrer le service pour la session en cours, consulter l'état du service :
sudo systemctl daemon-reload
sudo systemctl enable ventilateur.service
sudo systemctl start ventilateur.service
sudo service ventilateur status


Voilà, à chaque démarrage du µ-ordinateur, le ventilateur se mettra en route et régulera sa vitesse en fonction de la température du processeur ARM.



Modifications utiles :

Si on souhaite baisser la vitesse du ventilo par rapport à la température, on peut modifier le code comme ceci :

  • Repérer la ligne :
duty_cycle = 20 + (int)((temperature_c / 80.0) * 80);
  • Qui donne ceci :

Température du CPU : 29.00 °C
Cycle de travail PWM : 49%

  • Et modifier les valeurs comme ceci :
duty_cycle = 20 + (int)((temperature_c / 100.0) * 60);
  • Ce qui donne alors :

Température du CPU : 29.00 °C
Cycle de travail PWM : 37%

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

Fil des commentaires de ce billet