Fabriquer un afficheur analogique externe pour PC avec une carte Arduino / Genuino

New old school

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino | Mots clefs : Arduino Genuino Analog Afficheur


Dans ce projet, nous allons fabriquer un afficheur analogique externe pour ordinateur avec une carte Arduino / Genuino et des afficheurs analogiques à aiguille.

Sommaire

Bonjour à toutes et à tous !

Vous avez peut-être déjà croisé sur internet des projets mettant en oeuvre des voltmètres à aiguilles reliés à un ordinateur pour afficher le pourcentage d'utilisation du processeur, de la mémoire, etc.

Personnellement, j'adore ce genre de projet "new old school". C'est donc sans grande surprise que j'ai décidé de réaliser un projet similaire. Prenez place, le spectacle commence ;)

Les afficheurs à aiguilles

Afficheurs analogiques à aiguilles

Les afficheurs

Pour ce projet, j'ai décidé d'utiliser deux types d'afficheurs à aiguilles différents : des carrés "classiques" et des ronds, plus "old school". Ces deux types d'afficheurs sont disponibles sur eBay pour quelques euros pièce sous les références 65C5 et 85C1, ou plus simplement en cherchant "5 volts analog meter".

Les modèles que j'ai utilisés sont des voltmètres 5 volts. Il n'y a pas grand-chose à dire à propos de ces afficheurs. Ce sont des afficheurs mécaniques à aiguille, avec une coque en plastique autour.

Préparation des afficheurs

Avant de pouvoir réaliser le montage, il est nécessaire de préparer les afficheurs. Cela se passe en deux étapes : le retrait de la résistance interne et l'ajout d'une nouvelle face avant.

Gros plan sur la résistance interne de l'afficheur

La résistance interne

Gros plan sur la résistance interne de l'afficheur.

La résistance interne

À l'intérieur des afficheurs se trouve une résistance en série avec la partie électromécanique de l'afficheur. Cette résistance permet de convertir la tension à mesurer en courant. En réalité ces afficheurs ne sont pas des voltmètres "5 volts" mais bien des ampèremètres 1 mA avec une résistance de 5K ohms en série (rappel : U = R * I, soit I = U / R, or 5 volts / 5000 ohms = 1 mA).

Le problème c'est que la résistance de 5K ohms a une valeur un poil trop grande pour notre application. Il faut donc la retirer pour la remplacer par une valeur plus faible qui permet d'avoir un peu plus de marge pour régler la limite haute de l'affichage.

Pourquoi ne pas utiliser directement des ampèremètres 1mA ?

Parce que je suis un idiot qui a commandé 12 voltmètres 5 volts au lieu de 12 ampèremètres 1mA.

Mais c'était tout à fait volontaire (ou pas), car de cette façon, je pouvais documenter la modification. En utilisant directement des ampèremètres 1mA, il ne devrait pas y avoir besoin de faire la modification ci-après (en théorie, cela reste à vérifier).

Gros plan sur le câblage de l'afficheur

Gros plan sur le câblage après modification

Gros plan sur le câblage de l'afficheur

Gros plan sur le câblage après modification

Retirer la résistance interne demande un simple petit coup de fer à souder, rien de bien terrible.

PS Pour ouvrir les afficheurs, il suffit de dévisser les vis sur les côtés ou à l'arrière en fonction du modèle. Soyez prudent, le plastique n'est pas de très bonne qualité.

Seconde étape de la préparation des afficheurs : l'ajout d'une nouvelle face avant.

Capture d'écran du logiciel Silhouette Studio

Capture d'écran du logiciel Silhouette Studio

Réaliser les faces avant aux bonnes dimensions dans un logiciel de dessin technique m'a pris une éternité. Entre l'alignement et la taille d'impression, j'ai du faire une bonne trentaine de révisions avant d'obtenir un résultat convenable.

PS J'ai utilisé FreeCAD pour les contours de découpe et Inkscape pour les éléments graphiques.

Mais ces difficultés n'étaient rien à comparer de l'importation du fichier fini dans le logiciel de découpe de ma découpeuse de vinyle. Au final, j'ai même dû acheter une licence Silhouette Studio Designer à 50€ (cela fait bien cher le logiciel d'importation de fichiers SVG si vous voulez mon avis) pour pouvoir enfin imprimer les faces avants des mes afficheurs avec avec les marqueurs d'alignement spéciaux et découper le tout directement à partir du fichier vectoriel.

La découpeuse Silhouette Portrait

La découpeuse et le résultat de la découpe

Heureusement la découpe s'est passée sans soucis. Ma découpeuse de vinyle (une Silhouette Portrait) a fait son travail et a même réussi l'alignement de la tête de découpe du premier coup. Les utilisateurs de Portrait crieront au miracle (normalement, il faut 5 ou 6 essais avant d'obtenir la confirmation d'alignement dans le logiciel).

PS Disont les choses telles qu'elles sont, la Silhouette Portrait et Silhouette Studio, c'est de la camelote. Mais c'est de la camelote à 200€ (150€ la machine et 50€ le logiciel). Une découpeuse vinyle Roland (le haut de gamme du marché) d'occasion coute pas loin de 1000€. Question de budget donc.

Faces avant personnalisées pour voltmètres analogiques

Faces avant personnalisées

Faces avant personnalisées pour voltmètres analogiques

Faces avant personnalisées

Pour ce projet, j'ai décidé de faire quatre étiquettes, toutes graduées en pourcentage :

  • une pour le pourcentage d'utilisation processeur,

  • une pour le pourcentage d'utilisation de la mémoire vive,

  • une pour le pourcentage d'utilisation des disques durs,

  • et une pour le pourcentage d'utilisation du réseau.

Voltmètres analogique modifiés et personnalisés

Le résultat final après remontage

Si vous voulez faire vos propres étiquettes, je vous ai préparé une archive avec les patrons en format SVG, ainsi que le fichier Silhouette Studio qui va bien : Etiquettes_voltemetres.zip

Le montage

Maintenant que les afficheurs sont prêts, il est temps de passer à l'électronique.

Photographie du matériel nécessaire à la réalisation du montage d'affichage analogique externe pour ordinateur

Matériel nécessaire

Pour réaliser ce montage, il va nous falloir :

  • Une carte Arduino UNO (et son câble USB),

  • Quatre afficheurs analogiques à aiguilles de type ampèremètre 1mA ou voltmètre 5 volts (voir chapitre précédent),

  • Quatre résistances de 4.7K ohms – code couleur jaune violet rouge,

  • Quatre potentiomètres multitours de 1K ohms,

  • Une plaque d'essai et des fils pour câbler notre montage.

Vue prototypage du montage d'affichage analogique externe pour ordinateur

Vue prototypage du montage

Illustration du câblage du montage d'affichage analogique externe pour ordinateur

Illustration du câblage

Le principe du montage est assez simple : placer une résistance de valeur variable en série avec chaque afficheur.

Pour ce faire, on commence par câbler les potentiomètres à une broche PWM de la carte Arduino. J'utilise les broches D3, D9, D10 et D11 pour ce projet. On relie ensuite le curseur de chaque potentiomètre à une extrémité d'une résistance de 4.7K ohms.

Gros plan sur l’électronique de commande du montage

L’électronique de commande

On termine ensuite le circuit en reliant l'autre extrémité de chaque résistance à la borne positive de l'afficheur puis à la masse GND de la carte Arduino.

Photographie du montage d'affichage analogique externe pour ordinateur

Le montage fini

PS Pour certains afficheurs, j'ai été obligé d'utiliser des résistances de 4.3K ohms au lieu de 4.7K ohms. 4.7K ohms est vraiment juste limite pour le réglage. Ça ne se joue qu'à quelques centaines d'ohms près.

Le code

Le code de ce projet est en deux parties : le code Arduino pour l'affichage et un code Python 3 pour la récupération et l'envoi des informations à afficher.

Le code Arduino

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* Taille du buffer de réception série */
const byte BUFFER_SIZE = 64;

/* Nroches analogiques (PWM) */
const byte ANALOG_A_PIN = 3;
const byte ANALOG_B_PIN = 9;
const byte ANALOG_C_PIN = 10;
const byte ANALOG_D_PIN = 11;

/* Buffer de réception série  */
static char buffer[BUFFER_SIZE] = { 0 };

Le code de la partie Arduino commence comme d'habitude avec une série de constantes.

Sont déclarés dans le code : la taille du buffer de réception pour le port série, le buffer en question, ainsi que les quatre broches PWM pour le contrôle des afficheurs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void setup() {
 
 /* Initialise le port série */
 Serial.begin(115200); 
 Serial.setTimeout(2000); // Timeout de réception série

  /* Valeurs par défaut */
  analogWrite(ANALOG_A_PIN, 0);
  analogWrite(ANALOG_B_PIN, 0);
  analogWrite(ANALOG_C_PIN, 0);
  analogWrite(ANALOG_D_PIN, 0);
}

Vient ensuite la fonction setup() qui initialise le port série à une vitesse standard de 115200 bauds et configure le timeout de réception série à une valeur de 2 secondes. Ce timeout permet de ne pas bloquer le programme si on arrête la communication série du côté de l'ordinateur.

N.B. Il faudra augmenter la valeur de ce timeout si vous diminuez la vitesse de rafraichissement de l'affichage dans le code Python du chapitre suivant.

La fonction setup() s'occupe aussi de remettre à zéro les afficheurs au démarrage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void loop() {
 
  /* Lit une ligne de texte */
  int read = Serial.readBytesUntil('\n', buffer, BUFFER_SIZE - 1);
  if (read) {
    
    /* Cloture la chaine de caractéres */
    buffer[read] = '\0';
    
    /* Extrait les valeurs A, B, C et D */
    int a, b, c, d;
    if (sscanf(buffer,"%d %d %d %d", &a, &b, &c, &d) == 4) {
      
      /* Rafraichi l'affichage */
      analogWrite(ANALOG_A_PIN, a & 0xFF);
      analogWrite(ANALOG_B_PIN, b & 0xFF);
      analogWrite(ANALOG_C_PIN, c & 0xFF);
      analogWrite(ANALOG_D_PIN, d & 0xFF);
    }
  }
}

Pour finir, la fonction loop() lit une ligne de texte de taille maximum fixe, se terminant par un retour chariot (format de lignes Linux).

La fonction clôture la chaine de caractères reçue, puis utilise la fonction sscanf pour extraire quatre valeurs numériques qui seront utilisées pour rafraichir l'affichage au moyen de la fonction Arduino analogWrite().

Le code complet avec commentaires :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
 * Afficheur analogique externe pour ordinateur.
 */

/* Taille du buffer de réception série */
const byte BUFFER_SIZE = 64;

/* Nroches analogiques (PWM) */
const byte ANALOG_A_PIN = 3;
const byte ANALOG_B_PIN = 9;
const byte ANALOG_C_PIN = 10;
const byte ANALOG_D_PIN = 11;

/* Buffer de réception série  */
static char buffer[BUFFER_SIZE] = { 0 };


/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200); 
  Serial.setTimeout(2000); // Timeout de réception série

  /* Valeurs par défaut */
  analogWrite(ANALOG_A_PIN, 0);
  analogWrite(ANALOG_B_PIN, 0);
  analogWrite(ANALOG_C_PIN, 0);
  analogWrite(ANALOG_D_PIN, 0);
}

/** Fonction loop() */
void loop() {
 
  /* Lit une ligne de texte */
  int read = Serial.readBytesUntil('\n', buffer, BUFFER_SIZE - 1);
  if (read) {
    
    /* Cloture la chaine de caractéres */
    buffer[read] = '\0';
    
    /* Extrait les valeurs A, B, C et D */
    int a, b, c, d;
    if (sscanf(buffer,"%d %d %d %d", &a, &b, &c, &d) == 4) {
      
      /* Rafraichi l'affichage */
      analogWrite(ANALOG_A_PIN, a & 0xFF);
      analogWrite(ANALOG_B_PIN, b & 0xFF);
      analogWrite(ANALOG_C_PIN, c & 0xFF);
      analogWrite(ANALOG_D_PIN, d & 0xFF);
    }
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Le code PC

Je ne rentrerai pas dans les détails du code PC. Pour résumer, j'utilise la bibliothèque psutil pour récupérer les informations systèmes tels que le pourcentage d'utilisation du processeur et la bibliothèque pyserial pour transmettre ces informations via le port série.

N.B. Pour utiliser ce script Python, il faudra installer l'interpréteur Python 3. Il faudra aussi installer ces deux bibliothèques additionnelles avec la commande pip install psutil pyserial.

Le script se comporte comme un programme en ligne de commande. Pour l'utiliser, il est nécessaire de préciser au minimum le nom du port série. Voici quelques exemples d'utilisation :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Strict minimum
serial_stats.py COM3

# Avec une vitesse de communication différente (9600 bauds)
serial_stats.py COM3 9600

# Avec une vitesse de rafraichissement plus lente (5 secondes)
serial_stats.py COM3 115200 5

# Avec un maximum de 125Mo/s pour les disques et 1Mo/s pour le réseau (ADSL powaaa)
serial_stats.py COM3 115200 1 131072000 1048576

Le code complet avec commentaires :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
"""
Collecteur d'informations système pour un affichage déporté via le port série.
"""

import time
import argparse

import psutil
import serial


# Données de calibration par défaut
MAX_DISK_BANDWIDTH_BYTES = 1024 * 1024 * 550  # 550 Mo/s
MAX_NETWORK_BANDWIDTH_BYTES = 1024 * 1024 * 125  # 125 Mo/s


def monitor_resources(serial_port, serial_speed, refresh_speed,
                      max_disk_bandwidth_bytes, max_network_bandwidth_bytes):
    """
    Surveille l'état des ressources système et transmet les statistiques à un afficheur externe via le port série.
    :param serial_port: Le nom du port série à utiliser.
    :param serial_speed: La vitesse de communication à utiliser.
    :param refresh_speed: La vitesse de rafraichissement de l'afficheur externe.
    :param max_disk_bandwidth_bytes: Le débit maximum des disques en octets/s.
    :param max_network_bandwidth_bytes: Le débit maximum du réseau en octets/s.
    """

    # Ajuste les débits en fonction de la fenêtre de temps
    max_disk_bandwidth_bytes *= refresh_speed
    max_network_bandwidth_bytes *= refresh_speed

    # Démarrage à froid
    psutil.cpu_percent()
    psutil.virtual_memory()
    disk_io = psutil.disk_io_counters()
    previous_disk_io_bytes = disk_io.read_bytes + disk_io.write_bytes
    network_io = psutil.net_io_counters()
    previous_network_io_bytes = network_io.bytes_sent + network_io.bytes_recv

    # Ouvre le port série
    with serial.Serial(serial_port, serial_speed, timeout=1) as ser:
            
        # Tant que le script n'est pas tué (avec CTRL+C)
        while True:

            # Récupére l'utilisation du processeur en pourcentage
            cpu_percent = psutil.cpu_percent()
            
            # Récupére l'utilisation de la mémoire vive en pourcentage
            memory_percent = psutil.virtual_memory().percent
            
            # Récupére l'utilisation des disques en pourcentage
            disk_io = psutil.disk_io_counters()
            disk_io_bytes = disk_io.read_bytes + disk_io.write_bytes
            disk_percent = (disk_io_bytes - previous_disk_io_bytes) / max_disk_bandwidth_bytes * 100
            previous_disk_io_bytes = disk_io_bytes
            
            # Récupére l'utilisation du réseau en pourcentage
            network_io = psutil.net_io_counters()
            network_io_bytes = network_io.bytes_sent + network_io.bytes_recv
            network_percent = (network_io_bytes - previous_network_io_bytes) / max_network_bandwidth_bytes * 100
            previous_network_io_bytes = network_io_bytes

            # Affiche les stats (pour debug)
            print('cpu={:.1f}, memory={:.1f}, disk={:.1f}, network={:.1f}'.format(cpu_percent, memory_percent,
                                                                                  disk_percent, network_percent))

            # Envoi les stats via le port série à l'afficheur externe
            ser.write('{3} {2} {1} {0}\n'.format(int(cpu_percent / 100 * 255),
                                                 int(memory_percent / 100 * 255),
                                                 int(network_percent / 100 * 255),
                                                 int(disk_percent / 100 * 255)).encode())

            # Attends la prochaine fenêtre de rafraichissement
            time.sleep(refresh_speed)


# Programme principal
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Monitor system resources and send them to an external display over a serial port.')
    parser.add_argument('serial_port', metavar='<SERIAL PORT>',
                        help='The serial port name/path to be used for communication.')
    parser.add_argument('serial_speed', metavar='<SERIAL SPEED>', type=int, nargs='?', default=115200,
                        help='The serial port speed to be used for communication (default 115200 bauds).')
    parser.add_argument('refresh_speed', metavar='<REFRESH SPEED>', type=int, nargs='?', default=1,
                        help='The number of seconds between each refresh (default 1 second).')
    parser.add_argument('max_disk_bandwidth_bytes', metavar='<MAX DISK BANDWIDTH>', type=int, nargs='?',
                        default=MAX_DISK_BANDWIDTH_BYTES,
                        help='The maximum bandwith of the disk in bytes/s (default 550MB/s).')
    parser.add_argument('max_network_bandwidth_bytes', metavar='<MAX_NETWORK BANDWIDTH>', type=int, nargs='?',
                        default=MAX_NETWORK_BANDWIDTH_BYTES,
                        help='The maximum bandwith of the network in bytes/s (default 125MB/s).')
    args = parser.parse_args()
    monitor_resources(args.serial_port, args.serial_speed, args.refresh_speed,
                      args.max_disk_bandwidth_bytes, args.max_network_bandwidth_bytes)

L'extrait de code ci-dessus est disponible en téléchargement sur cette page.

Bonus : Calibration des afficheurs

Sur chaque afficheur se trouve une vis de réglage pour le zéro, en tournant cette vis on peut ajuster la limite basse de l'afficheur. En ajustant le potentiomètre de 1K ohms, on ajuste la limite haute de l'afficheur. Cela permet de calibrer la plage de variation de l'aiguille.

Pour calibrer les afficheurs, il convient d'ouvrir le moniteur série du logiciel Arduino, de le configurer en 115200 bauds avec retour de ligne "nouvelle ligne".

  • En saisissant 0 0 0 0 dans la barre de commande, les afficheurs reviendront à zéro. Il est alors possible d'ajuster le zéro avec la visse de réglage sur l'avant de l'afficheur.

  • En saisissant 255 255 255 255 dans la barre de commande, les afficheurs iront tous à 100%. Il est alors possible d'ajuster la limite haute des afficheurs en ajustant les potentiomètres.

N.B. L'opération est à répéter de temps en temps, ces afficheurs à aiguilles chinois ne sont pas très stables dans le temps.

Conclusion

Ce projet est désormais terminé.

Si ce projet vous a plu, n'hésitez pas à le commenter sur le forum, à le partager sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.