Modellbahntechnik


INHALTSVERZEICHNIS

1. Digitale DCC-Modellbahnsteuerung mit ARDUINO
1A. Das DCC-Protokoll
1B. Erklärungen zu Arduino
1C. Elektronischer Aufbau der Basisstation
2. Digitale Modellbahnsteuerung mit ARDUINO ohne DCC
2A. Vorbereitung der Lokomotive
2B. Umbau der Lokomotive

ANHANG (Codelistings)

ARDUINO SOURCECODE DCC-PROJEKT (GEMEINSAMER HEADER)
ARDUINO SOURCECODE DCC-PROJEKT (BASISSTATION)
ARDUINO SOURCECODE DCC-PROJEKT (FERNSTEUERUNG)
ARDUINO SOURCECODE NO-DCC-PROJEKT (LOKSEITIG)
ARDUINO SOURCECODE NO-DCC-PROJEKT (FERNSTEUERUNG)


1. Digitale DCC-Modellbahnsteuerung mit ARDUINO

Ziel ist eine drahtlose, mit einem ARDUINO Mikrokontroller realisierte Modellbahnsteuerung. Als Modellbahnsteuerungsprotokoll kommt DCC in einer vereinfachten Version zur Anwendung.

1A. Das DCC-Protokoll

Heutige Modellbahnen werden digital gesteuert. Dazu musste ein Protokoll definiert werden. Dieses Protokoll trägt den Namen «DCC Digital Command Control» Heutzutags gibt es auch Alternativen zu DCC. Die DCC-Spezifikation findet man hier:

Das DCC-Gleissignal hat einerseits den Zweck, die Fahrzeuge mit Energie zu versorgen und andererseits, sie mit individuellen Fahrzeugkommandos zu steuern. Es ist also möglich, auf einem Gleis mehrere Züge gleichzeitig zu steuern. Das folgende Bild zeigt den Signalverlauf eines Steuerbefehls. Logisch 0 und Logisch 1 wird nicht durch eine Spannungsdifferenz, sondern durch eine verschieden lange Pulsdauer unterschieden.



Ein einfaches DCC-Paket besteht aus 44 Bit und setzt sich wie folgt zusammen:




1B. Erklärungen zu Arduino

Arduino ist eine quelloffene, aus Soft- und Hardware bestehende Physical-Computing-Plattform. Die Hardware besteht grundsätzlich aus einem E/A-Board mit einem Mikrocontroller und analogen und digitalen Ein- und Ausgängen. Die Programmierung erfolgt in einer C- bzw. C++-ähnlichen Programmiersprache, wobei technische Details wie Header-Dateien vor den Anwendern weitgehend verborgen werden und umfangreiche Bibliotheken und Beispiele die Programmierung vereinfachen.
Beispiel für ein Programm (Sketch), das eine an das Arduino-Board angeschlossene LED blinken lässt:

int ledPin = 13;               //LED an Pin 13

void setup()                   //Wird beim Start des Programms einmalig aufgerufen
{
  pinMode(ledPin, OUTPUT);     //LED-Pin als Ausgang festlegen
}

void loop()                    //Wird ständig wiederholt, bis das Arduino-Board ausgeschaltet wird
{
  digitalWrite(ledPin, HIGH);  //LED anschalten
  delay(1000);                 //1000 Millisekunden warten
  digitalWrite(ledPin, LOW);   //LED ausschalten
  delay(1000);                 //1000 Millisekunden warten
}

Wie rechnet man auf «Bit-Ebene»?

Shift-Right: x >> n  (Bitfolge x um n-Stellen nach rechts verschieben. Rechts fallen die Bits heraus.)
Shift-Left:  x << n  (Bitfolge x um n-Stellen nach links verschieben. Neue 0-Bits werden rechts eingefügt.)
AND:         x & y   (0&0=0 / 0&1=0 / 1&0=0 / 1&1=1  Hinweis: Nicht verwechseln mit logischem AND &&)
OR           x | y   (0|0=0 / 0|1=1 / 1|0=1 / 1|1=1  Hinweis: Nicht verwechseln mit logischem OR ||)
XOR          x ^ y   (0^0=0 / 0^1=1 / 1^0=1 / 1^1=0)
NOT          ~x      (~0=1 / ~1=0)
(Die zur Verfügung stehende Bitbreite ist zu beachten! Stichwort: Data-Overflow)

Binäre Division durch 2 (Bsp.: 12 div 2 = 6 Rest 0)
12DEZ = 1100BIN
1100BIN >> 1BIN = 110BIN    (Shift-Right)
110BIN = 6DEZ              (Ist bei Shift-Right rechts eine 0 herausgefallen: Rest 0)

Binäre Division durch 2 (Bsp.: 15 div 2 = 7 Rest 1)
15DEZ = 1111BIN
1111BIN >> 1BIN = 111BIN    (Shift-Right)
111BIN = 7DEZ              (Ist bei Shift-Right rechts eine 1 herausgefallen: Rest 1)

Multiplikation mit Faktor 2 (Bsp.: 13 * 2 = 26)
13DEZ = 1101BIN
1101BIN << 1BIN = 11010BIN  (Shift-Left)
11010BIN = 26DEZ

Addition von 2 Bits inkl. Übertrag (Volladdierer)
(A=1.Bit, B=2.Bit, S=Summe, C1=Übertrag In, C2=Übertrag Out)
S = (A^B) ^ C1
C2= (A&B) | (C1&(A^B))

Zahlenbeispiel 1+0+1=10 oder 2DEZ
A = 1 (1. Bit)
B = 0 (2. Bit)
C1= 1 (Bit aus der vorangegangenen Binärstelle)
S = 0 (Summe)
C2= 1 (Übertrag in die nächste Binärstelle)

Bit auslesen (Bsp.: Zweites Bit von links auslesen)
a=0110   b=0011   c=0100
a & c = 0100
b & c = 0000

Bit setzen (Bsp.: Zweites Bit von links setzen)
a=0010   b=0110   c=0100
a | c = 0110 (Bit wurde gesetzt)
b | c = 0110 (Bit war schon gesetzt)

Bit löschen (Bsp.: Zweites Bit von links löschen)
a=0010   b=0110   c=0100   (Hinweis: ~c = 1011)
a & ~c = 0010 (Bit war schon gelöscht)
b & ~c = 0010 (Bit wurde gelöscht)




1C. Elektronischer Aufbau der Basisstation



Die Arduino-Codelistings findet man ab hier: ARDUINO SOURCECODE DCC-PROJEKT (GEMEINSAMER HEADER)




2. Digitale Modellbahnsteuerung mit ARDUINO ohne DCC

Hier wird auf DCC verzichtet. Eine RC-Fernsteuerung übermittelt seine Befehle zu Fahrtrichtung und Geschwindigkeit via Funkstrecke an einen RC-Empfänger in der Lok. Dieser steuert einen MAXON-Baustein mit H-Bridge, der wiederum den Motor mit Spannung versorgt. Somit braucht es den Umweg über DCC nicht.
Als Loktyp habe ich eine LGB-Märklin-Zahnraddampflok des Typs HG-4/4 (Art.-Nr.26270/71) gewählt. Da diese Lokomotive im abgelieferten Zustand nicht richtig überzeugen konnte, entschied ich mich, den ganzen elektrischen Teil auszubauen und mit neuen Komponenten zu versorgen.


Folgende Beweggründe gaben den Ausschlag für den Umbau:

Ein bewegtes Zahnstangentriebwerk bei Lokstillstand ist vorbildgerecht, erfordert aber einen zusätzlichen Motor (M) für die "Antriebs-Attrappe".


Das funktionelle Zahnrad ist mit einem knapp bemessenen Zahnriemen mit der einzig direkt angetriebenen vierten Achse verbunden.


Was wird wie ersetzt?

2A. Vorbereitung der Lokomotive

1. Schritt - Lösen von verschiedenen Klebeverbindungen:
Unterhalb der Rauchkammertür (E), an den beiden Laternen auf der Lokrückseite (F),(G) und die Lokaufstiege links und rechts der Rauchkammertür (A),(B),(C),(D).


2. Schritt - Lösen von Schrauben am Triebwerk:
Am Triebwerk müssen zwei Schrauben (A) und (B) entfernt werden. Um an der linken Seite an die Schraube (B) zu gelangen, muss zuerst das Kupferrohr (C) vorsichtig gelöst und entfernt werden. (Das Foto rechts zeigt das bereits demontierte Kupferrohr)


3. Schritt - Fahrwerk vom Aufbau trennen:
Zuerst müssen die drei Schrauben (A), (B) und (C) gelöst werden. Die violett markierten Schrauben aufdrehen, falls man die Achsabdeckungen entfernen möchte. Die rot markierten Gewindelöcher dienen ausschliesslich der Fixierung der Lok auf dem mitgelieferten Verpackungs-Gleisprofil.


4. Schritt - Lokaufbau entfernen:
Der Lokaufbau nun sorgfältig abheben. Fast alle elektrischen Verbindungen laufen über die Federpinleiste (Rote Pfeile in der Bildmitte). Nicht aber die Kabel für die rückseitigen Laternen (Roter Pfeil rechts). Darum Vorsicht beim Abnehmen des Lokaufbaus.


5. Schritt - Rauchgenerator entfernen:
Möchte man den Rauch- und Dampfgenerator erreichen, muss man zuerst das Blech an der Kesselunterseite entfernen. Dazu müssen die sechs mit grünen Pfeilen markierten Schrauben gelöst werden. Der Rauchgenerator ist mit (D) markiert. Er beinhaltet ein komplexes Belüftungssystem mit vier Lüftern. Auf der Höhe des Wasserkastens kann man den Lautsprecher erkennen. Bevor man das Rundlaufblech abnehmen kann, muss man vorher vorsichtig das Zahnstangensignal vom Wassertank lösen.


6. Schritt - Alles muss raus:
Fahrgestell mit Antriebsmotor (M) und Hilfsmotor (Z) für den Antrieb des sonst funktionslosen Zahnstangentriebwerks. Beim Tender erkennt man den Lokdecoder mit darunterliegenden Stützkondensatoren.


2B. Umbau der Lokomotive

Den Bühlermotor wurde durch einen MAXON RE30 (268213) ersetzt. Als PWM-Motorensteuerung dient der MAXON-Servokontroller ESCON Module 50/5 (438725). Die Ansteuerung und Funkstrecke erfolgt mit Arduinos von Adafruit Feather M0 Radio with RFM69 Packet Radio ISM 433MHz. (3177). Das Zahnstangentriebwerk wird durch die Zahnstange angetrieben.


Elektronikschema:



Die Arduino-Codelistings findet man ab hier: ARDUINO SOURCECODE NO-DCC-PROJEKT (LOKSEITIG)


ANHANG (Codelistings)

ARDUINO SOURCECODE DCC-PROJEKT (GEMEINSAMER HEADER)

VERWENDETER MIKROKONTROLLER:
Adafruit Feather M0 Radio with RFM69 Packet Radio, ATSAMD21G18 ARM Cortex M0 mit Funkmodul
RH_RF69 Radio-Control 433MHz ISM-Band Simple-messageing No-addressing No-reliability AES-encryption.
available() → Tested ob ein Funk-Paket vorhanden ist
waitAvailable() → Tested bis ein Funk-Paket vorhanden ist
waitPacketSent() → Wartet bis Funk-Paket verschickt ist
waitPacketSent(uint16_t timeout) → Wartet bis Timout oder bis Funk-Paket verschickt ist
waitAvailableTimeout(uint16_t timeout) → Tested bis Timout oder bis ein Funk-Paket vorhanden ist

ZUSÄTZLICHE ANGABEN ZUR BASISSTATION:
Der Funkhandregler schickt vom Benutzer eingegebene Lok-Befehle an die DCC-Basisstation. Diese Basisstation empfaengt vom Funkhandregeler ein DCC-Kommando und erstellt das entsprechende Gleissignal (DCC-Paket).
Akku LiPo 4200mAh
Schmelzsicherung am Eingang der Versorgungsspannung
82mOhm Messwiderstand für Fahrstrommessung
10k zu 100k Spannungsteiler zur Akkuspannungsüberwachung
Zweifarbige Kontroll-LED Rot/Grün

ZUSÄTZLICHE ANGABEN ZUM FUNKHANDREGLER:
Vier Eingabetaster
Drei rote Kontroll-LEDs

SCHALTBARE FUNKTIONEN DER MAERKLIN/LGB HG4/4 ART.-NR. 26270:
00: Beleuchtung
01: Geräusch: Pfeife lang
02: Geräusch: Bremsenquietschen aus
03: Rauchgenerator
04: Zylinderdampf
05: Geräusch: Kohle schaufeln
06: Geräusch: Betriebsgeräusch per Zufall
07: Führerstandsbeleuchtung
08: Sound an/aus
09: ABV, aus
10: Zahnradtriebwerk in der Fahrt
11: Zahnradtriebwerk in Stand
12: Rangierlicht doppel A
13: Feuerschein - Feuerbüchse
14: Geräusch: Pfeife kurz
15: Geräusch: Schaffnerpfiff
16: Geräusch: Saugluftbremse mit Dampf
17: Geräusch: Bahnhofsansage
18: Geräusch: Kohle fassen
19: Geräusch: Wasser fassen
20: Geräusch: Glocke (schweizer Bahnübergang)
21: Geräusch: Sanden
22: Geräusch: Injektor
23: Geräusch: Dampf ablassen
24: Geräusch: Betriebsgeräusch (mit Zahnstange)
25: Reduzierung der Zylinderschläge
26: Geräusch: Führerstandstüre auf/zu
27: Geräusch: Wasserstandsanzeige spülen
28: Geräusch: Schmierpumpe
29: Geräusch: Rauchkammertüre öffnen / schliessen
30: Geräusch: Sicherheitsventil

DCC-BEFEHLSDECODIERUNG:
(GEMÄSS RAILCOMMUNITY, RCN-210 BIS RCN-213, AUSGABE DEZ. 2019)

 Adresse    Adresse    Funktion    Beschreibung
 ---------  ---------  ---------   ------------
 0000-0000                       = Broadcastadresse
 1111-1111                       = Leerlauf/Idle
 0AAA-AAAA                       = Fahrzeugdecoder Kurze Adresse 0-127
 10AA-AAAA  1AAA-DAAR            = Einfache Zubehördecoder 11b-Adresse inkl. Funktion D=Aktivieren R=WeicheSchalten
 10AA-AAAA  0AAA-0AA1  DDDD-DDDD = Erweiterter Zubehördecoder (Wird hier nicht verwendet!)
 11AA-AAAA  AAAA-AAAA            = Fahrzeugdecoder Lange Adresse 0-10239 (Wird hier nicht verwendet!)

 Fahrzeugdecoder Befehlscodierung Auszug (dargestellt mit kurzer Lokadresse)
 ---------------------------------------------------------------------------
 Adresse    Fkt/Adr    Funktion    Beschreibung
 ---------  ---------  ---------   ------------
 0AAA-AAAA  0000-0000            = Decoder-Reset
 0AAA-AAAA  0000-0001            = Decoder-Hardreset
 0AAA-AAAA  0001-XXXX            = Mehrfachtraktion
 0AAA-AAAA  0011-1100  RGGG-GGGG - DDDD-DDDD {DDDD-DDDD {DDDD-DDDD {DDDD-DDDD} = Geschwindigkeit,Richtung,Funktion
 0AAA-AAAA  0001-1111  RGGG-GGGG = 128 Geschwindigkeitsstufen-Befehl
 0AAA-AAAA  01RG-GGGG            = Basis Geschwindigkeits- und Richtungsbefehl
 0AAA-AAAA  100X-XXXX            = Funktionssteuerung F0-F4
 0AAA-AAAA  1010-XXXX            = Funktionssteuerung F9-F12
 0AAA-AAAA  1011-XXXX            = Funktionssteuerung F5-F8
 0AAA-AAAA  1101-FFFF  DDDD-DDDD = Funktionssteuerung F13-F68
 0AAA-AAAA  111X-XXXX            = Konfigurationsvariablen-Zugriffsbefehl

 DCC-Befehlsdecodierung (Von dieser Basisstation unterstuetzte Befehle sind ausschliesslich 2 Byte lang!)
 Adresse    Funktion    Beschr.  Adressmaske    Adresstest      Funktionsmaske Funktionstest
 ---------  ---------   -------  -------------- --------------  -------------- --------------
 0AAA-AAAA  01RG-GGGG = Speed:   1000-0000=0x80 0000-0000=0x00  1100-0000=0xC0 0100-0000=0x40
 0AAA-AAAA  100X-XXXX = F0-F4:   1000-0000=0x80 0000-0000=0x00  1110-0000=0xE0 1000-0000=0x80
 0AAA-AAAA  1010-XXXX = F9-F12:  1000-0000=0x80 0000-0000=0x00  1111-0000=0xF0 0100-0000=0xA0
 0AAA-AAAA  1011-XXXX = F5-F8:   1000-0000=0x80 0000-0000=0x00  1111-0000=0xF0 0100-0000=0xB0
 10AA-AAAA  1AAA-DAAR = Zubehör: 1100-0000=0xC0 1000-0000=0x80  1000-0000=0x80 1000-0000=0x80

 DCC-Adresse 0x03: Zahnraddampflok HG4/4 (0000-0011)  Lokadresse
 DCC-Adresse 0x78: Strom aus  (120)      (0111-1000)  Funktion 0x80
 DCC-Adresse 0x80: Weiche (Zubehör)      (1000-0000)  Funktion 0xF8 und 0xF9       (z.Zt.ungenutzt!)
 DCC-Adresse 0x63: Lichsignal (99)       (0110-0011)  Funktion 0x80=Rot 0x90=Gruen (z.Zt.ungenutzt!)

 Fahrtrichtung und Geschwindigkeit: 01RG-GGGG G-GGGG : 2xStopp, 2xNothalt, 28 Fahrstufen. R=1 → Vorwärts
 Funktion 0-4  : 100L-0V0S : L=LED/F0/Licht&Grundsound, V=VAP/Dampf, S=SIG/F1/Lokpfeife
 Funktion 5-8  : 1011-WCB0 : B=Betriebsgeräusch/F6, C=CAB/F7/Kabinenlicht, W=WAV/F8/Sound
 Funktion 9-12 : 1010-00C0 : C=COG/F10/Zahnradimitation

DIE FAHRSTUFENTABELLE:
Fahrtrichtung und Geschwindigkeit 01RG-GGGG R=0:Rückwärts R=1:Vorwärts
Geschwindigkeit Bitbreite=5Bit
Zu beachten: Aus B4 B3 B2 B1 B0 wird B0 B4 B3 B2 B1
Falls das Bit-1 in der Konfigurationsvariable 29 gelöscht ist, wird das 4. Speed-Bit ignoriert und es stehen nur die ersten 14 Fahrstufen zur Verfügung.
 Nr.          01RG-GGGG HEX   Fahrstufe
 --- ------   --------- ----  ---------
     4 3210   01R0-4321 
 00: 1-1111 → 0101-1111 0x5F  S28 Rueckwärts
 01: 1-1110 → 0100-1111 0x4F  S27 ...
 02: 1-1101 → 0101-1110 0x5E  S26
 03: 1-1100 → 0100-1110 0x4E  S25
 04: 1-1011 → 0101-1101 0x5D  S24
 05: 1-1010 → 0100-1101 0x4D  S23
 06: 1-1001 → 0101-1100 0x5C  S22
 07: 1-1000 → 0100-1100 0x4C  S21
 08: 1-0111 → 0101-1011 0x5B  S20
 09: 1-0110 → 0100-1011 0x4B  S19
 10: 1-0101 → 0101-1010 0x5A  S18
 11: 1-0100 → 0100-1010 0x4A  S17
 12: 1-0011 → 0101-1001 0x59  S16
 13: 1-0010 → 0100-1001 0x49  S15
 14: 1-0001 → 0101-1000 0x58  S14
 15: 1-0000 → 0100-1000 0x48  S13
 16: 0-1111 → 0101-0111 0x57  S12
 17: 0-1110 → 0100-0111 0x47  S11
 18: 0-1101 → 0101-0110 0x56  S10
 19: 0-1100 → 0100-0110 0x46  S9
 20: 0-1011 → 0101-0101 0x55  S8
 21: 0-1010 → 0100-0101 0x45  S7
 22: 0-1001 → 0101-0100 0x54  S6
 23: 0-1000 → 0100-0100 0x44  S5
 24: 0-0111 → 0101-0011 0x53  S4
 25: 0-0110 → 0100-0011 0x43  S3
 26: 0-0101 → 0101-0010 0x52  S2 ...
 27: 0-0100 → 0100-0010 0x42  S1 Rueckwärts
     0-0011 → 0101-0001 0x11  Nothalt (Nicht implementiert)
     0-0010 → 0100-0001 0x01  Nothalt (Nicht implementiert)
     0-0001 → 0101-0000 0x10  0 Halt  (Nicht implementiert
 28: 0-0000 → 0100-0000 0x40  0 Halt  Fahrstufe 28 (-0)
 29: 0-0000 → 0110-0000 0x60  0 Halt  Fahrstufe 29 (+0)
     0-0001 → 0111-0000 0x10  0 Halt  (Nicht implementiert)
     0-0010 → 0110-0001 0x01  Nothalt (Nicht implementiert)
     0-0011 → 0111-0001 0x11  Nothalt (Nicht implementiert)
 30: 0-0100 → 0110-0010 0x62  S1 Vorwärts
 31: 0-0101 → 0111-0010 0x72  S2 ...
 32: 0-0110 → 0110-0011 0x63  S3
 33: 0-0111 → 0111-0011 0x73  S4
 34: 0-1000 → 0110-0100 0x64  S5
 35: 0-1001 → 0111-0100 0x74  S6
 36: 0-1010 → 0110-0101 0x65  S7
 37: 0-1011 → 0111-0101 0x75  S8
 38: 0-1100 → 0110-0110 0x66  S9
 39: 0-1101 → 0111-0110 0x76  S10
 40: 0-1110 → 0110-0111 0x67  S11
 41: 0-1111 → 0111-0111 0x77  S12
 42: 1-0000 → 0110-1000 0x68  S13
 43: 1-0001 → 0111-1000 0x78  S14
 44: 1-0010 → 0110-1001 0x69  S15
 45: 1-0011 → 0111-1001 0x79  S16
 46: 1-0100 → 0110-1010 0x6A  S17
 47: 1-0101 → 0111-1010 0x7A  S18
 48: 1-0110 → 0110-1011 0x6B  S19
 49: 1-0111 → 0111-1011 0x7B  S20
 50: 1-1000 → 0110-1100 0x6C  S21
 51: 1-1001 → 0111-1100 0x7C  S22
 52: 1-1010 → 0110-1101 0x6D  S23
 53: 1-1011 → 0111-1101 0x7D  S24
 54: 1-1100 → 0110-1110 0x6E  S25
 55: 1-1101 → 0111-1110 0x7E  S26
 56: 1-1110 → 0110-1111 0x6F  S27 ...
 57: 1-1111 → 0111-1111 0x7F  S28 Vorwärts

ZUSAMMENSTELLUNG EINES DCC-PAKETS:
Gleissignal LOG-1 bedeutet: 58usec Out1=24V und Out2=0V gefolgt von 58usec Out1=0V und Out2=24V
Gleissignal LOG-0 bedeutet: 116usec Out1=24V und Out2=0V gefolgt von 116usec Out1=0V und Out2=24V
Ein DCC-Paket setzt sich wie folgt zusammen:
 17b Synchronbit     11111111111111111  Gefolgt von...
  1b Delimiter       0                  ...gefolgt von...
  8b DCC-Adresse #3  0000 0011          ...gefolgt von...
  1b Delimiter       0                  ...gefolgt von...
  8b DCC-Funktion    FFFF FFFF          ...gefolgt von...
  1b Delimiter       0                  ...gefolgt von...
  8b Parity          PPPP PPPP          ...gefolgt von...
  1b Endbit          1                  ...danach Wiederholung des kompletten DCC-Pakets
  Total: 45b; 0..44; Paketdauer somit ca.: 11msec

DIE DCC-SIGNALAUFBEREITUNG MIT EINER H-BRÜCKE:
 WHT und Pinbelegung der L6203 H-Bridge
 --------------------------------------
 EN  IN1  IN2  SOURCE1  SOURCE2  SINK1  SINK2
 --  ---  ---  -------  -------  -----  -----
  H   L    L                       ON     ON
  H   L    H               ON      ON
  H   H    L      ON                      ON
  H   H    H      ON       ON
  l   X    X     OFF      OFF     OFF    OFF
  Pin01: OUT2
  Pin02: Vs (+18V)
  Pin03: OUT1
  Pin04: BOOT1 (15nF → OUT1)
  Pin05: IN1
  Pin06: GND
  Pin07: IN2
  Pin08: BOOT2 (15nF → OUT2)
  Pin09: Vref  (220nF → GND)
  Pin10: SENSE
  Pin11: EN

DIE ENERGIEVERSORGUNG MIT LIPO-AKKU:
Die Basisstation bzw. die Modellbahn wird hier durch einen LiPo betrieben. Märklin/LGB empfiehlt 22V Versorgungsspannung. Bei darunterliegenden Betriebsspannungen wie z.B. 18V fehlt den Fahrmotoren bei hoher Last oder im Zahnradbetrieb und engen Kurven die nötige Kraft. Wie Versuche zeigten, kommt die Zahnradlok auch mit höheren Spannung 22V..25V gut zurecht.
LiPo-Wahl: 4200mAh, 6S1P (6 Zellen)
Zellenspannung: 3.0V .. 4.2V (Min: 3V, Nominal: 3.67V, Storage:3.85V, Max: 4.2V)
Gesamtspannung: 18V .. 25.1V (Min: 18V, Nominal: 22.02V, Storage:23.1V, Max: 25.2V)

SPANNUNGS- UND STROMÜBERWACHUNG MIT ADAFRUIT INA260:
(In der aktuellen Basisstation nicht verwendet!)
Pin1: Vcc
Pin2: GND
Pin3: SCL (I2C-Clock, I2C-Adresse:0x40)
Pin4: SDA (I2C-Data)
Pin5: Alert, Interrupt-Pin
Pin6: VBus (VB-Jumper offen!)
Pin7: Vin+ (Strommesseingang)
Pin8: Vin- (Strommessausgang)
Kommandos:
ina260.setAlertLatch(INA260_ALERT_LATCH_ENABLED); → Verriegelt ALERT, bis mask/enable-Register gelesen wird.
ina260.getAlertLatch(); → Damit liest man das mask/enable-Register und setzt damit das Alert-Flag zurueck
ina260.setAlertLatch(INA260_ALERT_LATCH_TRANSPARENT); → Alert-Flag wird bei Unterschreiten von AlertLimit zurückgesetzt
Einstellungen des Spannungs/Strommessgeraet INA260:
INA260_TIME_140_us INA260_TIME_204_us INA260_TIME_332_us INA260_TIME_558_us
INA260_TIME_1_1_ms INA260_TIME_2_116_ms INA260_TIME_4_156_ms INA260_TIME_8_244_ms
INA260_COUNT_1 INA260_COUNT_4 INA260_COUNT_16 INA260_COUNT_64
INA260_COUNT_128 INA260_COUNT_256 INA260_COUNT_512 INA260_COUNT_1024
1.Bsp.: U→140us, I→588us, Average→4 ergibt: Update alle 2.912ms
2.Bsp.: U→140us, I→2.166ms, Average→16 ergibt: Update alle 36.9ms
3.Bsp.: U→140us, I→1.1ms, Average→16 ergibt: Update alle 19.8ms
4.Bsp.: U→140us, I→1.1ms, Average→4 ergibt: Update alle 5.2ms
5.Bsp.: U→140us, I→4.156ms, Average→16 ergibt: Update alle 68.74ms

ARDUINO SOURCECODE DCC-PROJEKT (BASISSTATION)

//======================================================================
// Titel: Basisstation DCC
// Autor: Juerg Arnold, CH-Meilen
// Datum: 14. Mai 2022
// Zweck: Die Basisstation empfängt vom Funkhandregler
//        die Fahr- und Umschaltkommandos und setzt sie in 
//        ein DCC-Gleissignal um.       
//======================================================================

#include <SPI.h>      //Fuer RFM69
#include <RH_RF69.h>  //Fuer RFM69

#define LED_RED 5     // Rote Power-LED Pin Nr.5
                      // Rote LED on:     Geraet in Betrieb aber mit tiefer Batteriespannung
                      // Rote LED off:    Falls gruene LED ebenfalls off -> Geraet ausgeschaltet
                      // Rote LED blinkt: RF69 oder INA260 hat einen Fehler
#define LED_GREEN 6   // Gruene Power-LED Pin Nr.6
                      // Gruene LED on:   Geraet in Betrieb, Batteriespannung ok.
                      // Gruene LED off:  Falls rote LED ebenfalls off -> Geraet ausgeschaltet                            

#define VBATPIN A2    // Spannungsteiler 10k zu 100k gegen positive Akkuspannung Pin Nr. A1
#define CURRPIN A1    // 82mOhm Messwiderstand fuer Strommessung

#define L6203_IN1 12  //L6203 H-Bridge IN1  //L6203 H-Bridge IN1   Pin Nr.12
#define L6203_IN2 11  //L6203 H-Bridge IN2  //L6203 H-Bridge IN2   Pin Nr.11
#define L6203_EN  10  //L6203 H-Bridge EN   //L6203 H-Bridge ENABLE Pin Nr.10

//Definitionen Radio Setup
#define RF69_FREQ 434.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#define RadioTIMER    0xFFFFFF     //Time Setup
RH_RF69 rf69(RFM69_CS, RFM69_INT); // Singleton instance of the radio driver
enum remoteSTATUS{RadioOK, RadioInitNOK, RadioFreqNOK};

//Funkmodul -> Empfangsbytes vom Funkhandregeler: Addresse/AddresseRedundat/Befehl/BefehlRedundant
uint8_t DCC_RadioPacket[4] = {0x00,0x00,0x00,0x00};  
uint8_t DCC_RadioPacketLEN = 4;

uint8_t validDCCMessage = 0x00;  //Empfangenes und gueltiges DCC-Funktionsbyte
uint8_t validDCCAdress  = 0x00;  //Empfangenes und gueltiges DCC-Adressbyte

bool NOTSTOP = true;               //NOTSTOP-Flag
bool LokSoundOn = false;           //LokSound-Flag on/off
bool LokPfiffOn = false;           //LokPfiff-Flag on/off
uint8_t LokPfiffDCCAdress = 0x00;  //Lokpfiff nach Zeitablauf zuruecksetzen
unsigned long LokPfiffCounter = 0; //Lokpfiff Zeitablauf
uint8_t averageCURR = 0x00;        //Ueberschrittene Werte bei Strommessung
uint8_t cheksumCURR = 0x00;        //Anzahl ueberschrittene Werte im Zeitfenster

//Globale in der TCC1-Handler ISR verwendete volatile Variablen
#define DCC_HI  0x000ADF  //58uSec. DCC-Signal
enum stHBrideEnumType{UP, holdUP, downHI, downHOLD, downLOW};
volatile stHBrideEnumType stHBrideNEXT=UP;
volatile uint8_t signalHI=true;    //Flag ob Log0 oder Log1 / Start mit Log-1
volatile uint8_t bitCount=0;
volatile uint8_t charMSG=0x00;     //DCC-Speed oder Funktionsbefehl
volatile uint8_t charADR=0x00;     //DCC-Adresse
//Waehrend dem Senden des DCC-Pakets zwischen Bit 27 (Separator-Bit) und Bit 45 (Endbit)
//darf sich die vom Funkhandregler erhaltene DCC-Funktion und DCC-Adresse nicht aendern.


void setup() //SETUP-------------------------------------------------------------------
{ //Serial.begin(9600);
  //Serial.println("DEBUGGING...");
  
  //Digitale Inputs/Outputs initialisieren---------------------------------------------
  pinMode(L6203_IN1, OUTPUT);
  pinMode(L6203_IN2, OUTPUT);
  pinMode(L6203_EN, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  digitalWrite(L6203_IN1, LOW);  //H-Bruecke OFF
  digitalWrite(L6203_IN2, LOW);  //H-Bruecke OFF
  digitalWrite(L6203_EN, LOW);   //H-Bruecke OFF
  digitalWrite(LED_RED, LOW);    //LED_RED OFF
  digitalWrite(LED_GREEN, LOW);  //LED_GREEN OFF

  //Abschalten, wenn Akkuspannung zu tief:
  //LiPo6s: Min. Akkuspannung pro Zelle: 3.3V -> 6*3.3V=19.8V
  //LiPO6s: Abzueglich Schutzdiode: 19.8V-0.55V=19.25V 
  //LiPo6s: Spannungsteiler 1:10 -> 19.25/11=1.75
  //Zuzueglich Reserve: 1.968V -> 22.2V -> 3.7V/Zelle
  //10Bit-ADC -> 1024: D=1024/3.3*Umess
  //1.968V = 610
  if(analogRead(VBATPIN) < 610)
  { digitalWrite(L6203_EN, LOW);       //H-Bruecke ausschalten
    rf69.sleep();                      //Funkmodul ausschalten
    digitalWrite(LED_GREEN, LOW);    //Gruene LED ausschalten
    digitalWrite(LED_RED, LOW);      //Rote LED ausschalten
    while(true)                        //Akku muss geladen werden / RG-Blinklicht / Endlosschlaufe
    { digitalWrite(LED_RED, HIGH);   //Rote LED einschalten
      delay(500);
      digitalWrite(LED_RED, LOW);    //Rote LED ausschalten
      digitalWrite(LED_GREEN, HIGH); //Gruene LED einschalten
      delay(500);
      digitalWrite(LED_GREEN, LOW);  //Gruene LED ausschalten
    }
  }

  //Initialisierung des Funkbausteins RFM69--------------------------------------------
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);
  //Feather RFM69 TX Test!
  digitalWrite(RFM69_RST, HIGH); // Manueller Reset
  delay(10);
  digitalWrite(RFM69_RST, LOW);  // Manueller Reset
  delay(10);
  if (!rf69.init())    //RFM69 Funkbausteininitialisierung fehlgeschlagen -> RadioInitNOK
    while(true)
    { digitalWrite(LED_RED, HIGH); //Rotes Blinken mit 400mSec
      delay(400);
      digitalWrite(LED_RED, LOW);
      delay(400);
    }

  //---
  //RFM69 Funkbaustein erfolgreich initialisiert. Es werden folgende Werte eingestellt:
  //434.0MHz
  //Modulation GFSK_Rb250Fd250
  //+13dbM (fuer Low Power Module)
  if (!rf69.setFrequency(RF69_FREQ))  //setFrequency 434MHz fehlgeschlagen -> RadioFreqNOK
    while(true)
    { digitalWrite(LED_RED, HIGH); //Rotes Blinken mit 400mSec
      delay(400);
      digitalWrite(LED_RED, LOW);
      delay(400);
    }

  //---
  //Hinweis: Bei Verwendung eines High-Power RF69 wie z.B. RFM69HW, muss eine Sendeleistung
  //mit dem Flag ishighpowermodule=true gesetzt werden.
  //Sendeleistungsbereich: 14..20
  //Das zweite Argument muss fuer das Modul 69HCW True sein.
  rf69.setTxPower(20, true);
  //---
  //Encryption-Schluessel (Muss auf Sender und Empfaenger uebereinstimmen!
  uint8_t key[]={0x10,0x51,0xB9,0xC0,0x10,0x51,0xB9,0xC0,0x15,0x00,0x74,0x60,0x15,0x00,0x74,0x60};
  rf69.setEncryptionKey(key);
  //rf69.sleep(); //So koennte man das Funkmodul abschalten, um Batteriestrom zu sparen
  //Der Funkbaustein ist nun Sendebereit---

  //Initialisierung des Timers fuer die TCC1-Handler ISR-----------------------------------------------------------------------
  //***TCCx Timer-Setup for Feather M0 SAM D21
  //***Divide the 48MHz system clock by 1 = 48MHz. Set division on Generic Clock Generator (GCLK) 4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | GCLK_GENDIV_ID(4);
  while (GCLK->STATUS.bit.SYNCBUSY);
  //***Set the duty cycle to 50/50 HIGH/LOW. Enable GCLK 4. Set the clock source to 48MHz. Set clock source on GCLK 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(4);
  while (GCLK->STATUS.bit.SYNCBUSY); 
  //***Enable the generic clock on GCLK4. Feed the GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_ID_TCC0_TCC1;
  while (GCLK->STATUS.bit.SYNCBUSY);
  //***Set prescaler to 1, 48Mhz. Reload timer on next prescaler clock, use if prescaler DIV is more than 1
  //***TCC1->CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC | TCC_CTRLA_PRESCALER_DIV1;
  //***Set the Nested Vector Interrupt Controller (NVIC) priority for TCCx to 0 (highest)
  NVIC_SetPriority(TCC1_IRQn, 0);
  //***Connect TCCx to Nested Vector Interrupt Controller (NVIC)
  NVIC_EnableIRQ(TCC1_IRQn);
  //***Enable TCCx MC0-interrupt
  TCC1->INTENSET.reg = TCC_INTENSET_MC0;
  //***Setup normal frequency operation on TCCx
  TCC1->WAVE.reg |= TCC_WAVE_WAVEGEN_NFRQ;
  while (TCC1->SYNCBUSY.bit.WAVE);
  //***The timer frequency is calculated with the PER register:
  //***Timer-Frequency = Generic-Clock-Frequency / (Timer-Prescaler * (Value-PER-Register + 1))
  //***By default the counter counts up to the PER register value then overflows as it returns to 0.
  //***In normal frequency (NFRQ) mode the timer's output toggles each time the PER value is reached.
  //***Set the frequency of the timer to 0x000ADF by 48MHz and Prescale=1: TF=48MHz/(1*(0x000ADF+1))=17241.39Hz -> 58us 
  TCC1->PER.reg = DCC_HI;    //DCC_HI=0x000ADF -> 58uSec. DCC-Signal
  while(TCC1->SYNCBUSY.bit.PER);
  //***Set counter compare: The CC[0] Register can be set at values between 0 and PER, and can trigger
  //***interrupts at any point along the Timer-Cycle, whenever the timer's COUNT register matches the CC[0] value.
  TCC1->CC[0].reg = DCC_HI;  //DCC_HI=0x000ADF -> 58uSec. DCC-Signal
  while(TCC1->SYNCBUSY.bit.CC0);
  //***Enable timer TCCx
  TCC1->CTRLA.bit.ENABLE = 1;             
  while (TCC1->SYNCBUSY.bit.ENABLE);

  delay(100);                   //100ms warten
  digitalWrite(LED_GREEN, LOW); //Gruene LED ausschalten
  digitalWrite(LED_RED, HIGH);  //Rote LED einschalten
  digitalWrite(L6203_EN, LOW);  //H-Bruecke ausschalten
  NOTSTOP=true;                 //NOTSTOP aktiv -> Startsituation wird durch Funkhandregler aufgehoben
}

void loop() //-------------------------------------------------------------------------------------------------------------
{ 
  //A: Messen der Akkuspannung -> Sofortige Abschaltung wenn Akkuspannung zu tief (Sonst Akku-Beschaedigung!)
  //LiPo6s: Min. Akkuspannung pro Zelle: 3.3V -> 6*3.3V=19.8V
  //LiPO6s: Abzueglich Schutzdiode: 19.8V-0.55V=19.25V 
  //LiPo6s: Spannungsteiler 1:10 -> 19.25/11=1.75
  //10Bit-ADC -> 1024: D=1024/3.3*Umess
  //1.75V = 543
  if(analogRead(VBATPIN) < 543)
  { digitalWrite(L6203_EN, LOW);     //H-Bruecke ausschalten
    rf69.sleep();                    //Funkmodul ausschalten
    digitalWrite(LED_GREEN, LOW);    //Gruene LED ausschalten
    digitalWrite(LED_RED, LOW);      //Rote LED ausschalten
    while(true)                      //Akku muss geladen werden / RG-Blinklicht / Endlosschlaufe
    { digitalWrite(LED_RED, HIGH);   //Rote LED einschalten
      delay(500);
      digitalWrite(LED_RED, LOW);    //Rote LED ausschalten
      digitalWrite(LED_GREEN, HIGH); //Gruene LED einschalten
      delay(500);
      digitalWrite(LED_GREEN, LOW);  //Gruene LED ausschalten
    }
  }  //end-if Akkuspannung pruefen
 
  //B: Messen des Fahrstroms -> Durchschnittswert ueber 8 Messungen
  //Es werden jeweils 8 Messungen beruecksichtigt und wenn 6 davon ueber dem Limit, dann NOTSTOP
  //10Bit-ADC -> 1024: D=1024/3.3*Umess
  //Rmess=0.082Ohm -> Limit von 7.0Ampere -> 0.574Volt -> 178
  //Es gilt ein Zeitfenster von 8 Messungen als FIFO-Byte (averageCURR)
  //Jedes gesetzte Bit entspricht einer Messung groesser dem Limit
  //cheksumCURR addiert im Sinne einer Quersumme die gesetzten Bits (Ebenfalls FIFO)
  //cheksumCURR muss bei jedem aus averageCURR links herausfallendes gesetzte Bit um 1 reduziert werden
  //Zahlenbeispiel ueber mehrere Messloops:
  //t00, I>5A: averageCURR=0000-0001; cheksumCURR=0000-0001
  //t01, I>5A: averageCURR=0000-0011; cheksumCURR=0000-0011
  //t02, I<5A: averageCURR=0000-0110; cheksumCURR=0000-0011
  //t03, I>5A: averageCURR=0000-1101; cheksumCURR=0000-0111
  //t04, I<5A: averageCURR=0001-1010; cheksumCURR=0000-0111
  //t05, I>5A: averageCURR=0011-0100; cheksumCURR=0000-0111
  //t06, I>5A: averageCURR=0110-1001; cheksumCURR=0000-1111
  //t07, I<5A: averageCURR=1101-0010; cheksumCURR=0000-1111
  //t08, I<5A: averageCURR=1010-0100; cheksumCURR=0000-0111 -> Quersumme um 1 rediziert
  //t09, I<5A: averageCURR=0100-1000; cheksumCURR=0000-0011 -> Quersumme um 1 rediziert
  //t10, I<5A: averageCURR=1001-0000; cheksumCURR=0000-0011
  if(analogRead(CURRPIN) > 178)
  { if(averageCURR & 0x80)            //Wenn vorderstes Bit links gesetzt ist, dann...
      cheksumCURR = cheksumCURR >> 1; //Quersumme-1 weil linkes Bit nun aus dem Zeitfenster faellt
    averageCURR = averageCURR | 0x01; //Mehr als 5A gemessen. Erstes Bit rechts mit OR auf 1 setzen
    averageCURR = averageCURR << 1;   //Alle Bits um eine Stelle nach links veschieben
    cheksumCURR = cheksumCURR | 0x01; //Mehr als 5A gemessen. Erstes Bit rechts mit OR auf 1 setzen
    cheksumCURR = cheksumCURR << 1;   //Alle Bits um eine Stelle nach links veschieben
  }
  else
    averageCURR = averageCURR << 1;   //Alle Bits um eine Stelle nach links veschieben
                                      //Es rueckt automatisch eine 0 nach)

  //C: Reagieren auf Kurzschluss (Fahrstrom zu hoch), sonst Funkbefehl einlesen und verarbeiten                               
  if(cheksumCURR >= 0x1F)             //Fahrstrom zu hoch (Fuenfmal mehr als 5A gemessen)
  { digitalWrite(L6203_EN, LOW);      //H-Bruecke sofort ausschalten
    digitalWrite(LED_GREEN, LOW);     //Gruene LED aus
    digitalWrite(LED_RED, HIGH);      //Rote LED ein
    NOTSTOP=true;                     //NOTSTOP aktivieren
    averageCURR = 0x00;               //Alle Bits auf 0 setzen, Strommessung Reset
    cheksumCURR = 0x00;               //Alle Bits auf 0 setzen, Strommessung Reset
  }
  else  //Fahrstrom korrekt
  {
    //Funk-Datenpaket falls vorhanden einlesen
    if (rf69.waitAvailableTimeout(600))
      if (rf69.recv(DCC_RadioPacket, &DCC_RadioPacketLEN))
        if( (DCC_RadioPacket[2]==DCC_RadioPacket[0]) &&
            (DCC_RadioPacket[3]==DCC_RadioPacket[1])    )              //Keine Uebermittlungsfehler?
    { if(NOTSTOP) //NOTSTOP ist aktiv -> Pruefen auf NOTSTOP-Deaktivierung
      { if( (DCC_RadioPacket[0]==0x78) && (DCC_RadioPacket[1]==0x01) )   //Notstopp-Deaktivierung erhalten
        { NOTSTOP=false;                  //Status aendern
          digitalWrite(L6203_EN, HIGH);   //H-Bruecke einschalten
          digitalWrite(LED_RED, LOW);     //Rote LED ausschalten
          digitalWrite(LED_GREEN, HIGH);  //Gruene LED einschalten
          averageCURR = 0x00;             //Strommessung Reset
          cheksumCURR = 0x00;             //Strommessung Reset
          LokInit();
        }
      }  //end-if Pruefen auf NOTSTOP-Deaktivierung
      else  //Zurzeit kein NOTSTOP aktiv
      { if( (DCC_RadioPacket[0]==0x78) && (DCC_RadioPacket[1]==0x80) ) //NOTSTOP-Befehl erhalten
			  { NOTSTOP=true;                                                //Status aendern
			    digitalWrite(L6203_EN, LOW);                                 //H-Bruecke sofort ausschalten
          digitalWrite(LED_GREEN, LOW);                                //Gruene LED aus
          digitalWrite(LED_RED, HIGH);                                 //Rote LED ein
			  }
        else                                                           //Normaler Befehl erhalten
        { switch(DCC_RadioPacket[1])
          {
            case 0x91 : //LokPfiff-Befehl erhalten
                        LokPfiffDCCAdress=DCC_RadioPacket[0];  //Lok-Pfiff spaeter korrekt ausschalten
                        LokPfiffCounter=millis();              //LokPfiff-Dauer starten
                        LokPfiffOn=true;                       //Lokpfiff-Flag setzen
                        validDCCAdress=DCC_RadioPacket[0];     //DCC-Adresse umkopieren
                        validDCCMessage=DCC_RadioPacket[1];    //DCC-Funktion umkopieren
                        break;
            case 0xBA : //LokSound-Befehl erhalten
                        if(LokSoundOn)
                        { LokSoundOn=false;
                          validDCCAdress=DCC_RadioPacket[0];  //DCC-Adresse umkopieren
                          validDCCMessage=0xB2;               //DCC-Funktion LokSound abschalten
                        }
                        else
                        { LokSoundOn=true;
                          validDCCAdress=DCC_RadioPacket[0];  //DCC-Adresse umkopieren
                          validDCCMessage=DCC_RadioPacket[1]; //DCC-Funktion umkopieren
                        }
                        break;
            default:    validDCCAdress=DCC_RadioPacket[0];    //DCC-Adresse umkopieren
                        validDCCMessage=DCC_RadioPacket[1];   //DCC-Funktion umkopieren
                        break;
          } //end-switch
        } //end-if-else kein NOTSTOP-Paket erhalten
      } //end-if-else kein NOTSTOP aktiv
    }  //end-if Gueltiges Paket erhalten
    if(LokPfiffOn)
      if((millis() - LokPfiffCounter) > 1000)  //Lokpfiffdauer abgelaufen
      { LokPfiffOn=false;
        validDCCAdress=LokPfiffDCCAdress;      //DCC-Adresse der Lok mit LokPfiff-Ausloesung 
        validDCCMessage=0x90;                  //DCC-Funktion LokPfiff abschalten
      }
  }  //end-if-else Fahrstrom korrekt
  delay(300);
  DCC_RadioPacket[0]=0x00;  //Zwischenzeitlich eingetroffene Funkbefehle ignorieren
  DCC_RadioPacket[1]=0x00;
}

void LokInit(void) //Lok vor der Fahrt initialisieren----------------------------------------------------
//HEX-BIN -> A=1010 B=1011 C=1100 D=1101 E=1110 F=1111
//F5-F8: 1011-WCB0   W=WAV/F8/Sound, C=CAB/F7/Kabinenlicht, B=Betriebsgeraeusch/F6 (F5=KohleSchaufeln)
//F0-F4: 100L-0V0S   L=LED/F0/Licht&Grundsound, V=VAP/Dampf, S=SIG/F1/Lokpfeife (F4=Zylinderdampf, F2=Bremsquitschen)
{ validDCCAdress=0x03;          //LokAdresse 3
  validDCCMessage=0x00;         //Decoder Reset
  delay(4000);
  validDCCMessage=0x90;         //Licht&Grundsound ein
  delay(1000);
  validDCCMessage=0xBA;         //Betriebsgeraeusch ein
  delay(1000);
  validDCCMessage=0x60;         //Fahrstufe Halt
  delay(1000);
  validDCCMessage=0x90;         //Licht&Grundsound ein wiederholen
  delay(300);
  DCC_RadioPacket[0]=0x00;
  DCC_RadioPacket[1]=0x00;
}

void TCC1_Handler() //TCC1-Handler ISR--------------------------------------------------------------
{ //***Per Funk erhalten: 1 Byte (Speed/F0-F4/F5-F8/F9-F12)
  //***Das DCC-Paket setzt sich zusammen aus:
  //***17b Synchronbit     11111111111111111
  //*** 1b Delimiter       0
  //*** 8b DCC-Adresse #3  0000 0011
  //*** 1b Delimiter       0
  //*** 8b DCC-Funktion    FFFF FFFF
  //*** 1b Delimiter       0
  //*** 8b Parity          PPPP PPPP
  //*** 1b Endbit          1
  //***Total: 45b; 0..44; Paketdauer ca.: 11msec)
  if (TCC1->INTFLAG.bit.MC0)   // Test if an MC0 interrupt has occured
  { TCC1->INTFLAG.bit.MC0=1;   // Clear the interrupt flag
    switch(stHBrideNEXT)
    { case UP:       digitalWrite(L6203_IN1, HIGH);  //DCC-HI & DCCLO  (H-Bruecke ON Source1 -> Sink2)
                     digitalWrite(L6203_IN2, LOW);   //DCC-HI & DCCLO  (H-Bruecke ON Source1 -> Sink2)
                     if(signalHI)                    //DCC-HI
                       stHBrideNEXT=downHI;
                     else                            //DCC-LO
                       stHBrideNEXT=holdUP;       
                     break;  
      case holdUP:   stHBrideNEXT=downLOW;           //DCC-HI
                     break;
      case downLOW:  digitalWrite(L6203_IN1, LOW);   //DCC-LO  (H-Bruecke ON Source2 -> Sink1)
                     digitalWrite(L6203_IN2, HIGH);  //DCC-LO  (H-Bruecke ON Source2 -> Sink1)
                     stHBrideNEXT=downHOLD;
                     break;
      case downHI:   digitalWrite(L6203_IN1, LOW);   //DCC-HI  (H-Bruecke ON Source2 -> Sink1)
                     digitalWrite(L6203_IN2, HIGH);  //DCC-HI  (H-Bruecke ON Source2 -> Sink1)
      case downHOLD: bitCount++;                     //DCC-HI & DCCLO
                     if(bitCount>45)
                       bitCount=0;
                     switch(bitCount)
                     { //***Synchronbits:
                       case  0: case  1: case  2: case  3: case  4: case  5: case  6: case  7:
                       case  8: case  9: case 10: case 11: case 12: case 13: case 14: case 15:
                       case 16: case 17: signalHI=true; break;
                       //Separator-Bit (immer Log-0)
                       case 18: case 27: case 36: signalHI=false; break;
                       //***Adressbyte
                       case 19: signalHI=charADR & 0x80;  break;  //DCC-Adresse
                       case 20: signalHI=charADR & 0x40;  break;  //DCC-Adresse
                       case 21: signalHI=charADR & 0x20;  break;  //DCC-Adresse
                       case 22: signalHI=charADR & 0x10;  break;  //DCC-Adresse
                       case 23: signalHI=charADR & 0x08;  break;  //DCC-Adresse
                       case 24: signalHI=charADR & 0x04;  break;  //DCC-Adresse
                       case 25: signalHI=charADR & 0x02;  break;  //DCC-Adresse
                       case 26: signalHI=charADR & 0x01;  break;  //DCC-Adresse
                       //***DCC-Message
                       //***Speed  -> 01RG-GGGG : Richtung/Geschwindigkeit
                       //***F0-F4  -> 100L-000S : L=LED/F0/Stirnlicht, S=SIG/F1
                       //***F5-F8  -> 1011-WC10 : C:CAB/F7/Kabinenlicht, W:WAV/F8/Sound
                       //***F9-F12 -> 1010-00C0 : C=COG/F10/Zahnradimitation
                       case 28: signalHI=charMSG & 0x80;  break;  //DCC-Message
                       case 29: signalHI=charMSG & 0x40;  break;  //DCC-Message
                       case 30: signalHI=charMSG & 0x20;  break;  //DCC-Message
                       case 31: signalHI=charMSG & 0x10;  break;  //DCC-Message
                       case 32: signalHI=charMSG & 0x08;  break;  //DCC-Message
                       case 33: signalHI=charMSG & 0x04;  break;  //DCC-Message
                       case 34: signalHI=charMSG & 0x02;  break;  //DCC-Message
                       case 35: signalHI=charMSG & 0x01;  break;  //DCC-Message
                       //***Paritaetsberechnung: XOR-Parity von Adressbyte DCC-Messagebyte
                       case 37: signalHI=(charMSG & 0x80)^(charADR & 0x80); break;  //XOR-Parity berechnen
                       case 38: signalHI=(charMSG & 0x40)^(charADR & 0x40); break;  //XOR-Parity berechnen
                       case 39: signalHI=(charMSG & 0x20)^(charADR & 0x20); break;  //XOR-Parity berechnen
                       case 40: signalHI=(charMSG & 0x10)^(charADR & 0x10); break;  //XOR-Parity berechnen
                       case 41: signalHI=(charMSG & 0x08)^(charADR & 0x08); break;  //XOR-Parity berechnen
                       case 42: signalHI=(charMSG & 0x04)^(charADR & 0x04); break;  //XOR-Parity berechnen 
                       case 43: signalHI=(charMSG & 0x02)^(charADR & 0x02); break;  //XOR-Parity berechnen
                       case 44: signalHI=(charMSG & 0x01)^(charADR & 0x01); break;  //XOR-Parity berechnen
                       case 45: signalHI=true;                    //Endbit und neue gueltige DCC-Adresse/Funktion lesen
                                charMSG=validDCCMessage;
                                charADR=validDCCAdress;
                                break; 
                     } //End-of-Switch
                     stHBrideNEXT = UP;        
    }
  }
}

ARDUINO SOURCECODE DCC-PROJEKT (FERNSTEUERUNG)

//======================================================================
// Titel: Funkhandregler
// Autor: Juerg Arnold, CH-Meilen
// Datum: 14. Mai 2022
// Zweck: Der Funkhandregler nimmt die Befehle vom Benutzer 
//        entgegen und schickt sie an die Basisstation.
//======================================================================

#include <SPI.h>      //Fuer RFM69
#include <RH_RF69.h>  //Fuer RFM69

//Definitionen Radio Setup
#define RF69_FREQ 434.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
RH_RF69 rf69(RFM69_CS, RFM69_INT); // Singleton-Instanz des Funkmodultreibers

#define LOK_ADRESS 0x03    //Fixe DCC-Lokadresse Nr. 3

#define SpeedPT A1         //0: Potentiometer Speed
#define BTN_STOP 6         //1: Button1 (NotStop)
#define BTN_FAHRT 10       //2: Button2 (Fahrtrichtung)

#define BTN_AUX1 11        //4: Button4 (Zurzeit belegt mit Lokpfiff)
#define BTN_AUX2 5         //3: Button3 (Zurzeit belegt mit Loksound on/off)

#define LED_BuiltIn 13     //Eingebaute rote LED
#define LED_Vorwaerts A4   //Rote LED Fahrt Vorwaerts (Rechts neben Poti)
#define LED_Rueckwaerts A2 //Rote LED Fahrt Rueckwaerts (Mitte)
#define LED_AUX A3         //Rote LED Links

#define DT_Out 400         //Debouncing-TimeOut zum Tasten entprellen

volatile unsigned long B_STOP_DT=0;  //Button STOP Debouncing-Timer
volatile unsigned long B_FAHRT_DT=0; //Button STOP Debouncing-Timer
volatile unsigned long B_AUX1_DT=0;  //Button AUX1 DebouncingTimer
volatile unsigned long B_AUX2_DT=0;  //Button AUX2 DebouncingTimer
volatile bool NOTSTOP = false;       //NOTSTOP-Taste gerdueckt
volatile uint8_t B_STATUS = 0;       //Button-Nr. des zuletzt gedrueckten Buttons

//Geschwindigkeitswert in HEX gemaess Fahrstufentabelle
const uint8_t SpeedTable[2][29] =
{ {0x40,0x42,0x52,0x43,0x53,0x44,0x54,0x45,0x55,0x46,0x56,0x47,0x57,0x48,0x58,    //Rueckwaerts
        0x49,0x59,0x4A,0x5A,0x4B,0x5B,0x4C,0x5C,0x4D,0x5D,0x4E,0x5E,0x4F,0x5F},
  {0x60,0x62,0x72,0x63,0x73,0x64,0x74,0x65,0x75,0x66,0x76,0x67,0x77,0x68,0x78,    //Vorwaerts
        0x69,0x79,0x6A,0x7A,0x6B,0x7B,0x6C,0x7C,0x6D,0x7D,0x6E,0x7E,0x6F,0x7F} };

uint8_t DCC_RadioPacket[4] = {0x00,0x00,0x00,0x00}; //Funkmodul -> Sendebytes zur Basisstation
uint8_t DCC_RadioPacketLEN = 4;

uint16_t ADC10BitValue = 0;        //0..1023 vom AD/Wandler (Potentiometer, Akkuspannung)
uint16_t SPEEDlevel = 0;           //Fahrtsufe 0..28
bool     Vorwaerts = true;         //Fahrtrichtung Vorwaerts oder Rueckwaerts
uint8_t  counter = 0;              //Counter-Variable


//***SETUP****************************************************************************************
void setup()
{ //Serial.begin(9600);
  //Serial.println("ONLY FOR DEBUGGING");

  // Digitale Inputs/Outputs initialisieren
  pinMode(BTN_STOP, INPUT_PULLUP);
  pinMode(BTN_FAHRT, INPUT_PULLUP);
  pinMode(BTN_AUX2, INPUT_PULLUP);  
  pinMode(BTN_AUX1, INPUT_PULLUP);
  pinMode(LED_Vorwaerts, OUTPUT);
  pinMode(LED_Rueckwaerts, OUTPUT);
  pinMode(LED_AUX, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(BTN_STOP), BTN_STOP_ISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BTN_FAHRT), BTN_FAHRT_ISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BTN_AUX2), BTN_AUX2_ISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BTN_AUX1), BTN_AUX1_ISR, CHANGE);

  //Akkuspannung messen und auf Entscheid warten, ob geladen oder gefahren werden soll
  ADC10BitValue = analogRead(A7); //Aktueller AD-Wert der Batteriemessung
  counter = 0;
  B_STATUS=0;
  while(B_STATUS==0)
    while( (counter<10) && (B_STATUS==0) )
    { digitalWrite(LED_AUX, HIGH);
      if(ADC10BitValue>540) digitalWrite(LED_Rueckwaerts, HIGH); //>3.48Volt
      else digitalWrite(LED_Rueckwaerts, LOW);
      if(ADC10BitValue>606) digitalWrite(LED_Vorwaerts, HIGH);   //>3.91Volt
      else digitalWrite(LED_Vorwaerts, LOW);
      delay(1000);
      digitalWrite(LED_Vorwaerts, LOW);
      digitalWrite(LED_Rueckwaerts, LOW);
      digitalWrite(LED_AUX, LOW);
      delay(66);
      digitalWrite(LED_Vorwaerts, HIGH);
      delay(66);
      digitalWrite(LED_Vorwaerts, LOW);
      delay(66);
      digitalWrite(LED_Vorwaerts, HIGH);
      delay(66);
      digitalWrite(LED_Vorwaerts, LOW);
      delay(66);
      digitalWrite(LED_Vorwaerts, HIGH);
      counter++;
    }

  //Initialisierung des Funkbausteins RFM69. Funk abschalten mit: rf69.sleep();
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);
  //Feather RFM69 TX Test!
  digitalWrite(RFM69_RST, HIGH); delay(10); //Manueller Reset
  digitalWrite(RFM69_RST, LOW);  delay(10); //Manueller Reset
  if (!rf69.init()) //RFM69 Funkbausteininitialisierung fehlgeschlagen
    while(true) RadioErrorLED();
  //RFM69-Funkbaustein-Init OK! Konfiguration: 434.0MHz/Modulation-GFSK_Rb250Fd250/+13dbM LowPowerModule
  if (!rf69.setFrequency(RF69_FREQ))  //setFrequency 434MHz fehlgeschlagen
    while(true) RadioErrorLED();
  //Bei High-Power RF69 (RFM69HW) -> Sendeleistung mit ishighpowermodule=true setzen!
  //Sendeleistungsbereich: 14..20 Zweites Argument muss fuer Modul 69HCW True sein.
  rf69.setTxPower(20, true);
  //Encryption-Schluessel -> Uebereinstimmung Sender/Empfaenger!
  uint8_t key[]={0x10,0x51,0xB9,0xC0,0x10,0x51,0xB9,0xC0,0x15,0x00,0x74,0x60,0x15,0x00,0x74,0x60};
  rf69.setEncryptionKey(key); //Funkmodul ist nun sendebereit!

  //NOTSTOP-Deaktivierungsbefehl an Basisstation
  digitalWrite(LED_Rueckwaerts, LOW);
  digitalWrite(LED_AUX, LOW);
  DCC_RadioPacket[0]=0x78;               //DCCAdressByte NOTSTOP
  DCC_RadioPacket[1]=0x01;               //DCCFunctionByte NOTSTOP Deaktivieren
  DCC_RadioPacket[2]=DCC_RadioPacket[0]; //Fehlererkennung Basisstation
  DCC_RadioPacket[3]=DCC_RadioPacket[1]; //Fehlererkennung Basisstation
  for(counter=0; counter<10; counter++)  //Den Befehl 10x schicken
  { digitalWrite(LED_Vorwaerts, HIGH);
    rf69.send(DCC_RadioPacket, DCC_RadioPacketLEN); 
    rf69.waitPacketSent();
    delay(100);
    digitalWrite(LED_Vorwaerts, LOW);
    delay(100);
  }

  digitalWrite(LED_BuiltIn, HIGH);
  digitalWrite(LED_Vorwaerts, HIGH);
  digitalWrite(LED_Rueckwaerts, LOW);
  digitalWrite(LED_AUX, LOW);
  Vorwaerts = true;  //Fahrtrichtung Vorwaerts
  NOTSTOP=false;
  B_STATUS=0;
}

void loop()
{ switch(B_STATUS)
  {
    case 0: //Geschwindigkeitsbefehl aufbereiten
            ADC10BitValue=analogRead(SpeedPT);
            if(ADC10BitValue<9) SPEEDlevel=0;
            else SPEEDlevel=(ADC10BitValue-9)/35+1;
            DCC_RadioPacket[0]=LOK_ADRESS;                              //DCCAdressByte Lokadresse 3
            if(Vorwaerts) DCC_RadioPacket[1]=SpeedTable[1][SPEEDlevel]; //Vorwaerts=SpeedTable[1]
            else DCC_RadioPacket[1]=SpeedTable[0][SPEEDlevel];          //Rueckwaerts=SpeedTable[0]
            break;

    case 1: //AUX1 (Lokpfiff) aufbereiten
            DCC_RadioPacket[0]=LOK_ADRESS; //DCCAdressByte Lokadresse 3
            DCC_RadioPacket[1]=0x91;       //DCCFunctionByte Lokpfiff ausloesen
            //DCC_RadioPacket[1]=0x90;       DCCFunctionByte Lokpfiff beenden (Erledigt die Basisstation)
            B_STATUS=0;
            break;

    case 2: //Fahrtrichtungswechsel Vorwaerts/Rueckwaerts aufbereiten
            if(NOTSTOP) //Betaetigen der Taste Fahrtrichtung hebt NOTSTOP auf
            { if(counter<10)                        //Der folgende Befehl im loop 10x schicken
              { DCC_RadioPacket[0]=0x78;            //DCCAdressByte NOTSTOP
                DCC_RadioPacket[1]=0x01;            //DCCFunctionByte NOTSTOP deaktivieren
                digitalWrite(LED_Rueckwaerts, LOW); //LED aus
                digitalWrite(LED_AUX, LOW);         //LED aus
                if(counter %2) digitalWrite(LED_Vorwaerts, LOW);
                else digitalWrite(LED_Vorwaerts, HIGH);
                counter++;
              }
              else  //NOTSTOP-Aufloesung wurde 10x verschickt
              { NOTSTOP=false;
                B_STATUS=0;
                Vorwaerts=true;
                digitalWrite(LED_Vorwaerts, HIGH);
              }
            }
            else  //Normalbetrieb (kein NOTSTOP)
            { if(Vorwaerts)
              { Vorwaerts=false;
                digitalWrite(LED_Vorwaerts, LOW);
                digitalWrite(LED_Rueckwaerts, HIGH);
              }
              else
              { Vorwaerts=true;
                digitalWrite(LED_Vorwaerts, HIGH);
                digitalWrite(LED_Rueckwaerts, LOW);
              }
              B_STATUS=0;
            }
            break;

    case 3: //NOTSTOP
            counter=0; //Wird bei der Ausloesung des NOTSTOPs benoetigt (siehe case 2)
            digitalWrite(LED_Vorwaerts, HIGH);   //LED ein
            digitalWrite(LED_Rueckwaerts, HIGH); //LED ein
            digitalWrite(LED_AUX, HIGH);         //LED ein
            DCC_RadioPacket[0]=0x78;             //DCCAdressByte NOTSTOP
            DCC_RadioPacket[1]=0x80;             //DCCFunctionByte NOTSTOP aktivieren
            break;

    case 4: //AUX2 (Loksound) aufbereiten
            DCC_RadioPacket[0]=LOK_ADRESS; //DCCAdressByte Lokadresse 3
            DCC_RadioPacket[1]=0xBA;       //DCCFunctionByte Betriebsgeraeusch ein
            //DCC_RadioPacket[1]=0xB2;       DCCFunctionByte Betriebsgeraeusch aus (Erledigt die Basisstation)
            B_STATUS=0;
            break;
  }
  //Paket versenden
  DCC_RadioPacket[2]=DCC_RadioPacket[0];          //Fehlererkennung Basisstation
  DCC_RadioPacket[3]=DCC_RadioPacket[1];          //Fehlererkennung Basisstation
  rf69.send(DCC_RadioPacket, DCC_RadioPacketLEN); //Kommando schicken
  rf69.waitPacketSent();
  delay(225);
}

void RadioErrorLED(void)
{ digitalWrite(LED_Vorwaerts, HIGH);
  delay(200);
  digitalWrite(LED_Vorwaerts, LOW);
  digitalWrite(LED_Rueckwaerts, HIGH);
  delay(200);
  digitalWrite(LED_Rueckwaerts, LOW);
  digitalWrite(LED_AUX, HIGH);
  delay(200);
  digitalWrite(LED_AUX, LOW);
  digitalWrite(LED_Rueckwaerts, HIGH);
  delay(200);
  digitalWrite(LED_Rueckwaerts, LOW);
}

void BTN_AUX2_ISR()  //Lokpfiff -> Weisser Button links
{ if((millis() - B_AUX2_DT) > DT_Out)
  { B_AUX2_DT = millis();
    if(NOTSTOP) B_STATUS = 3;
    else B_STATUS = 1;
  }
}

void BTN_FAHRT_ISR()  //Fahrtrichtung -> Gelber Button
{ if((millis() - B_FAHRT_DT) > DT_Out)
  { B_FAHRT_DT = millis();
    B_STATUS = 2;
  }
}

void BTN_STOP_ISR()  //NOTSTOP -> Roter Button
{ if((millis() - B_STOP_DT) > DT_Out)
  { B_STOP_DT = millis();
    NOTSTOP = true;
    B_STATUS = 3;
  }
}

void BTN_AUX1_ISR()  //Loksound -> Weisser Button rechts
{ if((millis() - B_AUX1_DT) > DT_Out)
  { B_AUX1_DT = millis();
    if(NOTSTOP) B_STATUS = 3;
    else B_STATUS = 4;
  }
}



ARDUINO SOURCECODE NO-DCC-PROJEKT (LOKSEITIG)

//======================================================================
// Titel: HG4/4 Lok
// Autor: Juerg Arnold, CH-Meilen
// Datum: 29. Mai 2023
// Der Funkhandregler nimmt die Fahrbefehle für Lok1 und Lok2 entgegen.
// Die jeweilige Lok erhält per Funk den Fahrbefehl und regelt Geschwindigkeit/Fahrtrichtung.
// Stirnlampenwechsel gemäss CH-Vorbild und Fahrtrichtung. Bei Lokstillstand Kabinenlicht ein.
// Microcontroller:   ADAFRUIT Feather M0 Radio with RFM69 Packet Radio 433MHz Art.-Nr. 3177
// Motor:             MAXON RE30/60Watt 18Volt Art.-Nr. 268213/310006
// Servokontroller:   MAXON ESCON Modul 50/5 Art.-Nr. 438725
// 1. Sendebyte:      0SSSSSSS Lok-A Richtung1  SSSSSSS=Speed
//                    1SSSSSSS Lok-A Richtung2  Speed: 0..127 (7 Bit)
// 2. Sendebyte:      0SSSSSSS Lok-B Richtung1  SSSSSSS=Speed
//                    1SSSSSSS Lok-B Richtung2  Speed: 0..127 (7 Bit)             
#include <SPI.h>                    //Fuer RFM69
#include <RH_RF69.h>                //Fuer RFM69 3177
#define RF69_FREQ 434.0             //Radio Setup in MHz
#define RFM69_CS      8             //Radio Setup
#define RFM69_INT     3             //Radio Setup
#define RFM69_RST     4             //Radio Setup
#define LED_RED       13            //Rote eingebaute LED
#define LED_CAB       A2            //Führerstandslicht
#define LED_VO        A3            //Stirnlampen Oben und Links
#define LED_VL        5             //Stirnlampen Oben und Links
#define LED_VR        6             //Stirnlampen Rechts
#define LED_HOL       A5            //Rücklicht Oben und Links
#define LED_HR        A4            //Rücklicht Rechts
#define A_IN1         A0            //MAXON Servokontroller AnalogIN1 Voltage-Sollwert
#define D_IN2         11            //MAXON Servokontroller IN2 Freigabe Hi=Aktiv
#define D_IN3         12            //MAXON Servokontroller IN3 Drehrichtung LO=CW / HI=CCW
#define ZeroSpeed      5            //Geschwindigkeitsvorgabe vom Funkhandregler ist auf Null         
RH_RF69  rf69(RFM69_CS, RFM69_INT); //Singleton-Instanz des Funkmodultreibers
uint8_t  RFMPKG[] = {0x00, 0x00};   //Funkmodul Empfangsbytes
uint8_t  RFMLEN   = 2;              //Funkmodul Empfangsbytelaenge
uint16_t Speed    = 0x0000;         //Geschwindigkeitskommando für den 10Bit DAC des uControllers
uint8_t  PKG      = 0x00;           //Erhaltenes Befehlspaket

void setup()
{ //Serial.begin(9600); //Serial.println("ONLY FOR DEBUGGING");

  analogReadResolution(8);    //AD-Wandler liefert Werte zwischen 0..255
  pinMode(LED_RED,   OUTPUT); //Rote eingebaute LED aktivieren
  pinMode(LED_CAB,   OUTPUT); //Führerstandslicht aktivieren
  pinMode(LED_VO,    OUTPUT); //Stirnlampen Oben aktivieren
  pinMode(LED_VL,    OUTPUT); //Stirnlampen Links aktivieren
  pinMode(LED_VR,    OUTPUT); //Stirnlampen Rechts aktivieren
  pinMode(LED_HOL,   OUTPUT); //Rücklicht Oben und Links aktivieren
  pinMode(LED_HR,    OUTPUT); //Rücklicht Rechts aktivieren
  pinMode(D_IN2,     OUTPUT); //D_IN2 Freigabe
  pinMode(D_IN3,     OUTPUT); //D_IN3 Drehrichtung
  analogWrite(A_IN1,  0x000); //Speed-Sollwert auf 0 setzen
  digitalWrite(D_IN2,   LOW); //D_IN2 Freigabe OFF
  digitalWrite(D_IN3,   LOW); //D_IN3 Drehrichtung
  digitalWrite(LED_RED,HIGH); //Rote eingebaute LED einschalten
  digitalWrite(LED_VO,  LOW); //Stirnlampe oben
  digitalWrite(LED_VL,  LOW); //Stirnlampe unten links
  digitalWrite(LED_VR,  LOW); //Stirnlampe unten rechts
  digitalWrite(LED_HOL, LOW); //Ruecklampe oben und unten links
  digitalWrite(LED_HR,  LOW); //Ruecklampe unten rechts
  digitalWrite(LED_CAB, LOW); //Fuehrerstandslicht

  //Initialisierung des Funkbausteins RFM69. Funk abschalten mit: rf69.sleep();
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);
  //Feather RFM69 TX Test!
  digitalWrite(RFM69_RST, HIGH); delay(10);  //Manueller Reset
  digitalWrite(RFM69_RST, LOW);  delay(10);  //Manueller Reset
  if (!rf69.init()) //RFM69 Funkbausteininitialisierung fehlgeschlagen
  { digitalWrite(LED_VO,HIGH); while(true) delay(33);} //Obere Stirnlampe an.
  //RFM69-Funkbaustein-Init OK! Konfiguration: 434.0MHz/Modulation-GFSK_Rb250Fd250/+13dbM LowPowerModule
  if (!rf69.setFrequency(RF69_FREQ))  //setFrequency 434MHz fehlgeschlagen
  { digitalWrite(LED_VO,HIGH); while(true) delay(33);} //Obere Stirnlampe an.
  //Bei High-Power RF69 (RFM69HW) -> Sendeleistung mit ishighpowermodule=true setzen!
  //Sendeleistungsbereich: 14..20 Zweites Argument muss fuer Modul 69HCW True sein.
  rf69.setTxPower(20, true);
  //Encryption-Schluessel -> Uebereinstimmung Sender/Empfaenger!
  uint8_t key[]={0x10,0x51,0xB9,0xC0,0x10,0x51,0xB9,0xC0,0x15,0x00,0x74,0x60,0x15,0x00,0x74,0x60};
  rf69.setEncryptionKey(key); //Funkmodul ist nun sendebereit!
  //Alles OK und bereit zur Abfahrt
  digitalWrite(LED_RED, HIGH);  //Rote LED des Mikrokontrollers     
  digitalWrite(LED_VR,  HIGH);  //Stirnlampe vorne/rechts ->Immer ON
  digitalWrite(LED_HR,  HIGH);  //Stirnlampe hinten/rechts->Immer ON
  digitalWrite(LED_VO,  HIGH);  //Stirnlampe vorne/oben
  digitalWrite(LED_VL,  HIGH);  //Stirnlampe vorne/links
  digitalWrite(LED_HOL,  LOW);  //Stirnlampe hinten/oben/links
  digitalWrite(D_IN2,   HIGH);  //D_IN2 Freigabe ON
}

void loop()
{ if (rf69.waitAvailableTimeout(200)) //Alle ca. 166mSec sollte ein Funkbefehl eintreffen
    if (rf69.recv(RFMPKG, &RFMLEN))
    { PKG=RFMPKG[0];           //Lok-A: 8Bit-Paket umkopieren (Erstes Sendebyte)
                               //Für Lok-B zweites Sendebyte → PKG=RFMPKG[1]
      Speed=0x0000;            //VariablenReset
      Speed=PKG & 0x7F;        //7Bit-Speedbefehl auslesen
      Speed=Speed << 3;        //Aus 7Bit mach 10Bit weil der Mikrokontroller-Ausgang A0 ist ein 10Bit-DAC
      if(Speed < ZeroSpeed) digitalWrite(LED_CAB, HIGH);     //Fuehrerstandslicht ON;
      else                    digitalWrite(LED_CAB, LOW);    //Fuehrerstandslicht OFF
      if(PKG & 0x80)                  //Fahrtrichtung auslesen
      { digitalWrite(LED_VO,   LOW);  //Stirnlampe vorne/oben
        digitalWrite(LED_VL,   LOW);  //Stirnlampe vorne/links
        digitalWrite(LED_HOL, HIGH);  //Stirnlampe hinten/oben/links
        digitalWrite(D_IN3,    LOW);  //Servokontroller Drehrichtung LO=CW
        analogWrite(A_IN1,   Speed);  //Analoger Speed-Sollwert im Servokontroller setzen
      }
      else
      { digitalWrite(LED_VO,  HIGH);  //Stirnlampe vorne/oben
        digitalWrite(LED_VL,  HIGH);  //Stirnlampe vorne/links
        digitalWrite(LED_HOL,  LOW);  //Stirnlampe hinten/oben/links
        digitalWrite(D_IN3,   HIGH);  //Servokontroller Drehrichtung HI=CCW
        analogWrite(A_IN1,   Speed);  //Analoger Speed-Sollwert im Servokontroller setzen
      }
    }
}


ARDUINO SOURCECODE NO-DCC-PROJEKT (FERNSTEUERUNG)

//======================================================================
// Titel: HG4/4-Funk
// Autor: Juerg Arnold, CH-Meilen
// Datum: 29. Mai 2023
// Der Funkhandregler nimmt die Fahrbefehle für Lok2 und Lok3 entgegen.
// Die jeweilige Lok erhält per Funk den Fahrbefehl und regelt Geschwindigkeit/Fahrtrichtung.
// Stirnlampenwechsel gemäss CH-Vorbild und Fahrtrichtung. Bei Lokstillstand Kabinenlicht ein.
// Microcontroller:   ADAFRUIT Feather M0 Radio with RFM69 Packet Radio 433MHz Art.-Nr. 3177
// Motor:             MAXON RE30/60Watt 18Volt Art.-Nr. 268213/310006
// Servokontroller:   MAXON ESCON Modul 50/5 Art.-Nr. 438725
// 1. Sendebyte:      0SSSSSSS Lok-A Richtung1  SSSSSSS=Speed
//                    1SSSSSSS Lok-A Richtung2  Speed: 0..127 (7 Bit)
// 2. Sendebyte:      0SSSSSSS Lok-B Richtung1  SSSSSSS=Speed
//                    1SSSSSSS Lok-B Richtung2  Speed: 0..127 (7 Bit)
#include <SPI.h>                    //RFM69 3177 Radio
#include <RH_RF69.h>                //RFM69 3177 Radio
#define  RF69_FREQ   434.0          //Radio Setup in MHz
#define  RFM69_CS        8          //Radio Setup
#define  RFM69_INT       3          //Radio Setup
#define  RFM69_RST       4          //Radio Setup
#define  LEDred         13          //Eingebaute rote LED

#define SPEEDApin       A1          //10kOhm Potentiometer
#define DIRApin          6          //Schalter auf Potentiometer1
#define SPEEDBpin       A3          //10kOhm Potentiometer
#define DIRBpin          5          //Schalter auf Potentiometer1

#define  BATpin         A7          //Spannungsteiler für BAT-Prüfung (3.6V..4.3V)
#define  BATlow         35          //Formel:ADC=Vbat*64/6.6; Vaccu<3.6 Volt; ADC:6b
#define  SendWait      100          //Wartezeit nach Packetversand
#define TimeoutButton  400          //Debouncing-TimeOut in Millisekunden zum Tasten entprellen
#define PotiLeftStop     3          //ADC-Wert der als Potentiometer auf Null gewertet wird
#define PotiRightStop   60          //ADC-Wert der als Potentiometer auf Null gewertet wird
#define Winking         50          //Rote LED ON bei Poti nicht am linken Anschlag bei Richtungswechsel           

RH_RF69  rf69(RFM69_CS, RFM69_INT); // Singleton-Instanz des Funkmodultreibers
uint8_t   RFMPKG[2] = {0x00, 0x00}; //Funkmodul -> 1. Sendebyte zur HG 4/4 Nr2, 2. Sendebyte zu HG4/4 Nr3
uint8_t   RFMLEN    = 2;            //Funkmodul -> Sendebytelänge
uint8_t   SpeedA    = 0;            //PotiA Speed
uint8_t   SpeedB    = 0;            //PotiB Speed
uint8_t   DirA      = 0x00;         //ButtonA Fahrtrichtung LocoA 0x00->DirA1, 0x40->DirA2
uint8_t   DirB      = 0x80;         //ButtonB Fahrtrichtung LocoB 0x80->DirB1, 0xC0->DirB2
uint8_t   Vbat      = 0;            //Batteriespannung aktuelle Messung
volatile unsigned long DebounceA=0; //ButtonA Debouncing-Timer
volatile unsigned long DebounceB=0; //ButtonB Debouncing-Timer
volatile bool DIRAflag=false;       //Flag das anzeigt, dass Fahrtrichtungswechsel gedrückt wurde
volatile bool DIRBflag=false;       //Flag das anzeigt, dass Fahrtrichtungswechsel gedrückt wurde

//***SETUP****************************************************************************************
void setup()
{ //Serial.begin(9600);  //Serial.println("ONLY FOR DEBUGGING");

  analogReadResolution(7);                   //6-Bit-ADC-Einstellung für 0..127
  pinMode(LEDred, OUTPUT);                   //Rote eingebaute LED aktivieren
  pinMode(DIRApin, INPUT_PULLUP);            // Digitaler Input-A initialisieren
  pinMode(DIRBpin, INPUT_PULLUP);            // Digitaler Input-B initialisieren
  attachInterrupt(digitalPinToInterrupt(DIRApin), DIRApinISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(DIRBpin), DIRBpinISR, FALLING);

  //Beide Speedbuttons voll offen bedeutet kein Fahrbetrieb dafür Batterie laden
  if( (analogRead(SPEEDApin) > PotiRightStop) && (analogRead(SPEEDBpin) > PotiRightStop) )    
    while(true)                     //Hier geht es nicht mehr weiter
    { digitalWrite(LEDred,HIGH); delay(100); digitalWrite(LEDred,LOW);  delay(900); }

  //Initialisierung des Funkbausteins RFM69. Funk abschalten mit: rf69.sleep();
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);
  //Feather RFM69 TX Test!
  digitalWrite(RFM69_RST, HIGH); delay(10); //Manueller Reset
  digitalWrite(RFM69_RST, LOW);  delay(10); //Manueller Reset
  if (!rf69.init()) //RFM69 Funkbausteininitialisierung fehlgeschlagen
    while(true)
    { digitalWrite(LEDred,HIGH); delay(100); digitalWrite(LEDred,LOW);  delay(100); }
  //RFM69-Funkbaustein-Init OK! Konfiguration: 434.0MHz/Modulation-GFSK_Rb250Fd250/+13dbM LowPowerModule
  if (!rf69.setFrequency(RF69_FREQ))  //setFrequency 434MHz fehlgeschlagen
    while(true)
    { digitalWrite(LEDred,HIGH); delay(100); digitalWrite(LEDred,LOW);  delay(100); }
  //Bei High-Power RF69 (RFM69HW) -> Sendeleistung mit ishighpowermodule=true setzen!
  //Sendeleistungsbereich: 14..20 Zweites Argument muss fuer Modul 69HCW True sein.
  rf69.setTxPower(20, true);
  //Encryption-Schluessel -> Uebereinstimmung Sender/Empfaenger!
  uint8_t key[]={0x10,0x51,0xB9,0xC0,0x10,0x51,0xB9,0xC0,0x15,0x00,0x74,0x60,0x15,0x00,0x74,0x60};
  rf69.setEncryptionKey(key); //Funkmodul ist nun sendebereit!

  //Beide Speedbuttons auf Null und damit Kavalierstart vermeiden
  while( (analogRead(SPEEDApin) > PotiLeftStop) || (analogRead(SPEEDBpin)> PotiLeftStop) )
  { digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW);  delay(Winking);
    digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW);  delay(4*SendWait);
  }
  
  //Alles OK und bereit zur Abfahrt
  digitalWrite(LEDred, HIGH);  //Eingebaute rote LED einschalten
}

void loop()
{ 
  Vbat = analogRead(BATpin);        //Akkuspannung prüfen
  if(Vbat < BATlow)                 //Akkuspannung kleiner 3.6 Volt
  { while(true)                     //Hier geht es nicht mehr weiter
    { digitalWrite(LEDred,HIGH); delay(100); digitalWrite(LEDred,LOW); delay(900); } 
  }
  
  SpeedA = analogRead(SPEEDApin); //Geschwindigkeits-DrehknopfA lesen
  SpeedB = analogRead(SPEEDBpin); //Geschwindigkeits-DrehknopfB lesen
  
  if(DIRAflag)
    if(SpeedA > PotiLeftStop)
    { SpeedA=0;
      digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW); delay(Winking);
      digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW); delay(2*SendWait);
    }
    else
    { if(DirA == 0x00) DirA=0x80; //0x80->LocoA/Dir2
      else DirA=0x00;             //0x00->LocoA/Dir1
      digitalWrite(LEDred, HIGH);
      DIRAflag=false;
    }

  if(DIRBflag)
    if(SpeedB > PotiLeftStop)
    { SpeedB=0;
      digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW); delay(Winking);
      digitalWrite(LEDred,HIGH); delay(Winking); digitalWrite(LEDred,LOW); delay(2*SendWait);
    }
    else
    { if(DirB == 0x00) DirB=0x80; //0x80->LocoB/Dir2
      else DirB=0x00;             //0x80->LocoB/Dir2
      digitalWrite(LEDred, HIGH);
      DIRBflag=false;
    }

  //LocoA-Kommando zusammenstellen und schicken
  RFMPKG[0] = DirA;               //LocoA und Fahrtrichtung
  RFMPKG[0] = RFMPKG[0] | SpeedA; //7-Bit-SpeedwertA setzen
  //LocoB-Kommando zusammenstellen und schicken
  RFMPKG[1] = DirB;               //LocoB und Fahrtrichtung
  RFMPKG[1] = RFMPKG[1] | SpeedB; //7-Bit-SpeedwertB setzen
  rf69.send(RFMPKG, RFMLEN);      //Kommando schicken
  rf69.waitPacketSent();
  delay(SendWait);  

}

void DIRApinISR()  //ISR-RoutineA
{ if((millis() - DebounceA) > TimeoutButton ) //Debouncing-TimeOut zum Tasten entprellen
  { DebounceA = millis(); DIRAflag=true;
  }
}

void DIRBpinISR()  //ISR-RoutineB
{ if((millis() - DebounceB) > TimeoutButton ) //Debouncing-TimeOut zum Tasten entprellen
  { DebounceB = millis(); DIRBflag=true;
  }
}