making hub

Digitaler Tachometer

In diesem Beitrag zeige ich dir, wie du deinen eigenen digitalen Tachometer für dein Fahrzeug bauen kannst.

Ein solches Projekt erfordert nicht nur technisches Geschick und Kreativität, sondern bietet auch die Möglichkeit, etwas Einzigartiges zu schaffen, das perfekt auf deine Bedürfnisse abgestimmt ist. Egal, ob du ein Technik-Enthusiast bist oder einfach nur Spaß am Basteln hast dieser DIY-Tacho ist eine großartige Möglichkeit, deine Fähigkeiten auszubauen und dein Fahrzeug mit einem personalisierten Touch zu versehen.

Lass uns loslegen und Schritt für Schritt durch den Bauprozess gehen!

Benötigte Komponenten:

Funktionsweise des Tachometers:

Um die aktuelle Geschwindigkeit zu messen, wird einer oder mehrere kleine Magnete an einer Speiche des Vorderrads befestigt (bei mehreren ist auf eine symmetrische Anordnung zu achten, siehe Abbildung), während ein Hall-Sensor an der Gabel montiert wird. Jedes Mal, wenn der Magnet am Sensor vorbeikommt, registriert dieser eine Umdrehung des Rades.

Die Geschwindigkeit wird aus der Anzahl der Radumdrehungen pro Zeitspanne und dem Umfang des Rades berechnet, den der Benutzer zuvor in der Software eintragen muss. Der Radumfang dient als Basis, um die Geschwindigkeit mit der Formel zu ermitteln:

$$ geschwindigkeit = (radumfang (m) ⋅ deltaTime (s))⋅3.6 $$

Schaltplan:

Die Schaltung umfasst vier Hauptkomponenten: einen Adafruit Trinket M0 Mikrocontroller, ein Display, einen Hallsensor sowie eine speziell entwickelte Leiterplatte, die für die Gleichrichtung und Transformation der Spannung auf 5V zuständig ist.

Das Display wird von der Leiterplatte mit Spannung versorgt. Die I2C-Datenleitungen des Displays sind direkt mit dem Mikrocontroller verbunden.

Der Hallsensor wird ebenfalls an die Leiterplatte angeschlossen. Sein Ausgangssignal wird über die Leiterplatte zum GPIO-Pin 1 des Mikrocontrollers weitergeleitet.

Der Mikrocontroller bezieht die 5V-Betriebsspannung sowie GND von der Leiterplatte. Zusätzlich liefert er eine 3,3V-Leitung zurück an die Leiterplatte. Diese 3,3V-Spannung wird zur Versorgung des Displays, des Pull-up-Widerstands und des Hallsensors genutzt.

Leiterplatte:

Die Leiterplatte umfasst einen Gleichrichter aus vier Dioden, einen Pull-up-Widerstand für das Hallsensorsignal, einen LM1117-Spannungsregler sowie einen Anschluss für einen externen 1000-µF-Kondensator.

Am Eingang (IN) der Leiterplatte wird eine Wechsel- oder Gleichspannung zwischen 6 und 20 V (gemäß Datenblatt des LM1117) angelegt. Diese Spannung wird durch den Gleichrichter in eine Gleichspannung umgewandelt und anschließend mithilfe des Kondensators geglättet. Der Spannungsregler wandelt die geglättete Spannung in konstante 5 V um, die zur Versorgung des Mikrocontrollers dienen.

Der Kondensator ist aus Platzgründen nicht direkt auf der Leiterplatte montiert und wird extern angeschlossen. Zusätzliche Anschlüsse auf der Leiterplatte sind für die Anbindung der Peripherie vorgesehen.

Die Leiterplatte wurde mit Fusion 360 erstellt und kann anhand der Gerber-Files bei JLCPCB beschafft werden.

Spannungsverdoppler:

Neuere Motorräder verwenden in der Regel ein 12V-Gleichspannungssystem, wodurch der Betrieb des Tachos problemlos möglich ist. Ältere Mopeds und Motorräder hingegen verfügen oft über ein 6V-Wechselspannungssystem ohne Batterie. In solchen Fällen kann es vorkommen, dass die vom Motor erzeugte Wechselspannung im Leerlauf deutlich unter 6V sinkt. Dies führt dazu, dass der Spannungswandler nicht mehr korrekt arbeitet.

Um dieses Problem zu lösen, kann die Spannung mithilfe eines Spannungsverdopplers erhöht werden. Ein solcher Verdoppler besteht lediglich aus zwei Dioden und zwei Kondensatoren. Eine ausführliche Erklärung dieses Prinzips habe ich in einem separaten Blogbeitrag bereitgestellt.

Weitere Informationen gibt es dort:

       Spannungsverdoppler

Konstruktion:

Für das Gehäuse des Tachos wird ein alter 48 mm Tacho verwendet. Dieser wird an der Unterseite geöffnet, sodass der rechts gezeigte Aufbau hineinpasst.

Folgende Teile sind 3D geruckt:  

  • Distanzring (PLA)
  • Displayhalter (PLA)
  • Platinenhalter (PLA)
  • Abschlusskappe (PLA)
  • Kondensatorhalter (PLA)
  • Stopfen (TPU)

Software:

#include "SPI.h"
#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1    // Reset pin
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3C for 128x32

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
  #define Serial SERIAL_PORT_USBVIRTUAL
#endif

volatile int geschwindigkeit;
volatile unsigned long lastInterruptTime; // Zeitpunkt des letzten Interrupts

const int sensor = 1; // Pin für den Sensor
const unsigned long timeout = 1000; // Timeout für Stillstand in Millisekunden
int sensorsig;

// Individuell anpassen
const int magnetCount = 3;
const int radumfang = 1200; // mm

void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

  // Initialisierung des Displays
  display.display();
  delay(2000); // Pause for 2 seconds
  display.clearDisplay();

  pinMode(sensor, INPUT_PULLUP);

  // Startzeit initialisieren
  lastInterruptTime = millis();

  // Interrupt aktivieren
  attachInterrupt(digitalPinToInterrupt(sensor), time, FALLING);
}

void loop() {
  sensorsig = digitalRead(sensor);

  // Überprüfung auf Timeout
  unsigned long currentTime = millis();
  if (currentTime - lastInterruptTime > timeout) {
    geschwindigkeit = 0; // Geschwindigkeit auf 0 setzen bei Timeout
  }

  // Geschwindigkeitsanzeige
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print(geschwindigkeit, 1);
  display.setCursor(50, 0);
  display.setTextSize(2);
  display.print(" Km/h");
  display.display();

  // Serielle Ausgabe
  Serial.println("geschwindigkeit:");
  Serial.println(geschwindigkeit);
  Serial.println("sensor:");
  Serial.println(sensorsig);
}

void time() {
  unsigned long currentTime = millis();
  unsigned long deltaTime = currentTime - lastInterruptTime;

  // Zeit in Sekunden umrechnen
  if (deltaTime > 0) {
    geschwindigkeit = (((radumfang / 1000.0) / magnetCount) / (deltaTime / 1000.0)) * 3.6;
  }

  // Zeitpunkt des letzten Interrupts aktualisieren
  lastInterruptTime = currentTime;
}

Codebeschreibung:

Bibliotheken

  1. #include „SPI.h“
    • Bibliothek für die SPI-Schnittstelle (Serial Peripheral Interface). Wird hier nicht explizit genutzt, aber könnte erforderlich sein, falls andere Peripheriegeräte über SPI angeschlossen werden.
  2. #include „Wire.h“
    • Bibliothek für die I²C-Schnittstelle (Inter-Integrated Circuit), die für die Kommunikation mit dem OLED-Display verwendet wird.
  3. #include „Adafruit_GFX.h“ und #include „Adafruit_SSD1306.h“
    • Adafruit_GFX: Bietet grundlegende Grafikfunktionen wie Zeichnen von Text, Linien und Formen.
    • Adafruit_SSD1306: Eine spezifische Bibliothek zur Ansteuerung des SSD1306-OLED-Displays.

 

Konstanten und Makros

  1. #define SCREEN_WIDTH 128 und #define SCREEN_HEIGHT 32
    • Breite und Höhe des OLED-Displays in Pixeln (128×32).
  2. #define OLED_RESET -1
    • Reset-Pin des Displays. Mit -1 wird angezeigt, dass kein Reset-Pin verwendet wird.
  3. #define SCREEN_ADDRESS 0x3C
    • Die I²C-Adresse des OLED-Displays. Diese wird im Datenblatt des Displays angegeben.

 

Globale Variablen

  1. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET)
    • Initialisierung des OLED-Display-Objekts mit der Adafruit-Bibliothek.
  2. volatile int geschwindigkeit
    • Die aktuelle Geschwindigkeit, berechnet in der Interrupt-Service-Routine (ISR). Als volatile markiert, da sie sowohl im Hauptprogramm als auch in der ISR verwendet wird.
  3. volatile unsigned long lastInterruptTime
    • Speichert den Zeitpunkt des letzten Interrupts in Millisekunden. Wird ebenfalls als volatile markiert.
  4. const int magnetCount = 3
    • Anzahl der Magneten an dem Rad.
  5. const int radumfang = 1200
    • Umfang des Rads in Millimetern.
  6. const int sensor = 1
    • Pin, an dem der Magnet-Sensor angeschlossen ist.
  7. const unsigned long timeout = 1000
    • Timeout-Zeit in Millisekunden. Wird verwendet, um die Geschwindigkeit auf 0 zu setzen, falls der Sensor für eine bestimmte Zeit keinen Interrupt auslöst (Stillstand des Fahrzeugs).
  8. int sensorsig
    • Speichert den aktuellen Zustand des Sensors (1 = High, 0 = Low).

 

Funktionen

setup()
  • Initialisiert die serielle Kommunikation mit Serial.begin(9600) (Baudrate: 9600).
  • Initialisiert das OLED-Display mit display.begin(). Wenn das Display nicht erfolgreich gestartet wird, wird eine Fehlermeldung ausgegeben und das Programm stoppt.
  • Zeigt den Adafruit-Splashscreen für 2 Sekunden an, bevor der Displaypuffer gelöscht wird.
  • Konfiguriert den Sensor-Pin als Eingang mit Pull-up-Widerstand (INPUT_PULLUP).
  • Initialisiert die Variable lastInterruptTime mit der aktuellen Millisekunden-Zeit (millis()).
  • Aktiviert den Interrupt am definierten Sensor-Pin (attachInterrupt). Der Interrupt wird bei fallenden Flanken (FALLING) des Sensorsignals ausgelöst und ruft die Funktion time() auf.
loop()
  • Liest den aktuellen Zustand des Sensors und speichert ihn in sensorsig.
  • Überprüft, ob seit dem letzten Interrupt mehr als timeout Millisekunden vergangen sind. Falls ja, wird die Geschwindigkeit auf 0 gesetzt. Dies simuliert den Stillstand des Fahrzeugs.
  • Zeichnet die Geschwindigkeit in großen Zahlen (Textgröße 3) auf das OLED-Display. Einheiten („Km/h“) werden daneben in kleinerer Schrift angezeigt.
  • Gibt die Geschwindigkeit und den Sensorstatus über die serielle Schnittstelle aus.
time()
  • Diese Funktion wird durch den Interrupt aufgerufen, wenn der Sensor ausgelöst wird.
  • Berechnet die Zeitdifferenz (deltaTime) seit dem letzten Interrupt in Millisekunden.
  • Berechnet die Geschwindigkeit in km/h mithilfe der Formel: geschwindigkeit=(radumfang (in m)deltaTime (in s))⋅3.6
  • Aktualisiert die Variable lastInterruptTime mit der aktuellen Zeit.

 

Funktionsweise

  1. Startphase:
    • Das Display wird initialisiert, und der Adafruit-Splashscreen wird für 2 Sekunden angezeigt. Danach beginnt die Geschwindigkeitsmessung.
  2. Geschwindigkeitsmessung:
    • Immer wenn der Magnet-Sensor ein Signal (fallende Flanke) registriert, wird ein Interrupt ausgelöst. In der ISR wird die Geschwindigkeit basierend auf der Zeitdifferenz seit dem letzten Interrupt berechnet.
  3. Stillstandserkennung:
    • In der loop-Funktion wird regelmäßig überprüft, ob seit dem letzten Sensor-Interrupt mehr als timeout Millisekunden vergangen sind. Falls ja, wird die Geschwindigkeit auf 0 gesetzt.
  4. Ausgabe:
    • Die Geschwindigkeit wird auf dem OLED-Display und über die serielle Schnittstelle ausgegeben.

 

Wichtige Hinweise

  • Timeout und Genauigkeit: Der Timeout-Wert (1000 ms) bestimmt, wie schnell das Programm auf Stillstand reagiert. Ein zu hoher Wert könnte dazu führen, dass die Geschwindigkeit länger angezeigt wird, als das Fahrzeug tatsächlich in Bewegung ist.
  • Pin-Konflikte vermeiden: Der Sensor ist an Pin 1 angeschlossen. Auf vielen Arduino-Boards (z. B. Uno) wird dieser Pin für die serielle Kommunikation verwendet, was zu Problemen führen kann. Es wäre ratsam, einen anderen Pin zu verwenden.
  • Einheiten: Der radumfang ist in Millimetern angegeben, wird aber in der Berechnung intern in Meter umgerechnet.