//-------------------------------------------------------------------------------------------------------
/*
DCC-Monitor zur Ausgabe der DCC-Befehle auf dem seriellen Monitor und 4x20 LCD display
======================================================================================
V 1.0, 18.01.2020 domapi
erweitert bis V 1.7 mit LCD, bei Moppe 19.04.2020
Features:
---------
- Ausgabe der Lok-Befehle
- Ausgabe der Lok-CV-Kommandos
- Ausgabe der Accessory-Befehle
- Ausgabe der Accessory-CV-Kommandos
- Ausgabe der CV-Befehle auf dem Programmiergleis
- Eingaben über seriellen Monitor zur Steuerung der Anzeige
- DCC-Paket-Speicher zum Verhindern der mehrfachen Ausgabe von Befehlen
- einstellbar getrennt nach Loks, Accessories und CVs
- Wurde ein gültiges Paket gefunden und im seriellen Monitor ausgegeben, wird es in einer Tabelle gespeichert
- Vor jeder Paketausgabe wird geprüft, ob es in der Tabelle bereits enthalten ist, d.1h. schon ausgegeben wurde
- Die Tabelle umfasst für Loks 240 x 4 Bytes, ohne Präambel und ohne Check-Byte; für Zubehörbefehle nur 5 x 4 Bytes
(Lok-Befehle werden sehr häufig und vor allem periodisch wiederholt, Zubehör mehrfach, aber nur bei Änderungen )
- Die Tabelle wird sukzessive aufgefüllt, wenn sie voll ist, wird der erste Eintrag gelöscht und alle anderen Einträge nach vorne geschoben
Das neue Paket wandert dann an die letzte Stelle
- Die Tabellengröße sollte für ca. 70 Loks reichen
- Über das Menü kann die Speicherung ein-/aus-geschaltet werden (separat für Acc, CVs, Loks)
- Ausgabe der Lokname in Klartext
- Ausgabe einer Statistik über die detektierten DCC-Befehle
- LCD kann letzter Lok oder Weichen befehl zeigen
Hardware:
---------
- Schaltplan, siehe: https://www.stummiforum.de/viewtopic.php?t=165060&start=225#p1957001
- DCC-Signal-Auswertung über 6N137 Optokoppler, Schutzdiode (1N4148) und Vorwiderstand (1k). Oder über Brückengleichrichter (siehe Link).
Pin 6 Optokoppler mit 10 kOhm an 5V reicht aus. Pin 7 muss nicht an 5V angeschlossen sein, er kann unbelegt bleiben.
- ACK-Signal über Optokoppler CNY17 und Transistor BC557 anschließen, erzeugt Stromimpuls an +/- des Brückengleichrichters.
Arduino Nano:
// +-----+
// +------------| USB |------------+
// | +-----+ |
// | [ ]D13/SCK MISO/D12[ ] |
// | [ ]3.3V MOSI/D11[ ]~|
// | [ ]V.ref ___ SS/D10[ ]~|
// | [ ]A0 / N D9[ ]~|
// | [ ]A1 / A D8[ ] |
// | [ ]A2 N / D7[ ] |
// | [ ]A3 _0_/ D6[ ]~|
// | [ ]A4/SDA D5[ ]~|
// ACK-Pin | [ ]A5/SCL D4[ ] |
// | [ ]A6 INT1/D3[ ]~|
// | [ ]A7 INT0/D2[ ] | DCC-Eingang über Optokoppler Pin 6
// | [ ]5V GND[ ] |
// | [ ]RST RST[ ] |
// | [ ]GND 5V MOSI GND TX1[ ] |
// | [ ]Vin [ ] [ ] [ ] RX1[ ] |
// | [ ] [ ] [ ] |
// | MISO SCK RST |
// | NANO-V3 |
// +-------------------------------+
LCD:
LCD RS pin to digital pin 12
LCD Enable pin to digital pin 11
LCD D4 pin to digital pin 6
LCD D5 pin to digital pin 5
LCD D6 pin to digital pin 4
LCD D7 pin to digital pin 3
LCD R/W pin to ground
LCD VSS pin to ground
LCD VCC pin to 5V
10K Pot:
ends to +5V and ground
wiper to LCD VO pin (pin 3)
Todos:
- Komplettanzeige für eine Lok auf Basis Puffer
- Anzeige auf bestimmte Adressen beschränken
*/
//-------------------------------------------------------------------------------------------------------
#include <NmraDcc.h>
#include <LiquidCrystal.h>
NmraDcc Dcc ;
//-------------------------------------------------------------------------------------------------------
// Mit diesen Variablen kann man einstellen, was auf dem seriellen Monitor ausgegeben wird
// Die Einstellungen können während des laufenden Betriebs des DCC-Monitors später im seriellen Monitor geändert werden
//-------------------------------------------------------------------------------------------------------
byte Anzeige_Loks = 1; // Lok-Befehle für Lok-Dekoder und funktionsdekoder
byte Anzeige_Acc = 1; // Zubehör-Befehle für Weichendekoder
byte Anzeige_CV = 1; // Ausgabe von CV-Aktionen (Lesen und Schreiben etc.)
byte puffern_Lok = 1; // Schaltet die Speicherung und Prüfung bereits empfangener DCC-Pakete ein/aus
byte puffern_Acc = 1;
byte puffern_CV = 1;
//-------------------------------------------------------------------------------------------------------
const byte DccAckPin = A5; // Arduino-Pin zur Erzeugung eines ACK-Signals
byte blinker = 5; // toggelt die LED an Pin 13 jedes 5. Mal, wenn ein DCC-Paket gefunden wurde --> zeigt DCC-Signal an
const byte bufferSizeAcc = 5; // Schaltartikelbefehle werden nicht andauernd wiederholt; hier reichen ein paar Pufferplätze aus
const byte bufferSizeLok = 240;
byte Acc_counter = 0; // läuft von 1 - bufferSizeAcc
byte Paket_bekannt_A = 0;
byte Lok_counter = 0; // läuft von 1 - bufferSizeLok
byte Paket_bekannt_L = 0;
// Strukturen zur Speicherung der bereits empfangenen Werte
typedef struct
{
int ADR;
byte DIR;
byte COIL;
} ACC_Befehl;
typedef struct
{
int ADR;
byte ORDER;
byte FUNC;
} Lok_Befehl;
ACC_Befehl Acc_received [bufferSizeAcc];
Lok_Befehl Lok_received [bufferSizeLok];
byte pktByteCount = 0;
unsigned int decoderAddress;
unsigned int decoderAddress_alt = 0;
unsigned int weichenadresse;
byte Ausgang;
byte Spule;
byte Befehl;
byte Funktion;
byte Befehls_Byte;
byte decoderType; //0=Lok, 1=Zubehör/Accessory
byte command;
int CV_address;
byte CV_value;
byte command_alt = 0;
int CV_address_alt = 0;
byte CV_value_alt = 0;
byte speed;
byte checksum = 0;
// Zähler für die Statistik
unsigned long start_time = 0;
unsigned long z_bytes = 0;
unsigned long z_invalid = 0;
unsigned long z_idle = 0;
unsigned long z_lok_speed = 0;
unsigned long z_lok_F0 = 0;
unsigned long z_lok_F5 = 0;
unsigned long z_lok_F9 = 0;
unsigned long z_lok_F13 = 0;
unsigned long z_lok_F21 = 0;
unsigned long z_lok_F29 = 0;
unsigned long z_acc = 0;
unsigned long z_dec_reset = 0;
unsigned long z_acc_cv = 0;
unsigned long z_lok_cv = 0;
unsigned long z_prg_CV = 0;
unsigned long z_ack = 0;
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
LiquidCrystal lcd(12, 11, 6, 5, 4, 3);
// these constants won't change. But you can change the size of
// your LCD using them:
const int numRows = 4;
const int numCols = 20;
const int splashdelay = 5000;
//-------------------------------------------------------------------------------------------------------
void setup()
//-------------------------------------------------------------------------------------------------------
{
pinMode(13, OUTPUT); // eingebaute LED
Serial.begin(115200);
pinMode(DccAckPin, OUTPUT); // Configure the DCC CV Programing ACK pin for an output
Serial.println(F("Domapi´s DCC Monitor V 1.0"));
Serial.println(F("und Moppes DCC display"));
// set up the LCD's number of columns and rows:
lcd.begin(numCols, numRows);
lcd.setCursor(0, 0);
lcd.write("Domapi's DCC-Monitor ");
lcd.setCursor(0, 1);
lcd.write(" erweitert bis:");
lcd.setCursor(0, 2);
lcd.write(" Moppes DCC display");
lcd.setCursor(0, 3);
lcd.write(" von stummiforum.de");
delay(splashdelay);
lcd.clear();
lcd.write("Moppes DCC display ");
Dcc.pin(0, 2, 1); // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 ); // Call the main DCC Init function to enable the DCC Receiver
start_time = millis();
}
//-------------------------------------------------------------------------------------------------------
void loop()
//-------------------------------------------------------------------------------------------------------
{
// You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
Dcc.process();
if (Serial.available())
{
// Tastatur-Befehle via seriellen Monitor des Arduinos:
// 1 = Anzeige Loks ein/aus
// 2 = Anzeige Zubehör ein/aus
// 3 = Anzeige CV-Befehle ein/aus
// 4 = Nur neue Lok-Pakete ein/aus
// 5 = Nur neue Zubehör-Pakete ein/aus
// 6 = Nur neue CV-Befehle ein/aus
// 7 = Statistik
// ? = Befehle anzeigen
switch (Serial.read())
{
case 49: // 1
Serial.print(F("1 Anzeige Loks ein/aus = "));
Anzeige_Loks = !Anzeige_Loks;
if (Anzeige_Loks) Serial.println("ein");
else Serial.println("aus");
break;
case 50: //2
Serial.print(F("2 Anzeige Zubehör ein/aus = "));
Anzeige_Acc = !Anzeige_Acc;
if (Anzeige_Acc) Serial.println("ein");
else Serial.println("aus");
break;
case 51: // 3
Serial.print(F("3 Anzeige CV-Befehle ein/aus = "));
Anzeige_CV = !Anzeige_CV;
if (Anzeige_CV) Serial.println("ein");
else Serial.println("aus");
break;
case 52: // 4
Serial.print(F("4 Nur neue Lok-Pakete anzeigen ein/aus = "));
puffern_Lok = !puffern_Lok;
if (puffern_Lok) Serial.println("ein");
else Serial.println("aus");
break;
case 53: // 5
Serial.print(F("5 Nur neue Zubehör-Pakete anzeigen ein/aus = "));
puffern_Acc = !puffern_Acc;
if (puffern_Acc) Serial.println("ein");
else Serial.println("aus");
break;
case 54: // 6
Serial.print(F("6 Nur neue CV-Befehle anzeigen ein/aus = "));
puffern_CV = !puffern_CV;
if (puffern_CV) Serial.println("ein");
else Serial.println("aus");
break;
case 55: // 7
Serial.println();
Serial.println(F("S t a t i s t i k"));
Serial.println(F("-----------------"));
Serial.print(F("Zeitraum [sec] :")); print_Zahl_rechts_ln((millis() - start_time) / 1000);
Serial.print(F("Anzahl empfangene Bytes:")); print_Zahl_rechts_ln(z_bytes);
Serial.print(F("Gültige Kommandos :"));
print_Zahl_rechts_ln(z_invalid + z_idle + z_lok_speed + z_lok_F0 + z_lok_F5 + z_lok_F9 + z_lok_F13 + z_lok_F21 + z_lok_F29 + z_acc + z_dec_reset + z_acc_cv + z_lok_cv + z_prg_CV);
Serial.print(F("Ungültige Kommandos :")); print_Zahl_rechts_ln(z_invalid);
Serial.print(F("Idle-Pakete :")); print_Zahl_rechts_ln(z_idle);
Serial.print(F("Geschwindigkeitsbefehle:")); print_Zahl_rechts_ln(z_lok_speed);
Serial.print(F("F0 - F4 Funktionen :")); print_Zahl_rechts_ln(z_lok_F0);
Serial.print(F("F5 - F8 Funktionen :")); print_Zahl_rechts_ln(z_lok_F5);
Serial.print(F("F9 - F12 Funktionen :")); print_Zahl_rechts_ln(z_lok_F9);
Serial.print(F("F13 - F20 Funktionen :")); print_Zahl_rechts_ln(z_lok_F13);
Serial.print(F("F21 - F28 Funktionen :")); print_Zahl_rechts_ln(z_lok_F21);
Serial.print(F("F29 - F36 Funktionen :")); print_Zahl_rechts_ln(z_lok_F29);
Serial.print(F("Zubehör-Befehle :")); print_Zahl_rechts_ln(z_acc);
Serial.print(F("Dekoder-Reset-Befehle :")); print_Zahl_rechts_ln(z_dec_reset);
Serial.print(F("Zubehör-CV-Befehle :")); print_Zahl_rechts_ln(z_acc_cv);
Serial.print(F("Lok-CV-Befehle :")); print_Zahl_rechts_ln(z_lok_cv);
Serial.print(F("Programmiergleisbefehle:")); print_Zahl_rechts_ln(z_prg_CV);
Serial.print(F("Acknowledgments :")); print_Zahl_rechts_ln(z_ack);
Serial.print(F("Counter Lok :")); print_Zahl_rechts_ln(Lok_counter);
Serial.print(F("Counter Acc :")); print_Zahl_rechts_ln(Acc_counter);
break;
case 63: // ?
Serial.println(); Serial.println(F("Tastaturbefehle für den seriellen Monitor:")); Serial.println();
Serial.print (F("1 = Anzeige Loks ein/aus "));
if (Anzeige_Loks) Serial.println("ein");
else Serial.println("aus");
Serial.print (F("2 = Anzeige Zubehör ein/aus "));
if (Anzeige_Acc) Serial.println("ein");
else Serial.println("aus");
Serial.print (F("3 = Anzeige CV-Befehle ein/aus "));
if (Anzeige_CV) Serial.println("ein");
else Serial.println("aus");
Serial.print (F("4 = Nur neue Lok-Pakete anzeigen ein/aus "));
if (puffern_Lok) Serial.println("ein");
else Serial.println("aus");
Serial.print (F("5 = Nur neue Zubehör-Pakete anzeigen ein/aus "));
if (puffern_Acc) Serial.println("ein");
else Serial.println("aus");
Serial.print (F("6 = Nur neue CV-Befehle ein/aus "));
if (puffern_CV) Serial.println("ein");
else Serial.println("aus");
Serial.println(F("7 = Statistik anzeigen"));
Serial.println(F("? = Befehle anzeigen"));
break;
}
Serial.println(" ");
}
}
//-------------------------------------------------------------------------------------------------------
// This function is called by the NmraDcc library when a DCC ACK needs to be sent
// Calling this function should cause an increased 60ma current drain on the power supply for 6ms to ACK a CV Read
void notifyCVAck()
//-------------------------------------------------------------------------------------------------------
{
digitalWrite( DccAckPin, HIGH );
delay( 6 );
digitalWrite( DccAckPin, LOW );
z_ack++;
}
//-------------------------------------------------------------------------------------------------------
void notifyDccMsg(DCC_MSG * Msg)
//-------------------------------------------------------------------------------------------------------
{
// LED toggeln
blinker--;
if (blinker == 0)
{
digitalWrite(13, !digitalRead(13));
blinker = 5;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Alle gefunden Bytes XOR-verknüpfen; muss 0 ergeben, dann wurde ein gültiger Befehl gefunden!
//-------------------------------------------------------------------------------------------------------------------------------
pktByteCount = Msg->Size; // Anzahl gefundene Bytes ohne Präambel aber incl. Prüfbyte !!!
checksum = 0; // Wir starten mit 0
z_bytes = z_bytes + pktByteCount;
for (byte n = 0; n < pktByteCount; n++)
{
checksum ^= Msg->Data[n];
}
if (checksum)
{
z_invalid++;
return; // Ungültige Checksumme --> nix tun !
}
//-------------------------------------------------------------------------------------------------------------------------------
// Start Dekodierung
//-------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------
// Idle Kommando
//-------------------------------------------------------------------------------------------------------------------------------
if (Msg->Data[0] == B11111111)
{
z_idle++;
return; // Idle packet
}
//-------------------------------------------------------------------------------------------------------------------------------
// Reset Befehl zur Einleitung der CV-Programmierung
//-------------------------------------------------------------------------------------------------------------------------------
if (Msg->Data[0] == 0)
{
z_dec_reset++;
command = Msg->Data[0];
CV_address = Msg->Data[1];
CV_value = Msg->Data[2];
// Nur verarbeiten, wenn neuer Befehl!
if (!(((CV_value == CV_value_alt) && (CV_address == CV_address_alt) && (command == command_alt)) && puffern_CV))
{
Serial.print(F("Prg Dekoder-Reset-Befehl"));
print_spaces(43);
printPacket(Msg);
command_alt = command;
CV_address_alt = CV_address;
CV_value_alt = CV_value;
}
return;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Programmiermodus auf dem Programmiergleis ohne Adresse !
//-------------------------------------------------------------------------------------------------------------------------------
if ((Msg->Data[0] & B11110000) == B01110000)
{
/* Service Mode.Prog: [preamble] 0 [0111CCVV] 0 [VVVVVVVV] 0 [DDDDDDDD] 0 [EEEEEEEE] 1
CC = Command
VV VVVVVVVV = 10 bit CV Number
DDDDDDDD = New Value (8 bit)
EEEEEEEE = Checksum
Lesen/Schreiben auf dem Prg.gleis geht ohne Dekoderadresse !
Schreiben: z.B. CV 6, Wert 20 (--> 4 Bytes ohne Präambel und Trennbits!)
Byte 0 Byte 1 Byte 2 Byte 3
01111100 -----101 ---10100 10000001
0111CCVV VVVVVVVV DDDDDDDD EEEEEEEE
11 = Schreiben
01 = Lesen/Überprüfen
10 = 10 Bit Manipulation
V = CV - 1 --> 5 = CV 5 + 1 = CV6 !
D = 20 */
z_prg_CV++;
// Nur verarbeiten, wenn neuer Befehl!
command = Msg->Data[0] & B00001100; // 2 Bits enthalten den Schreib-/ Verify-Befehl etc.
CV_address = ((Msg->Data[0] & B00000011) * 256) + Msg->Data[1] + 1; // Nummer der CV
CV_value = Msg->Data[2]; // CV-Wert für das Schreiben
if (!(((CV_value == CV_value_alt) && (CV_address == CV_address_alt) && (command == command_alt)) && puffern_CV))
{
decoderType = 255; // vorsichtshalber mal auf einen dämlichen Wert setzen, damit weiter unten nix passiert
Serial.print("Prg CV");
CV_address = ((Msg->Data[0] & B00000011) * 256) + Msg->Data[1] + 1;
Serial.print(CV_address);
if (CV_address < 1000) Serial.print(" ");
if (CV_address < 100) Serial.print(" ");
if (CV_address < 10) Serial.print(" ");
Serial.print(" ");
switch (Msg->Data[0] & B00001100)
{
/* Die für den Befehlstyp (xxxx-KKxx) im ersten Befehlsbyte festgelegten Werte sind:
KK = 00 – reserviert
KK = 01 – Byte Überprüfen
KK = 11 – Byte Schreiben
KK = 10 – Bit Manipulation*/
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
case B00000100: // Verify Byte
Serial.print(F("Lese CV"));
print_spaces(49);
break;
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
case B00001100: // Write Byte
Serial.print(F("Schreibe CV ="));
if (Msg->Data[2] < 100) Serial.print(" ");
if (Msg->Data[2] < 10) Serial.print(" ");
Serial.print(" ");
Serial.print(Msg->Data[2]);
print_spaces(39);
break;
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
case B00001000: // Bit Write
// 0111-10VV VVVV-VVVV 111K-DBBB EEEE-EEEE --> im Programmiermodus für Zugriffe auf einzelne Bits
// K = 1 – Bit Schreiben
// K = 0 – Bit Überprüfen
if (Msg->Data[2] & B00010000)
{
Serial.print("Schreibe Bit #");
Serial.print(Msg->Data[2] & B00000111);
Serial.print(" = ");
Serial.print((Msg->Data[2] & B00001000) >> 3);
print_spaces(36);
}
else
{
Serial.print("Lese Bit #");
Serial.print(Msg->Data[2] & B00000111);
print_spaces(42);
}
break;
}
printPacket(Msg);
command_alt = command;
CV_address_alt = CV_address;
CV_value_alt = CV_value;
return;
}
}
else
{
//-------------------------------------------------------------------------------------------------------------------------------
// 0xxx-xxxx --> bit7=0 -> Lok Dekoder kurze Adresse
//-------------------------------------------------------------------------------------------------------------------------------
if (!bitRead(Msg->Data[0], 7))
{
decoderType = 0; // Lok
decoderAddress = Msg->Data[0]; // kurze Adresse
Befehls_Byte = Msg->Data[1];
// Aufteilung der gefundenen Bytes auf Befehle (Was soll der Dekoder tun?) und Funktionen (Wie soll er es tun?)
if ((Befehls_Byte & B11100000) == B10000000)
{
Befehl = B00000100; // 100 = F0 - F4
Funktion = Befehls_Byte & B00011111; // x-xxxx = F0 F4 - F1
z_lok_F0++;
}
else if ((Befehls_Byte & B11110000) == B10110000)
{
Befehl = B00001011; // 1011 = F5 - F8
Funktion = Befehls_Byte & B00001111; // xxxx = F8 - F5
z_lok_F5++;
}
else if ((Befehls_Byte & B11110000) == B10100000)
{
Befehl = Befehls_Byte >> 4; // 1010 = F9 - F12
Funktion = Befehls_Byte & B00001111; // xxxx = F12 - F9
z_lok_F9++;
}
else if (Befehls_Byte == B11011110)
{
Befehl = Befehls_Byte; // 1101-1110 = F13 - F20
Funktion = Msg->Data[2]; // xxxx-xxxx = F20 - F13
z_lok_F13++;
}
else if (Befehls_Byte == B11011111)
{
Befehl = Befehls_Byte; // 1101-1111 = F21 - F28
Funktion = Msg->Data[2]; // xxxx-xxxx = F28 - F21
z_lok_F21++;
}
else if (Befehls_Byte == B11011000)
{
Befehl = Befehls_Byte; // 1101-1000 = F29 - F36
Funktion = Msg->Data[2]; // xxxx-xxxx = F36 - F29
z_lok_F29++;
}
else if (((Befehls_Byte & B11000000) == 0) && (pktByteCount == 3))
{
Befehl = 0; // 00RSSSSS
Funktion = Befehls_Byte; // Speed 28 Stufen
z_lok_speed++;
}
else if (((Befehls_Byte & B11000000) == 64) && (pktByteCount == 4)) // Adr 10000 ???
{
Befehl = 0; // 00RSSSSS
Funktion = Befehls_Byte; // Speed 28 Stufen
z_lok_speed++;
}
else if ((Befehls_Byte == B00111111) && (pktByteCount == 4))
{
Befehl = Befehls_Byte; // 00111111
Funktion = Msg->Data[2]; // RSSSSSSS Speed 127 Stufen
z_lok_speed++;
}
}
else
{
//-------------------------------------------------------------------------------------------------------------------------------
// 11xx-xxxx --> bit7 = 1 AND bit6 = 1 -> Lok Dekoder lange Adresse
//-------------------------------------------------------------------------------------------------------------------------------
if (bitRead(Msg->Data[0], 6))
{
// 11xx-xxxx
decoderAddress = 256 * (Msg->Data[0] & B00111111) + Msg->Data[1];
Befehls_Byte = Msg->Data[2];
decoderType = 0;
// Aufteilung der gefundenen Bytes auf Befehle und Funktionen
if ((Befehls_Byte & B11100000) == B10000000)
{
Befehl = B00000100; // 100 = F0 - F4
Funktion = Befehls_Byte & B00011111; // x-xxxx = F0 F4 - F1
z_lok_F0++;
}
else if ((Befehls_Byte & B11110000) == B10110000)
{
Befehl = B00001011; // 1011 = F5 - F8
Funktion = Befehls_Byte & B00001111; // xxxx = F8 - F5
z_lok_F5++;
}
else if ((Befehls_Byte & B11110000) == B10100000)
{
Befehl = B00001010; // 1010 = F9 - F12
Funktion = Befehls_Byte & B00001111; // xxxx = F12 - F9
z_lok_F9++;
}
else if (Befehls_Byte == B11011110)
{
Befehl = Befehls_Byte; // 1101-1110 = F13 - F20
Funktion = Msg->Data[3]; // xxxx-xxxx = F20 - F13
z_lok_F13++;
}
else if (Befehls_Byte == B11011111)
{
Befehl = Befehls_Byte; // 1101-1111 = F21 - F28
Funktion = Msg->Data[3]; // xxxx-xxxx = F28 - F21
z_lok_F21++;
}
else if (Befehls_Byte == B11011000)
{
Befehl = Befehls_Byte; // 1101-1000 = F29 - F36
Funktion = Msg->Data[2]; // xxxx-xxxx = F36 - F29
z_lok_F29++;
}
else if (((Befehls_Byte & B11000000) == 0) && (pktByteCount == 4))
{
Befehl = 0; // 00RSSSSS
z_lok_speed++;
Funktion = Befehls_Byte; // Speed 28 Stufen
}
else if (((Befehls_Byte & B11000000) == 64) && (pktByteCount == 4)) // Adr 10000 ???
{
Befehl = 0; // 00RSSSSS
Funktion = Befehls_Byte; // Speed 28 Stufen
z_lok_speed++;
}
else if ((Befehls_Byte == B00111111) && (pktByteCount == 5))
{
Befehl = Befehls_Byte; // 00111111
Funktion = Msg->Data[3]; // RSSSSSSS Speed 127 Stufen
z_lok_speed++;
}
}
//-------------------------------------------------------------------------------------------------------------------------------
else //bit7=1 AND bit6=0 -> Accessory Decoder
{
// 10xx-xxxx
//-------------------------------------------------------------------------------------------------------------------------------
decoderAddress = Msg->Data[0] & B00111111;
Befehls_Byte = Msg->Data[1];
decoderType = 1;
}
}
}
//-------------------------------------------------------------------------------------------------------------------------------
if (decoderType == 1) // Accessory Basic
{
if (Anzeige_Acc && (Msg->Size != 6) )
{
z_acc++;
if ((Befehls_Byte & B10000000) && (Msg->Size == 3)) // Steuerbefehl für Zubehör Dekoder (Basic Accessory)
{
// die Zentrale sendet für Magnetartikel (accessories) immer ein Paket bestehend aus Adresse (= Signal), Richtung (rot/grün) und ein/aus (Spule).
// Zunächst wird eine Richtung eingeschaltet, dazu wird das DCC-Telegramm ggf. mehrfach wiederholt
// Bei der ECOS wird nach einer einstellbaren Zeit pro "Magnetartikel" ein Ausschaltbefehl (auch mehrfach) hinterhergeschickt
/* Für Zubehör-/Weichendekoder sendet die DCC-Zentrale 3 Bytes:
Byte 1 Byte 2 Byte 3
__ __ __
1 0 A7 A6 A5 A4 A3 A2 : 1 AA A9 A8 P A1 A0 R : C7 C6 C5 C4 C3 C2 C1 C0
- AA..A0 sind die 11 Bit Adresse eines Zubehördekoders ("Addr")
- P = Power (0 = off, 1 = on) ("OutputPower"), bei Spulenantrieben von Weichen sind das Spule 1 und Spule 2
- R = Schaltrichtung (in welche Richtung "Direction" sich der Servo bewegt, 0 = rot, 1 = grün)
- C7..C0 = Checkbyte (Byte_3 = Byte_1 XOR Byte_2)
- Das 1. Byte beginnt immer mit "10" --> 10XX XXXX (Bit#7 = 1, Bit#6 = 0)
- Das 2. Byte beginnt immer mit "1" --> 1XXX XXXX (Bit#7 = 1)
- Das 3. Byte dient der überprüfung der gesendeten Informationen: Byte_3 = Byte_1 XOR Byte_2
- Die 11 Bit Adresse des zu steuernden Spulenpaares entsteht aus den 11 Adress-Bits (AA ... A0). Dabei ist zu
beachten, dass die Bits AA, A9, A8 invertiert im ursprünglichen DCC-Paket abgebildet sind.
*/
decoderAddress = (((~Befehls_Byte)&B01110000) << 2) + decoderAddress;
Ausgang = (Befehls_Byte & B00000110) >> 1;
Spule = (bitRead(Befehls_Byte, 3));
weichenadresse = (decoderAddress - 1) * 4 + Ausgang + 1;
if (puffern_Acc)
{
//-------------------------------------------------------------------------------------------------------------------------------
// alle Werte im Puffer ausgeben
// Schauen im Array, ob die gefundenen Bytes schon einmal ausgegeben wurden
if (Acc_counter > 0)
{
for (byte j = 0; j < Acc_counter; j++)
{
Paket_bekannt_A = 0;
if (Acc_received [j].ADR == weichenadresse)
{
Paket_bekannt_A++;
}
if (Acc_received [j].DIR == Ausgang)
{
Paket_bekannt_A++;
}
if (Acc_received [j].COIL == Spule)
{
Paket_bekannt_A++;
}
if (Paket_bekannt_A == 3)
{
return; // nix machen, keine Ausgabe im seriellen Monitor: war lediglich eine Wiederholung !
}
}
}
if (Acc_counter > 0)
{
// Pufferzeilen mit der gleichen Adresse rauslöschen
for (byte j = 0; j < Acc_counter; j++)
{
if (Acc_received [j].ADR == weichenadresse)
{
// Befehl mit der gleichen Adresse aus der Puffertabelle löschen!
// dazu beginnend mit der Zeile j alle weiter hinten vorziehen, bis Pufferende oder mindestens bis Acc_counter
for (byte k = j; k < min (Acc_counter, bufferSizeAcc - 1); k++)
{
Acc_received [k].ADR = Acc_received [k + 1].ADR;
Acc_received [k].DIR = Acc_received [k + 1].DIR;
Acc_received [k].COIL = Acc_received [k + 1].COIL;
}
// }
Acc_counter--; // ein Eintrag wurde gelöscht
}
}
}
// eine Zeile im Acc_received mit neuem Paket befüllen
if (Acc_counter < bufferSizeAcc)
{
Acc_received [Acc_counter].ADR = weichenadresse;
Acc_received [Acc_counter].DIR = Ausgang;
Acc_received [Acc_counter].COIL = Spule;
Acc_counter++;
}
else
{
// ersten ältesten Wert löschen und alle anderen Wertepaare nach links rücken
for (byte j = 0; j < bufferSizeAcc - 1; j++)
{
Acc_received [j].ADR = Acc_received [j + 1].ADR;
Acc_received [j].DIR = Acc_received [j + 1].DIR;
Acc_received [j].COIL = Acc_received [j + 1].COIL;
}
// den letzten Eintrag nun dem neuen Paket befüllen
Acc_received [bufferSizeAcc - 1].ADR = weichenadresse;
Acc_received [bufferSizeAcc - 1].DIR = Ausgang;
Acc_received [bufferSizeAcc - 1].COIL = Spule;
Acc_counter = bufferSizeAcc;
}
}
//-------------------------------------------------------------------------------------------------------------------------------
// Zubehör-Daten ausgeben
Serial.print(F("Weichen-Adresse "));
lcd.setCursor(0, 3);
lcd.write(" ");
lcd.setCursor(0, 3);
lcd.write("W ");
if ((decoderAddress - 1) * 4 + Ausgang + 1 < 10) Serial.print(" ");
Serial.print((decoderAddress - 1) * 4 + Ausgang + 1);
lcd.print((decoderAddress - 1) * 4 + Ausgang + 1, DEC);
Serial.print(" (");
lcd.write(" (");
if (decoderAddress < 10) Serial.print(" ");
Serial.print(decoderAddress);
lcd.print(decoderAddress, DEC);
Serial.print(" : ");
lcd.write(":");
Serial.print(Ausgang + 1);
lcd.print(Ausgang + 1, DEC);
Serial.print(")");
lcd.write(")");
if (bitRead(Befehls_Byte, 0)) {
Serial.print(" A");
lcd.write(" A");
}
else {
Serial.print(" B");
lcd.write(" B");
}
if (bitRead(Befehls_Byte, 3)) {
Serial.print(" On ");
lcd.write(" On ");
}
else {
Serial.print(" Off");
lcd.write(" Off");
}
print_spaces(36);
}
else // Accessory Extended NMRA --> noch nicht getestet !!!
{
Serial.print("Acc Ext ");
decoderAddress = (decoderAddress << 5) + ((Befehls_Byte & B01110000) >> 2) + ((Befehls_Byte & B00000110) >> 1);
Serial.print(decoderAddress);
Serial.print(" Asp ");
Serial.print(Msg->Data[2], BIN);
}
printPacket(Msg);
}
//----------------------------------------------------------------------------------------------------------------------------------------
// CV-Befehle für Schaltdekoder POM
if (Anzeige_CV && (Msg->Size == 6)) // 6 Bytes --> d.h. CV-Befehle für Schaltdekoder !
{
z_acc_cv++;
/* ECOS: POM-Schaltartikel POM-Adresse 12, CV 6, Wert 20 Schreiben: (6 Bytes ohne Präambel und Trennbits!)
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5
10001100 11110000 11101100 -----101 ---10100 10000001
___ 3 MSB der Adresse sind invertiert !
10AAAAAA 1AAACDDD 1110CCVV VVVVVVVV DDDDDDDD EEEEEEEE
11 = Adresse 12
11 = Schreiben
10 = Lesen
V = CV + 1 --> 5 + 1 = 6
20
Lesen:
10001100 11110000 11100100 -----101 -------0 10011101
Bas.Op.Mode.Prog [preamble]0[10AAAAAA]0[1AAACDDD]0[CVACCESS]0[EEEEEEEE]1
AAAAAA AAA1DDD = Output Address
AAAAAA AAA0000 = Decoder Address
CVACCESS = DCC Programming CMD
EEEEEEEE = Checksum
CVACCESS [1110CCVV]0[VVVVVVVV]0[DDDDDDDD]
CC = Command
CC = 01 Verify Byte
CC = 11 Write Byte
CC = 10 Bit Manipulation
VV VVVVVVVV = CV Number
DDDDDDDD = New Value
EEEEEEEE = Checksum
*/
decoderAddress = (((~Msg->Data[1]) & B01110000) << 2) + (Msg->Data[0] & B00111111); // Adresse ist in 2 Bytes kodiert, MSB invertiert in Byte1 (Bit4-6) und der Rest in Byte0 (Bit 0-5)
command = Msg->Data[2] & B00001100; // 2 Bits enthalten den Schreib-/ Verify-Befehl etc.
CV_address = ((Msg->Data[2] & B00000011) * 256) + Msg->Data[3] + 1; // Nummer der CV
CV_value = Msg->Data[4]; // CV-Wert für das Schreiben
if (!(((decoderAddress == decoderAddress_alt) && (CV_address == CV_address_alt) && (command == command_alt)) && puffern_CV)) // immer ausgeben
{
switch (command)
{
case 12: //B1100 --> 12 ==> Schreiben
Serial.print(F("Acc " ));
Serial.print(decoderAddress);
if (decoderAddress < 100) Serial.print(" ");
if (decoderAddress < 10) Serial.print(" ");
Serial.print(" CV ");
Serial.print(CV_address); Serial.print(" ");
if (CV_address < 100) Serial.print(" ");
if (CV_address < 10) Serial.print(" ");
Serial.print(F("Schreibe CV ="));
if (CV_value < 100) Serial.print(" ");
if (CV_value < 10) Serial.print(" ");
Serial.print(" ");
Serial.print(CV_value);
print_spaces(33);
break;
case 4: //B0100 --> 4 ==> Lesen
Serial.print(F("Acc " ));
Serial.print(decoderAddress);
if (decoderAddress < 100) Serial.print(" ");
if (decoderAddress < 10) Serial.print(" ");
Serial.print(" CV ");
Serial.print(CV_address); Serial.print(" ");
if (CV_address < 100) Serial.print(" ");
if (CV_address < 10) Serial.print(" ");
Serial.print(F("Lese CV "));
print_spaces(42);
break;
case 8: //B1000 ==> Bit-Gefummel !
// ... 111K-DBBB EEEE-EEEE --> Zugriffe auf einzelne Bits
// K = 1 – Bit Schreiben
// K = 0 – Bit Überprüfen
if (Msg->Data[pktByteCount - 2] & B00010000)
{
Serial.print("Schreibe Bit #");
Serial.print(Msg->Data[pktByteCount - 2] & B00000111);
Serial.print(" = ");
Serial.print((Msg->Data[pktByteCount - 2] & B00001000) >> 3);
print_spaces(36);
}
else
{
Serial.print("Lese Bit #");
Serial.print(Msg->Data[2] & B00000111);
print_spaces(41);
}
break;
}
printPacket(Msg);
decoderAddress_alt = decoderAddress;
CV_address_alt = CV_address;
command_alt = command;
}
return;
}
}
//-------------------------------------------------------------------------------------------------------------------------------
else if (decoderType == 0) // --> Lok / Funktionsdekoder
{
/* Aufbau der Fahrbefehle:
Fahrbefehl, kurze Adressen
Byte 7 6 5 4 3 2 1 0
---------------------------------------------------------------------------
0 0 A A A A A A A Adresse 7 Bit
1 0 0 R S S S S S R = Fahrtrichtung, Sn = Geschwindigkeit 28 Stufen
2 XOR Prüfbyte
0 0 A A A A A A A Adresse 7 Bit
1 0 0 1 1 1 1 1 1 Befehlsbyte 0x3F
2 R S S S S S S S R = Fahrtrichtung, Sn = Geschwindigkeit 127 Stufen
3 XOR Prüfbyte
- Fahrbefehl 28 Stufen: unsigned char speed = ((Byte[1] & 0x0F) << 1) + ((Byte[1] & 0x10) >> 4);
- Fahrbefehl 127 Stufen: unsigned char speed = Byte[2] & 0x7F;
Fahrbefehl, lange Adressen
Byte 7 6 5 4 3 2 1 0
---------------------------------------------------------------------------
0 1 1 A A A A A A Adresse 6 Bit
1 A A A A A A A A Adresse 8 Bit
2 0 0 R S S S S S R = Fahrtrichtung, Sn = Geschwindigkeit 28 Stufen
3 XOR Prüfbyte
Byte 7 6 5 4 3 2 1 0
---------------------------------------------------------------------------
0 1 1 A A A A A A Adresse 6 Bit
1 A A A A A A A A Adresse 8 Bit
2 0 0 1 1 1 1 1 1 Befehlsbyte 0x3F
3 R S S S S S S S R = Fahrtrichtung, Sn = Geschwindigkeit 127 Stufen
4 XOR Prüfbyte
- Fahrbefehl 28 Stufen: unsigned char speed = ((Byte[2] & 0x0F) << 1) + ((Byte[2] & 0x10) >> 4);
- Fahrbefehl 127 Stufen: unsigned char speed = Byte[3] & 0x7F;
*/
if (Anzeige_Loks )
// zu Testzwecken filtern auf bestimmte Lok-Adresse
// if (Anzeige_Loks && (decoderAddress == 42 || decoderAddress == 888))
// if (Anzeige_Loks && (decoderAddress < 25))
{
byte instructionType = Befehls_Byte >> 5;
/* Bits 7-6-5 sind relevant, ergibt 0 - 7 = 8 unterschiedliche Befehlstypen
0 = Control ??
1 = 0011-1111 128 Geschwindigkeitsstufen-Befehl, 0011-1110 Sonderbetriebsarten-Befehl
2 = 01xx-xxxx Basis Geschwindigkeits- und Richtungsbefehl rückwärts 28 Stufen
3 = 01xx-xxxx Basis Geschwindigkeits- und Richtungsbefehl vorwärts 28 Stufen
4 = Lok Funktionen F0, F1 - F4
5 = Lok-Funktionen F5 - F12
6 = Lok-Funktionen F13 - F36
7 = CVs */
if (puffern_Lok)
{
// Schauen im Array, ob die gefundenen Bytes schon einmal ausgegeben wurden
if (Lok_counter > 0)
{
for (byte j = 0; j < Lok_counter; j++)
{
Paket_bekannt_L = 0;
if (Lok_received [j].ADR == decoderAddress)
{
Paket_bekannt_L++;
}
if (Lok_received [j].ORDER == Befehl)
{
Paket_bekannt_L++;
}
if (Lok_received [j].FUNC == Funktion)
{
Paket_bekannt_L++;
}
if (Paket_bekannt_L == 3)
{
return; // nix machen, keine Ausgabe im seriellen Monitor: war lediglich eine Wiederholung !
}
}
}
if (Lok_counter > 0)
{
// Pufferzeilen mit der gleichen Adresse und dem gleichen Befehl rauslöschen
for (byte j = 0; j < Lok_counter; j++)
{
if (Lok_received [j].ADR == decoderAddress && Lok_received [j].ORDER == Befehl) // && Lok_received [j].FUNC == Funktion )
{
for (byte k = j; k < min (Lok_counter, bufferSizeLok - 1); k++)
{
Lok_received [k].ADR = Lok_received [k + 1].ADR;
Lok_received [k].ORDER = Lok_received [k + 1].ORDER;
Lok_received [k].FUNC = Lok_received [k + 1].FUNC;
}
Lok_counter--; // ein Eintrag wurde gelöscht
}
}
}
// eine Zeile im Acc_received mit neuem Paket befüllen
if (Lok_counter < bufferSizeLok)
{
Lok_received [Lok_counter].ADR = decoderAddress;
Lok_received [Lok_counter].ORDER = Befehl;
Lok_received [Lok_counter].FUNC = Funktion;
Lok_counter++;
}
else
{
// ersten ältesten Wert löschen und alle anderen Wertepaare nach links rücken
for (byte j = 0; j < bufferSizeLok - 1; j++)
{
Lok_received [j].ADR = Lok_received [j + 1].ADR;
Lok_received [j].ORDER = Lok_received [j + 1].ORDER;
Lok_received [j].FUNC = Lok_received [j + 1].FUNC;
}
// den letzten Eintrag nun mit dem neuen Paket befüllen
Lok_received [bufferSizeLok - 1].ADR = decoderAddress;
Lok_received [bufferSizeLok - 1].ORDER = Befehl;
Lok_received [bufferSizeLok - 1].FUNC = Funktion;
Lok_counter = bufferSizeLok;
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Serial.print("Lok ");
lcd.setCursor(0, 1);
lcd.write(" ");
lcd.setCursor(0, 2);
lcd.write(" ");
lcd.setCursor(0, 1);
lcd.write("L");
if (decoderAddress < 10) {
Serial.print(" ");
lcd.print(" ");
}
else if (decoderAddress < 100) {
Serial.print(" ");
lcd.print(" ");
}
else if (decoderAddress < 1000) {
Serial.print(" ");
lcd.print(" ");
}
else if (decoderAddress < 10000) {
Serial.print(" ");
lcd.print("");
}
Serial.print(decoderAddress); Serial.print(" "); Lokname(decoderAddress); Serial.print(" ");
lcd.print(decoderAddress, DEC);
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
switch (instructionType)
{
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
case 0:
// 000x-xxxx
Serial.print(" Control ");
break;
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
case 1:
// Advanced Operations
// 001x-xxxx
if (Befehls_Byte == B00111111) //128 speed steps
{
//0011-1111 128 Geschwindigkeitsstufen-Befehl
// Richtung auswerten Bit 7 "R" im vorletzten Byte
if (bitRead(Msg->Data[pktByteCount - 2], 7))
{
Serial.print(" -->> ");
lcd.print(" -->> ");
}
else
{
Serial.print(" <<-- ");
lcd.print(" <<-- ");
}
// Geschwindigkeit Bit 6 - 0 vom vorletzten Byte
byte speed = Msg->Data[pktByteCount - 2] & B01111111;
if (speed == 0)
{
Serial.print(" Stopp "); // wenn = 0, dann Stoppbefehl
lcd.print(" S"); // wenn = 0, dann Stoppbefehl
}
else if (speed == 1)
{
Serial.print(" Nothalt "); //