HUD automobile : vitesse et boussole projetés sur pare-brise
Composants et fournitures
| Composant | Quantité |
|---|---|
| Arduino Nano R3 | 1 |
| 3 afficheurs 7 segments 5161AS (cathode commune, rouge) | 3 |
| GPS série TTL Beitian BN-220 (1 Hz, trames GNRMC) | 1 |
| 3 décodeurs BCD à verrou 14511 (CD4511) | 3 |
| Boutons poussoirs (NO) + condensateurs de découplage | 3 |
| Résistances 220 Ω ¼ W | 22 |
| LED rouge 3 mm | 1 |
| Condensateurs 100 nF | 2 |
| Jumper (strap) pour déconnecter Rx pendant flash | 1 |
| Connecteurs : strip lines, prise USB-B femelle, câble pigtail GPS | - |
| Boîtier imprimé 3D (fichiers STL sur GrabCAD) | 1 |
À propos du projet
J'enviais l'affichage tête haute de la voiture d'un ami. Je l'ai donc construit moi-même : un HUD qui projette la vitesse (km/h) et le cap (degrés) sur le pare-brise via réflexion.
Principes logiciels
- Le GPS BN-220 envoie des trames NMEA
$GNRMCà 9600 bauds, 1 Hz. - Le code extrait vitesse (nœuds → km/h) et cap vrai (degrés) de chaque trame, avec vérification checksum.
- Trois afficheurs 7 segments pilotés par trois CD4511 (BCD + latch) en multiplexage : on active un latch, on met la valeur sur le bus 4 bits, on désactive, etc. Zéros non significatifs masqués.
- 3 boutons : + / - pour luminosité (PWM, sauvée en EEPROM après 60 s), S/H pour basculer Vitesse ↔ Cap (LED rouge allumée = mode Cap).
- Important : retirer le jumper sur Rx avant de flasher l'Arduino (conflit avec Tx GPS), le remettre après.
Évolutions
- V2 (juil. 2021) : ajout altitude via trame
$GNGGA(3e appui sur S/H). Nécessite : config GPS pour sortir GNGGA, déplacer PWM D5 vers point décimal afficheur central (R 220 Ω), connecter D13 à l'ancien circuit D5, flasher sketch V2. - V2.2 (nov. 2021) : luminosité auto par photorésistance LDR GL5539 + 10 kΩ (3 niveaux : jour/nuageux/nuit). Boutons +/− restent prioritaires ; appui long simultané = retour auto. EEPROM plus utilisée.
Code (sketch principal)
/* Car HUD - Marco Zonca 10/2020
Arduino Nano, GPS BT-220, 3x 7seg + 3x CD4511, EEPROM, 3 boutons
ATTENTION : retirer jumper Rx avant upload !
*/
#include <Wire.h>
#include <EEPROM.h>
String inputString = "";
String nm_time, nm_validity, nm_latitude, nm_longitude, nm_knots, nm_truecourse, nm_date;
float nmf_knots = 0, nmf_kmh = 0, nmf_truecourse = 360;
int nmi_kmh = 0, nmi_truecourse = 0;
byte kCent=0, kDeci=0, kUnit=0, tCent=0, tDeci=0, tUnit=0;
byte brightness = 120;
const byte latch_on = LOW, latch_off = HIGH;
unsigned long lastmemcheck = 0;
const unsigned long memcheck = 60000;
bool stringComplete = false, isKMH = true;
const int addr = 0;
// Broches
const int disp001 = 2, disp010 = 8, disp100 = 12; // Latchs unités/dizaines/centaines
const int disp001dim = 9, disp010dim = 10, disp100dim = 11; // PWM luminosité
const int button_kt = 14, button_more = 15, button_less = 16;
const int degreesLED = 3;
const int bit_3 = 7, bit_2 = 6, bit_1 = 5, bit_0 = 4;
const int dly = 10;
const byte off = 0;
const byte numbers[10][4] = {{0,0,0,0},{1,0,0,0},{0,1,0,0},{1,1,0,0},{0,0,1,0},
{1,0,1,0},{0,1,1,0},{1,1,1,0},{0,0,0,1},{1,0,0,1}};
void setup() {
Serial.begin(9600); Wire.begin(); inputString.reserve(200);
brightness = EEPROM.read(addr);
if (brightness > 250 || brightness < 10) brightness = 120;
pinMode(disp001, OUTPUT); pinMode(disp010, OUTPUT); pinMode(disp100, OUTPUT);
pinMode(degreesLED, OUTPUT);
pinMode(button_kt, INPUT_PULLUP); pinMode(button_less, INPUT_PULLUP); pinMode(button_more, INPUT_PULLUP);
pinMode(bit_3, OUTPUT); pinMode(bit_2, OUTPUT); pinMode(bit_1, OUTPUT); pinMode(bit_0, OUTPUT);
analogWrite(disp001dim, off); analogWrite(disp010dim, off); analogWrite(disp100dim, off);
analogWrite(degreesLED, off);
setBusNr(0);
digitalWrite(disp001, latch_on); digitalWrite(disp010, latch_on); digitalWrite(disp100, latch_on);
delay(dly);
digitalWrite(disp001, latch_off); digitalWrite(disp010, latch_off); digitalWrite(disp100, latch_off);
analogWrite(disp001dim, brightness); analogWrite(disp010dim, brightness); analogWrite(disp100dim, brightness);
}
void loop() {
if (stringComplete) {
bool ret = nmeaExtractData();
inputString = ""; stringComplete = false;
if (ret) {
kCent = nmi_kmh/100; int n = nmi_kmh - kCent*100; kDeci = n/10; kUnit = n - kDeci*10;
tCent = nmi_truecourse/100; n = nmi_truecourse - tCent*100; tDeci = n/10; tUnit = n - tDeci*10;
display();
}
}
if (millis() > lastmemcheck + memcheck) { EEPROM.update(addr, brightness); lastmemcheck = millis(); }
checkButtons();
}
void display() {
if (isKMH) { // Vitesse km/h
analogWrite(degreesLED, off);
setBusNr(kUnit); digitalWrite(disp001, latch_on); delay(dly); digitalWrite(disp001, latch_off);
if (kDeci > 0 || kCent > 0) { setBusNr(kDeci); digitalWrite(disp010, latch_on); delay(dly); digitalWrite(disp010, latch_off); analogWrite(disp010dim, brightness); }
else analogWrite(disp010dim, off);
if (kCent > 0) { setBusNr(kCent); digitalWrite(disp100, latch_on); delay(dly); digitalWrite(disp100, latch_off); analogWrite(disp100dim, brightness); }
else analogWrite(disp100dim, off);
} else { // Cap degrés
analogWrite(degreesLED, brightness);
setBusNr(tUnit); digitalWrite(disp001, latch_on); delay(dly); digitalWrite(disp001, latch_off);
if (tDeci > 0 || tCent > 0) { setBusNr(tDeci); digitalWrite(disp010, latch_on); delay(dly); digitalWrite(disp010, latch_off); analogWrite(disp010dim, brightness); }
else analogWrite(disp010dim, off);
if (tCent > 0) { setBusNr(tCent); digitalWrite(disp100, latch_on); delay(dly); digitalWrite(disp100, latch_off); analogWrite(disp100dim, brightness); }
else analogWrite(disp100dim, off);
}
}
void checkButtons() {
if (digitalRead(button_kt) == LOW) { isKMH = !isKMH; delay(250); }
if (digitalRead(button_more) == LOW) { if (brightness <= 240) brightness += 10; updateBrightness(); delay(100); }
if (digitalRead(button_less) == LOW) { if (brightness >= 20) brightness -= 10; updateBrightness(); delay(100); }
}
void updateBrightness() {
analogWrite(disp001dim, brightness); analogWrite(disp010dim, brightness); analogWrite(disp100dim, brightness);
}
void setBusNr(int number) {
for (byte b = 0; b <= 3; b++) {
int pin = (b==0)?bit_0:(b==1)?bit_1:(b==2)?bit_2:bit_3;
digitalWrite(pin, numbers[number][b] ? HIGH : LOW);
}
}
// Extraction données NMEA $GNRMC + checksum
bool nmeaExtractData() {
if (!(inputString.substring(0,6) == "$GNRMC" && inputString.substring(inputString.length()-4, inputString.length()-2) == nmea0183_checksum(inputString)))
return false;
int y = 0, z;
for (int s = 1; s < 11; s++) {
y = inputString.indexOf(",", y);
z = inputString.indexOf(",", y+1);
String val = (z > y+1) ? inputString.substring(y+1, z) : "";
switch(s) {
case 1: if(val.length()) nm_time = val.substring(0,2)+":"+val.substring(2,4)+":"+val.substring(4,6); break;
case 2: if(val.length()) nm_validity = val; break;
case 3: if(val.length()) nm_latitude = val.substring(0,2)+"°"+val.substring(2,10)+"'"; break;
case 4: if(val.length()) nm_latitude += val; break;
case 5: if(val.length()) nm_longitude = val.substring(0,3)+"°"+val.substring(3,11)+"'"; break;
case 6: if(val.length()) nm_longitude += val; break;
case 7: if(val.length()) { nmf_knots = val.toFloat(); nmf_kmh = round(nmf_knots * 1.852); nmi_kmh = (int)round(nmf_kmh); } break;
case 8: if(val.length()) { nmf_truecourse = val.toFloat(); nmi_truecourse = (int)round(nmf_truecourse); } break;
case 9: if(val.length()) nm_date = val.substring(0,2)+"/"+val.substring(2,4)+"/20"+val.substring(4,6); break;
}
y = z;
}
return true;
}
void serialEvent() {
while (Serial.available()) {
char c = Serial.read(); inputString += c;
if (c == '\n') stringComplete = true;
}
}
String nmea0183_checksum(String s) {
int crc = 0;
for (int i = 1; i < s.length()-5; i++) crc ^= s[i];
String h = String(crc, HEX); if (h.length()==1) h = "0"+h; h.toUpperCase(); return h;
}
Schémas et PCB
Deux PCB double face (≈50 rivets traversants + 5 trous d'alignement). Fichiers pour transfert toner (feuilles bleues/jaunes, laser 1200 dpi, fer à repasser). Gravure au perchlorure de fer. Masque composants imprimé aussi pour aspect pro.

Conduite prudente : au volant, ne regardez que la route !
Processus de fabrication
- Révolutionner la mobilité : à l'intérieur de la voiture semi-autonome d'Arrow Electronics pour conducteurs handicapés
- Voiture autonome Donkey Car : IA TensorFlow MobileNetV2 pour collecte de déchets
- Planification de mouvement temps réel pour véhicules autonomes en milieu urbain : Étude prototype à échelle réduite
- Comment la 5G transforme les voitures connectées : Vitesse, sécurité et fonctions intelligentes
- Sécurité d'abord : comment les Européens voient la voiture connectée de demain
- Choisir le câble moteur optimal pour les variateurs de vitesse (VSD)
- Tours rapides Twister : Polissage et ébavurage avancés avec 50 % plus de puissance et contrôle régénératif
- Tour à lit incliné FBL-1233 : précision, vitesse et polyvalence pour grande et petite série
- Échange de données produit : le défi clé pour les fabricants automobiles