Die wichtigste Datenquelle für die Blockstellenlogik ist der Belegtmelder oder Gleisbesetztmelder. Aus den im vorherigen Beitrag beschriebenen Gründen, habe ich mich dazu entschieden den Belegtmelder auf Basis der Stromfusserkennung aufzubauen.
Was Du für diesen Teil brauchst
- 1x INA219 Breakout Module (hier kaufen bei Amazon)
- 1x LN219 Module (hier kaufen bei Amazon)
- 1x ESP32 Development Board (hier kaufen bei Amazon)
- Diverse Kabel und Breadboard (hier kaufen bei Amazon)
- Gleichstromlok und Schienenset mit Stromanschluss, z.B. Roco 42521 (hier kaufen bei Amazon)
- Oder ein komplettes Starterset für Gleichstrom analog
- Piko: z.B. Start-Set DB Cargo BR 218 (hier kaufen)
- Roco: z.B. Analog Start Set Rh 2045 (hier kaufen)
- 12V DC Netzteil mit passender Buchse (hier kaufen bei Amazon)
- Android Handy mit App aus Teil 6: Android App mit Bluetooth als Steuerpult
Der I²C Bus
Da der eingesetzte Stromsensor INA219 Messergebnisse über den sogenannten I²C Bus bereitstellt, schauen wir uns dessen Implementierung auf dem ESP32 als erstes an. I²C, oder oft auch nur I2C, steht für Inter Integrated Circuit und wurde für die Kommunikation integrierter Schaltkreise untereinander entwickelt. Genutzt werden dazu zwei Leitungen: SCL als gemeinsame Leitung für den Bustakt (Clock) und SDA als Datenleitung (Data). Meherere Geräte können an diese Bus-Leitungen angeschlossen und über eine 7-Bit Adresse (max. 128 Teilnehmer) oder eine 10-Bit Adresse (max. 1024 Teilnehmer) angesprochen werden. Beide Leitungen müssen im Ruhezustand per Pull-up Widerstand auf Vcc (beim ESP32 3,3V) gezogen werden.
Der ESP32 nutzt für die I²C Implementierung die Wire-Library. Die Standard-Pins sind:
SDA | 21 |
SCL | 22 |
Im einfachsten Fall wird I²C wie folgt aktiviert:
#include <Wire.h>
void setup() {
// Start Serial
Serial.begin(115200);
delay(500);
Serial.println("++++Neustart++++");
// Start I2C
Wire.begin();
}
Durch Übergabe von Parametern können aber auch andere Pins genutzt werden:
const int I2C_SDA = 21;
const int I2C_SCL = 22;
Wire.begin(I2C_SDA, I2C_SCL, 100000);
I²C wird mit Pin 21 und 22 und einem Takt von 100 kHz gestartet.
I²C Scanner
Um die korrekte Verkabelung und Funktionalität des Bus im Falle von Debugging sicherzustellen, empfiehlt es sich, den Bus nach angeschlossenen Devices zu durchsuchen. Dazu wird in einer for-Schleife auf jeder Adresse ein Verbindungsaufbau gestartet. Man erkennt somit direkt, ob das gesuchte Device erkannt wird oder nicht.
bool od_scanI2C() {
byte error = 0;
byte address = 0;
int n = 0;
Serial.println("Scanning I2C...");
for (address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
n++;
}
}
if (n == 0) {
Serial.println("No I2C devices found\n");
return false;
}
else {
Serial.println("done\n");
return true;
}
}
Diese Funktion gibt true für den Fall mehr als 0 gefundener Devices zurück und false, wenn kein Device gefunden wird. Man kann die Funktion nach Wire.begin in setup() aufrufen:
const int I2C_SDA = 21;
const int I2C_SCL = 22;
Wire.begin(I2C_SDA, I2C_SCL, 100000);
if (od_scanI2C()) {
// do something with I2C devices
}
Das INA219 Modul
Das INA219 Modul misst Spannungen an einem mit dem Stromkreis in Reihe geschalteten Messwiderstand (Shunt) und nutzt das Prinzip des Spannungsabfalls an einem stromdurchflossenen Widerstand gemäß der Formel
U = R * I oder umgestellt I = U / R
zur Berechnung der Stromstärke. Damit die Messung nicht spürbar den Stromkreis verändert, ist dieser Widerstand üblicherweise sehr klein (z.B. R100, entspricht 100 mOhm). Der dadurch sehr kleine Spannungsabfall muss demnach sehr genau bzw. über eine Verstärkung gemessen werden.
Aufgrund der Funktionsweise der Verstärkung muss das INA219 Modul in einer sogenannten „High-side Konfiguration“ zwischen positiver Seite der Spannungsquelle und Hauptverbraucher eingebaut werden.

Der INA219 Baustein führt die Verstärkung und Messung durch und stellt Messergebnisse für Spannung und Stromstärke digital über die I²C-Schnittstelle bereit.
Um mehrere INA219 Module an einem I²C Bus betreiben zu können, lassen sich auf den oben verlinkten Breakout-Boards durch ein Verbinden der Lötpunkte bei A0 und/oder A1 neben der voreingestellten Adresse drei weitere Adressen konfigurieren.
A0 und A1 offen (Standard) | 0x40 |
A0 verbunden | 0x41 |
A1 verbunden | 0x44 |
A0 und A1 verbunden | 0x45 |
Für die Nutzung mit dem ESP32 gibt es verschiedene Bibliotheken. Die am weitesten verbreitete dürfte die Adafruit_INA219 sein, für die sich viele Anleitungen im Web finden. Ein Nachteil dieser Library ist aber, dass die voreingestellten Kalibrierungen keine Messwert-Mittelung aus einer Vielzahl von Einzelmessungen unterstützt. Der INA219 bietet diese Funktionalität, softwareseitig müsste man die Anpassungen aber in den Library Dateien vornehmen, was sicherlich zu Problemen beim nächsten Update führt.
Daher habe ich mich für die hervorragende Library INA219_WE von Wolfgang Ewald entschieden: https://github.com/wollewald/INA219_WE . Mit dieser kann man viele Einstellungen zum Messbereich und zu Samplings ohne Code-Änderungen in den Dateien der Library nutzen. Die Library muss vor Gebrauch über den Punkt „Manage Libraries“ im Tools-Menü der Arduino IDE installiert werden: Im Suchfeld „INA219_WE“ eingeben und dann auf „Install“ klicken.
INA219 Probleme mit PWM
Wenn wir uns nochmal den Spannungs- und damit auch Stromverlauf von PWM ansehen (siehe Teil 2: PWM und MOSFET für eine einfache Motorsteuerung) und mit dem Messprinzip des INA219 übereinanderlegen, tritt zwangsläufig ein Konflikt zutage: PWM schaltet den Stromfluss sehr schnell an und aus. An welcher Stelle der INA219 misst, ist rein zufällig. Die Messung hat also nicht notwendigerweise etwas mit dem mittleren Stromfluss am Motor zu tun. Da wir für die Belegtmeldung nur „irgendeinen“ Stromfluss erkennen wollen, soll uns das Problem in diesem Kontext nicht weiter stören, solange wir zuverlässig Messungen während des Stromflusses erzielen.
In meinen Tests habe ich aber noch ein weiteres Problem erkannt, wenn gerade ein Zug die Isolierung von zwei Abschnitten überbrückt. Bei Nutzung von PWM mit kleinen Duty-Cycles antworten die INA219 Module dann nur langsam auf I2C Anfragen oder hängen sich komplett auf, was auch zum Absturz des ganzen ESP mit unkontrollierten Zugfahrten führen kann. Ich bin dem Problem begegnet, in dem ich die PWM Frequenz auf 39062 Hz bis nah an das Limit der L298N Motortreiber verdoppelt habe. Gleichzeitig wird die Belegterkennung nur ausgeführt, wenn der Duty-Cycle >150 (von 255) ist. Für den Automatik-Betrieb wird später an anderer Stelle sichergestellt, dass immer mindestens ein ausreichender Duty-Cycle vorgewählt ist.
INA219 am ESP32
#include <Wire.h>
#include <INA219_WE.h>
INA219_WE ina219_block1 = INA219_WE(0x40);
bool od_scanI2C() {
// see function code above
return true;
}
void setup() {
// Start Serial
Serial.begin(115200);
delay(500);
Serial.println("++++Neustart++++");
Wire.begin(I2C_SDA, I2C_SCL, 125000); // start I2C bus as master
if (od_scanI2C()) {
// start INA219 Sensors
if (! ina219_block1.init()) {
Serial.println("Failed to find INA219 chip for block 1");
return false;
}
ina219_block1.setADCMode(SAMPLE_MODE_128);
ina219_block1.setPGain(PG_320);
}
}
void loop() {
float current = 0.0;
current = ina219_block1.getCurrent_mA();
Serial.println(current);
delay(1000);
}
Zunächst müssen die Libraries für I²C und den INA219 geladen und die Adresse des Moduls zugewiesen werden. Dann werden in setup() I²C und der Sensor gestartet. Auf die I²C-Scanner Funktion habe ich hier der Übersicht halber verzichtet und sie mit einem einfachen return true; „kurzgeschlossen“. Sofern der Sensor erfolgreich gestartet werden kann, wird die Messung auf den Durchschnitt von 128 Einzelmessungen eingestellt. Der INA219 führt diese kontinuierlich im Hintergrund durch und speichert den aktuellsten Durchschnitt im Ausgabe-Register wo es für den Abruf bereit liegt. Dann wird der PGain noch auf 320 gestellt, was dem Default Messbereich bis 3,2A entspricht.
In der loop() Funktion wird dann jede Sekunde der gemessene Strom ausgelesen.
Belegterkennung zunächst ohne weitere Blockstellen
Für die Belegterkennung brauchen wir jetzt noch eine Funktion, die diese Werte verarbeitet und sinnvoll als belegt / frei abspeichert, um sie anderen Funktionen der Blockstellenlogik zugänglich zu machen. Ich habe mich nach einigen Tests dafür entschieden, die positive Erkennung von Stromfluss unmittelbar in eine Belegtmeldung zu führen, das Ausbleiben eines Stromflusses aber erst in einem zweistufigen Prozess in eine Freimeldung zu führen.
Das Prefix „od_“ steht übrigens für Occupancy Detection, damit die damit zusammenhängenden Funktionen und Variablen besser von anderen Teilen des Codes unterschieden werden können.
void od_processBlock (byte block) {
float current = 0.0;
current = od_getCurrent(block);
if (current >= -10.0) { // only positive values are checked. values are positive if sensor is correct positioned on high side at forward direction
// Handle block above threshold and not yet detected: Set OCCUPIED
if (current > od_threshold_blocks && !od_detected[block]) {
od_detected[block] = true;
Serial.print((String)time0 + ": Occupancy detected, Block " + block);
Serial.println((String)" (" + current + " mA)");
}
// Handle block below threshold but still detected: Set FREE
else if (current < od_threshold_blocks && od_detected[block]) {
od_detected[block] = false;
Serial.print((String)time0 + ": Occupancy lost, Block " + block);
Serial.println((String)" (" + current + " mA)");
}
// Handle all other cases
else {
// no action required
}
}
}
Über die Funktion od_getCurrent(block) wird in einer hier nicht sichtbaren Funktion die Stromstärke in mA ausgelesen. Das funktioniert wie weiter oben dargestellt.
Die globale Variable od_threshold_blocks beinhaltet den Schwellwert, ab dem ein Stromfluss als Belegtmeldung erkannt wird. Ich nutze hierfür 10.0 (mA).
In einem globalen Array od_detected werden unter der Nummer des Blocks (in diesem Teil immer nur 1) mit true oder false die Belegt- bzw. Freimeldungen gespeichert.
Integration in das bestehende Projekt
Du merkst es, der Code wird langsam komplexer. Um die Schritte nachvollziehbar zu halten, bauen wir jetzt diesen Belegtmelder in den Code aus Teil 6: Android App mit Bluetooth als Steuerpult ein, zunächst ohne eine weitere Blockstelle. Um den Code aufgeräumt zu halten, habe ich ein neues Tab über die drei Punkte rechts oben in der Arduino IDE angelegt und „occupancy.ino“ genannt.

Dort kommen alle Funktionen zum Belegtmelder rein.
Im Hauptteil müssen dann noch die Libraries geladen, die gobalen Variablen deklariert und die Setup und Loop Funktionen des Belegtmelders aufgerufen werden.
Der Code des Projekts ist dann wie folgt:
Sobald ein Zug fährt, erscheint im seriellen Monitor der Arduino IDE die Nachricht „Zug erkannt, Block 1“. Wird der Zug angehoben oder der Slider in der App in die Mitte bewegt, wird Block 1 wieder freigemeldet.