Digitale DCC-Modellbahnsteuerung mit Arduino
Einleitung
Beim Freiluftbahner kommt bald mal der Wunsch auf, seine Fahrzeuge per Funk anstatt mit ortsgebundenen/verkabelten Fahrtrafos zu bedienen.
Verwendet man zudem Akkus, riskiert man keinen Stromschlag. Werden diese vom Freiland-Modellzug noch selber mitgeschleppt,
gehören Betriebsunterbrüche wegen verschmutztem Gleis der Vergangenheit an.
Der Markt bietet solche Fernsteuerungen schon lange an. Für meinen Geschmack allerdings mit etwas zu üppigem Funktionsumfang
und bisher nicht im Akku-Fahrstrombetrieb. Darum habe ich für mein Projekt folgendes festgelegt:
- DCC-Protokoll (Einschränkung auf 8-Bit DCC-Funktionsbefehle)
- Nur HG 4/4 Dampflok mit Standard-DCC-Adresse 3 unterstützt
- Lok-Funktionen: Fahrstufe, Vor- Rückwärts, Licht, Sound on/off und Lokpfiff
- Lok-Geschwindigkeit: 28 Fahrstufen je Fahrtrichtung
- Basisstation mit zweifarbiger Kontroll-LED für Betriebszustand
- Basisstation Fahrstrom und Elektronik (uC): 24-Volt Akku 6s-LiPo mit 4200mAh
(Unterhalb 22 Volt kein vernünftiger Betrieb mit Zahnstange und 12 Prozent Steigung möglich!) - Basisstation: Kurzschlusssichere 5 Ampere-H-Brücke (STMicrölectronics L6203)
- Gleisstrom: Kurzschlusssicher → Kontinuierliche Strommessung und Abschaltung bei Überstrom
- Funkhandregler mit drei roten LEDs für Betriebszustands (LCD-Displays sind bei starkem Sonnenlicht schlecht ablesbar!)
- Funkhandregeler: 3.7V-LiPo-Akku (Aufladbar per USB-Kabel)
Während Ladevorgang: Funkstille und abgeschaltete Anzeige - Funkverbindung: ISM-433MHz. (Industrial, Scientific and Medical Band).
- Mikrokontroller: Adafruit Feather M0 Radio (ATSAMD21G18 ARM Cortex M0, Taktrate 48MHz, 256k Flash-Speicher, 32k RAM)
- Kein RailCom (Antworten vom Lokdecoder)
- Weichenumschaltung per Funk
Das DCC-Protokoll
Modellbahnen werden schon längst digital gesteuert. Zum Beispiel mit dem DCC-Protokoll (Digital Command Control), das von allen Modellbahnherstellern unterstützt wird. Meine Wahl fiel auf DCC, weil es quelloffen ist und man auch entsprechende Normblätter im Internet findet, wie z.B. diese hier:
Das DCC-Gleissignal hat einerseits den Zweck, die Fahrzeuge mit Energie zu versorgen und andererseits, sie mit individuellen Fahrzeugkommandos 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.

Arduino als Entwicklungsplattform
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 }
Die Hardware der Basisstation
Lokomotive mit Güterwagen als Träger der Basisstation und des LiPO-Akkus:
Weicheantrieb mit Funksteuerung: Eigentlich war eine drehbare Weichenlaterne vorgesehen, die anzeigen würde, ob die Weiche auf «Gerade» oder «Abzweigend» steht.
Nun kommt aber ein sogenanntes Zwergsignal zum Einsatz, das gemäss den SBB-Fahrdienstvorschriften den Rangierbetrieb regelt und nicht Weichenstellungen anzeigt.
Um trotzdem sichtbar zu machen, wie der Zug die Weiche passieren wird und dabei die SBB-Fahrdienstvorschriften einigermassen zu erfüllen, besteht der Kompromiss darin,
eine abzweigende Weiche mit «Fahrt mit Vorsicht» bzw. bei Geradeausfahrt mit «Fahrt» zu signalisieren. Bei besetzter Weiche oder während der Weichenumstellung zeigt das Zwergsignal «Halt».
Das Weichenherz wird je nach Weichenstelung umgepolt. Als Gleisbesetztmelder wirkt eine IR-LED zur Distanzmessung bzw. Fahrzeugdedektierung. Die Ausgangslage der Weiche ist «Gerade» und wird per Funk auf
«Abzweigend» umgestellt. Nach der Passage der Zuges oder nach einem Timeout keht die Weiche automatisch in die Ausgangsposition zurück. Die Schaltelektronik (uC) wird über das Gleis mit Strom versorgt.
Bauteilliste:
- Mikrokontroller: Adafruit (3177) Feather M0 Radio (ATSAMD21G18 ARM Cortex M0, Taktrate 48MHz, 256k Flash-Speicher, 32k RAM)
- ISM 433MHz Antenne Molex inkl. uFl-Stecker (Die aufklebbare Flachantenne ist unter dem Gehäusedeckel angebracht)
- Gleisbesetztmelder: Adafruit (3316) VL6180X Time of Flight Distance Ranging Sensor 5..100mm
- Weichenherzpolarisierung: Adafruit (3191) 10-Ampere Power Relay FeatherWing (Non-latching type relay)
- Weichenmotor/Servoansteuerung: Adafruit (2928) 8-Channel PWM or Servo FeatherWing
- Weichenmotor/Servo: Miniatur-Servo KST X08 HV V6.0 im Alugehäuse und mit Metallgetriebe
- Brückengleichrichter mit 470uF Elektrolytkondensator und L7805CV (Linear Fixed Voltage Regulator, 5V, 1.5A, TO-220)
- 3 weisse LEDs mit Vorwiderständen


- 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 - ZUSÄTZLICHE ANGABEN ZUR WEICHENSTEUERUNG:
Distanzsensor: Adafruit VL6180X Time of Flight Distance Ranging Sensor 3316
Relais für Weichenherzumpolung: Adafruit Power Relay FeatherWing 3191
Servo für Weichenstellung: Digital Servo KST X08 V5 (3.8V-8.4V; 1520uS/333Hz, 900-2100uS)
Servotreiber: Adafruit Servo FeatherWing 2918 (ServoNr. 0-7, Pulslanege 12Bit 0..4096) - 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

//====================================================================== // 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; } } }

//====================================================================== // 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; } }

//====================================================================== // Titel: Weichensteuerung // Autor: Juerg Arnold, CH-Meilen // Datum: 14. Mai 2022 // Zweck: Die Weichensteuerung nimmt die Befehle vom Funkhandregler // entgegen und schaltet die Weiche. //====================================================================== #include <SPI.h> //RFM69 #include <RH_RF69.h> //RFM69 #include <Wire.h> //Servo PWM #include <Adafruit_PWMServoDriver.h> //Servo PWM #include "Adafruit_VL6180X.h" //IR-LED-Distanzmessung //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 Adafruit_PWMServoDriver ServoController = Adafruit_PWMServoDriver(); //Servo PWM I2C Defaultadresse 0x40 Adafruit_VL6180X BesetztMelder = Adafruit_VL6180X(); //IR-Sensor I2C Fixadresse 0x29 #define RELAY_OUT 12 //12: I/O-Pinn fuer Relays #define SENSOR_SHDN 6 // 6: LOW=Shutdown des IR-Sensors uint16_t WeicheGerade=1848; //Servo-Anschlag1 uint16_t WeicheAbzweigend=2260; //Servo-Anschlag2 uint8_t BesetztMeldung=0; //Verwendet in der Funktion GleisBesetzt() uint8_t BesetztStatus=0; //Verwendet in der Funktion GleisBesetzt() bool WeichenstellungGerade=true; //Funkmodul -> Empfangsbytes vom Funkhandregeler: Addresse/AddresseRedundat/Befehl/BefehlRedundant uint8_t RemPKG[4] = {0x00,0x00,0x00,0x00}; uint8_t RemPKGl = 4; //Paketlaenge //************************************************************************************************* void setup() { //Serial.begin(9600); //Serial.println("ONLY FOR DEBUGGING"); pinMode(RELAY_OUT, OUTPUT); pinMode(SENSOR_SHDN, OUTPUT); digitalWrite(RELAY_OUT, LOW); digitalWrite(SENSOR_SHDN, LOW); //Initialisierung der Servosteuerung PWM ServoController.begin(); ServoController.setOscillatorFrequency(27000000); ServoController.setPWMFreq(333); // Max. PWM-Frequenz 333Hz fuer ServoController KST X08 V5 delay(50); ZeigeVoll(); //Visuelle Lampenkontrolle delay(500); //500mSec. warten ZeigeHalt(); //Zug vor Weiche Halt signalisieren IRLEDon(); //IR-LED-Laser-Messung ein while(!GleisFrei()) //Warten bis Gleis frei delay(100); //Zwischen den Distanzmessungen kurz warten IRLEDoff(); //IR-LED-Laser-Messung aus WeicheGR(); //Weiche auf Gerade ZeigeFahrt(); //Zug darf kommen: Freie Fahrt anzeigen WeichenstellungGerade=true; //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 ZeigeFehlerFM(); //--- //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 ZeigeFehlerFM(); //--- //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--- ZeigeFahrt(); //Der Betrieb kann losgehen } void loop() { do { rf69.waitAvailable(); //Warten bis Funkpaket eintrifft }while(!(rf69.recv(RemPKG, &RemPKGl) && //Gueltiges Funkpaket (RemPKG[2]==RemPKG[0]) && //Keine Fehler bei der Uebermittlung (RemPKG[3]==RemPKG[1]) && //Keine Fehler bei der Uebermittlung (RemPKG[0]==0x03) && //Lok-Nr.3 (RemPKG[1]==0x91) )); //Lokfunktion Pfiff IRLEDon(); //IR-LED-Laser-Messung einschalten if(GleisFrei()) //Wenn kein Fahrzeug auf der Weiche steht { IRLEDoff(); //IR-LED-Laser-Messung ausschalten ZeigeHalt(); //Weiche darf jetzt nicht passiert werden: Halt anzeigen if(WeichenstellungGerade) { WeicheAB(); //Weiche auf abzweigende Fahrt stellen ZeigeVorsicht(); //Zug darf kommen: Fahrt mit Vorsicht anzeigen WeichenstellungGerade=false; //Flag zuruecksetzen } else { WeicheGR(); //Weiche auf gerade Fahrt stellen ZeigeFahrt(); //Zug darf kommen: Freie Fahrt anzeigen WeichenstellungGerade=true; //Flag setzen } } else //Weichenschaltauftrag wurde ignoriert IRLEDoff(); //IR-LED-Laser-Messung ausschalten RemPKG[0]==0x00; //Funk-Buffer leeren RemPKG[1]==0x00; //Funk-Buffer leeren } void ZeigeFehlerFM() //Gemaess SBB Fahrdienstvorschrift so nicht vorgesehen { ServoController.setPWM(1, 0, 4096); //LEDrechts aus ServoController.setPWM(2, 0, 4096); //LEDoben aus while(true) //Endlosschleife { ServoController.setPWM(3, 4096, 0); //LEDlinks ein delay(250); //Warten ServoController.setPWM(3, 0, 4096); //LEDlinks aus ServoController.setPWM(1, 4096, 0); //LEDoben ein delay(250); ServoController.setPWM(1, 0, 4096); //LEDoben aus } } void ZeigeFehlerIR() //Gemaess SBB Fahrdienstvorschrift so nicht vorgesehen { ServoController.setPWM(1, 0, 4096); //LEDrechts aus ServoController.setPWM(2, 0, 4096); //LEDoben aus while(true) //Endlosschleife { ServoController.setPWM(3, 4096, 0); //LEDlinks ein delay(250); //Warten ServoController.setPWM(3, 0, 4096); //LEDlinks aus ServoController.setPWM(2, 4096, 0); //LEDrechts ein delay(250); //Warten ServoController.setPWM(2, 0, 4096); //LEDrechts aus } } void ZeigeVoll() //Gemaess SBB Fahrdienstvorschrift so nicht vorgesehen { ServoController.setPWM(1, 4096, 0); //LEDrechts ein ServoController.setPWM(2, 4096, 0); //LEDoben ein ServoController.setPWM(3, 4096, 0); //LEDlinks ein } void ZeigeHalt() //Gemaess SBB Fahrdienstvorschrift "Halt" { ServoController.setPWM(1, 4096, 0); //LEDrechts ein ServoController.setPWM(2, 0, 4096); //LEDoben aus ServoController.setPWM(3, 4096, 0); //LEDlinks ein } void ZeigeFahrt() //Gemaess SBB Fahrdienstvorschrift "Fahrt" { ServoController.setPWM(1, 0, 4096); //LEDrechts aus ServoController.setPWM(2, 4096, 0); //LEDoben ein ServoController.setPWM(3, 4096, 0); //LEDlinks ein } void ZeigeVorsicht() //Gemaess SBB Fahrdienstvorschrift "Fahrt mit Vorsicht" { ServoController.setPWM(1, 4096, 0); //LEDrechts ein ServoController.setPWM(2, 4096, 0); //LEDoben ein ServoController.setPWM(3, 0, 4096); //LEDlinks aus } void WeicheGR() { ServoController.setPWM(7, 0, WeicheGerade); //Weiche auf Gerade stellen (Ausgangszustand) delay(800); //Servo benoetigt 800mSec. ServoController.setPWM(7, 0, 4096); //Weichenmotor ausschalten digitalWrite(RELAY_OUT, LOW); //Weichenherzpolarisierung auf Ausgangszustand } void WeicheAB() { ServoController.setPWM(7, 0, WeicheAbzweigend); //Weiche auf Abzweigend stellen (Temporaerer Zustand) delay(800); //Servo benoetigt 800mSec. ServoController.setPWM(7, 0, 4096); //Weichenmotor ausschalten digitalWrite(RELAY_OUT, HIGH); //Weichenherz umpolarisieren } void IRLEDon() { digitalWrite(SENSOR_SHDN, HIGH); //IR-Sensor aktivieren -> Starten der IR-Distanzmessung if (! BesetztMelder.begin()) { //IR-Sensorfehler ServoController.setPWM(7, 0, 4096); //Weichenmotor ausschalten digitalWrite(SENSOR_SHDN,LOW); //IR-Sensor deaktivieren ZeigeFehlerIR(); //Hier geht es nicht mehr weiter } } void IRLEDoff() { digitalWrite(SENSOR_SHDN,LOW); //IR-Sensor deaktivieren } bool GleisFrei() { BesetztMeldung=BesetztMelder.readRange(); BesetztStatus=BesetztMelder.readRangeStatus(); if((BesetztStatus == VL6180X_ERROR_NONE) && (BesetztMeldung>5) && (BesetztMeldung<100)) return false; //Gleis ist besetzt -> Vertikales Hindernis zwischen 5mm und 100mm else return true; //Gleis ist frei }
Programmiertipps: Rechnen mit binären Operatoren
Beim Low-Level-Programmieren von z.B. Mikrokontrollern (Arduino) und in der Kommunikation können Berechnungen auf binärer Ebene (HEX, BIN) Vorteile bringen, wie z.B. eine schellere Programmausführung.
x >> n bedeutet: Bitfolge x um n-Stellen nach rechts verschieben (Rechts fallen die Bits heraus) x << n bedeutet: Bitfolge x um n-Stellen nach links verschieben (Neue 0-Bits werden rechts eingefügt) & AND/UND (AND-WHT: 00=0 / 01=0 / 10=0 / 11=1) Nicht zu verwechseln mit logischem AND && | OR/ODER (OR-WHT: 00=0 / 01=1 / 10=1 / 11=1) Nicht zu verwechseln mit logischem OR ||; ^ XOR (Exklusives ODER WHT: 00=0 / 01=1 / 10=1 / 11=0) ~ NOT/NICHT/INVERTER (Bsp. 1011 wird zu 0100) (Die zur Verfügung stehende Bitbreite ist zu beachten! Stichwort: Data-Overflow) Ganzzahlige Division durch 2 Zahlenbeispiel 1: 12 div 2 = 6 12 = 1100 1100 >> 1 = 110 110 = 6 Zahlenbeispiel 2: 15 div 2 = 7 15 = 1111 1111 >> 1 = 111 111 = 7 Restwert (Modulo) bei Division durch 2 LSB prüfen: 0→Rest-0, 1→Rest-1 Multiplikation mit dem Faktor 2 Zahlenbeispiel: 13 mul 2 = 26 13 = 1101 1101 << 1 = 11010 11010 = 26 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 2 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 Zahlenbeispiel: Zweites Bit von links auslesen a=0110 b=0011 c=0100 a & c = 0100 b & c = 0000 Bit setzen Zahlenbeispiel: 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 Zahlenbeispiel: 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)