377 lines
17 KiB
C++
377 lines
17 KiB
C++
// 31 mars 2022 : Demo code for 2 MFX locs, 1 Turn (Adr=5) & S88 (16 inputs)
|
|
// MFX est une marque déposée par Märklin
|
|
// Ce code minimaliste a besoin de la Gleisbox pour lire l'UID présent dans chaque locomotive
|
|
// voir procédure dans §4.1 de http://gelit.ch/Train/Raildue_F.pdf
|
|
|
|
#include <DueTimer.h>
|
|
|
|
// Pin D0-D1 used by USB
|
|
const int Pin2_L293 = 2; // voir §10 de http://gelit.ch/Train/DirectMM2.pdf
|
|
const int Pin7_L293 = 3;
|
|
const int PowerPin = 8; // pin 1 L293 (enable H-Bridge)
|
|
|
|
const int CLOCK = 10; // S88 voir §5.3 de http://gelit.ch/Train/Raildue_F.pdf
|
|
const int DATA = 11;
|
|
const int RESET = 12;
|
|
const int LOAD = 13;
|
|
|
|
const int Red_Led = 9; // Power Status
|
|
|
|
const int S88_Nb = 16; // 1 module Littfinski RM-88-N-Opto
|
|
|
|
const byte LoMax = 2; // Taille du tableau des locomotives
|
|
|
|
const byte Rob = 1; // Robel 39548
|
|
const byte BLS = 2; // BLS 29486
|
|
|
|
// Toutes ces variables sont globales et présentes en PERMANENCE en RAM
|
|
volatile byte B[100]; // MFX Buffer (type BYTE to store Length)
|
|
volatile bool Busy; // many MFX Writers (Zentrale, Periodic, getSID, Speed, ...) --> only 1 at the same time (Busy=0 by Interrupt)
|
|
volatile int Ti; // MM2_Bit
|
|
volatile uint16_t crc; // Krauss p13
|
|
volatile bool F; // MFX First bit always 1
|
|
volatile byte I; // Interrupt buffer index B[I]);
|
|
volatile bool Le; // MFX Level (polarité)
|
|
volatile byte Len; // MFX Frame Length
|
|
volatile byte LoA; // only 1 Loc is active for Speed & Direction
|
|
volatile byte LoR; // Refresh
|
|
volatile byte N; // MFX Interrupt
|
|
volatile byte NSync; // 3 caract de synchro MFX au min
|
|
volatile bool P[S88_Nb+10]; // S88 Physical Register
|
|
volatile byte Pa[18+4]; // MM2 Packet for Turn
|
|
volatile bool Power;
|
|
volatile byte S; // State in loop
|
|
volatile int SM; // MFX State Machine
|
|
volatile byte StufN; // Stuffing bit
|
|
volatile bool ToSend;
|
|
volatile bool Trace; // Debug
|
|
volatile bool TuCmd; // Turn Cmd
|
|
volatile bool TurnVal; // Turn Value
|
|
volatile byte TurnAdr; // Turn Adress
|
|
volatile unsigned long TZ,TP,T_S88,Time;
|
|
volatile byte Za; // Zahler in Zentrale Frame
|
|
|
|
struct LoS {
|
|
bool Dir; // Direction
|
|
byte Vit; // Speed
|
|
bool Light;
|
|
byte UID0;
|
|
byte UID1;
|
|
byte UID2;
|
|
byte UID3;
|
|
};
|
|
volatile struct LoS Lo[LoMax+2];
|
|
|
|
void setup() {
|
|
byte a;
|
|
|
|
Trace=0; // debug
|
|
Serial.begin(115200); Serial.println("DirectMFX");
|
|
|
|
pinMode(PowerPin,OUTPUT); digitalWrite(PowerPin,0); Power=0;
|
|
pinMode(Red_Led,OUTPUT); digitalWrite(Red_Led,0);
|
|
pinMode(Pin2_L293,OUTPUT); pinMode(Pin7_L293,OUTPUT);
|
|
digitalWrite(Pin2_L293,0); digitalWrite(Pin7_L293,1);
|
|
|
|
Lo[Rob].UID0=0x73; Lo[Rob].UID1=0xfb; Lo[Rob].UID2=0x17; Lo[Rob].UID3=0x40; //
|
|
Lo[BLS].UID0=0xF9; Lo[BLS].UID1=0xF2; Lo[BLS].UID2=0x3A; Lo[BLS].UID3=0xC1;
|
|
|
|
LoA=1; Serial.println("Robel is active");
|
|
TurnAdr=5; TurnVal=0;
|
|
|
|
pinMode(DATA, INPUT); pinMode(CLOCK, OUTPUT); pinMode(LOAD, OUTPUT); pinMode(RESET, OUTPUT);
|
|
|
|
Timer3.attachInterrupt(T); Timer3.start(50); // interrupt every 50 us (micro seconde)
|
|
|
|
Busy=0;
|
|
S=1; SM=1; N=0; NSync=0; ToSend=0; StufN=0; TuCmd=0;
|
|
Le=1; // §2.2.9 Pause mit neg Potential
|
|
Serial.println("PowerOFF - Enter to PowerON");
|
|
}
|
|
|
|
void Speed(byte Loc, byte Value) { // §3.1.2 Fahren mit 8 steps
|
|
byte a,b;
|
|
if (Trace) {Serial.print("Speed-"); Serial.print(Loc); Serial.print("-"); Serial.println(Value);}
|
|
do {} while (Busy); Busy=1;
|
|
Len=0;
|
|
Adr(Loc);
|
|
B[10]=0; B[11]=0; B[12]=0; Len=Len+3;
|
|
if (Lo[Loc].Dir) {B[13]=1;} else {B[13]=0;} Len=Len+1; // Direction
|
|
b=2; for (a=0; a<3; a++) {B[a+14]=bitRead(Value,b); b--;} Len=Len+3;
|
|
B[0]=Len; // Length in FIRST BYTE
|
|
CRC(); ToSend=1;
|
|
}
|
|
|
|
void Adr(byte Loc) { // set SID
|
|
byte a,b;
|
|
B[1]=1; B[2]=0; // always 7 bit adr (B[0] used for LENGTH)
|
|
b=6; for (a=0; a<8; a++) {B[a+3]=bitRead(Loc,b); b--;}
|
|
Len=2+7;
|
|
}
|
|
|
|
void Func(byte Loc, byte Fu, bool Value) { // §3.1.6 Einzel Funktion
|
|
byte a,b;
|
|
if (Trace) {Serial.print("Func-"); Serial.print(Loc); Serial.print("-"); Serial.print(Fu); Serial.print("-"); Serial.println(Value);}
|
|
do {} while (Busy); Busy=1;
|
|
Len=0;
|
|
Adr(Loc);
|
|
B[10]=1; B[11]=0; B[12]=0; Len=Len+3;
|
|
b=6; for (a=0; a<8; a++) {B[a+13]=bitRead(Fu,b); b--;} Len=Len+7;
|
|
B[20]=0; B[21]=Value; Len=Len+2;
|
|
B[0]=Len; // Length in FIRST BYTE
|
|
CRC(); ToSend=1;
|
|
}
|
|
|
|
void Periodic(byte Loc) { // like MS2
|
|
byte a,b;
|
|
if (Trace) {Serial.print("Periodic-"); Serial.println(Loc);}
|
|
do {} while (Busy); Busy=1;
|
|
Len=0;
|
|
Adr(Loc);
|
|
B[10]=0; B[11]=0; B[12]=1; Len=Len+3; // Fahren
|
|
if (Lo[Loc].Dir) {B[13]=1;} else {B[13]=0;} Len=Len+1;
|
|
b=2; for (a=0; a<3; a++) {B[a+14]=bitRead(Lo[Loc].Vit,b); b--;} Len=Len+3;
|
|
|
|
B[17]=0; B[18]=0; B[19]=0; B[20]=0; Len=Len+4; // only MSB
|
|
B[21]=0; B[22]=1; B[23]=1; B[24]=1; Len=Len+4; // §3.1.5 F15-F0
|
|
B[25]=0; B[26]=0; B[27]=0; B[28]=0; B[29]=0; B[30]=0; B[31]=0; B[32]=0; Len=Len+8;
|
|
B[33]=0; B[34]=0; B[35]=0; B[36]=0; B[37]=0; B[38]=0; B[39]=0; B[40]=Lo[Loc].Light; Len=Len+8;
|
|
B[0]=Len; // Length in FIRST BYTE
|
|
CRC(); ToSend=1;
|
|
}
|
|
|
|
void Adr0() {B[1]=1; B[2]=0; B[3]=0; B[4]=0; B[5]=0; B[6]=0; B[7]=0; B[8]=0; B[9]=0; Len=2+7;} // always 7 bit adr (B[0] used for LENGTH)
|
|
|
|
void getSID(byte Loc) { // fixed SID from UID
|
|
byte a,b;
|
|
if (Trace) {Serial.print("getSID-"); Serial.println(Loc);}
|
|
do {} while (Busy); Busy=1;
|
|
Len=0;
|
|
Adr0();
|
|
B[10]=1; B[11]=1; B[12]=1; B[13]=0; B[14]=1; B[15]=1; Len=Len+6; // §3.2.4 111 011 AAAAAAAAAAAAAA UID
|
|
B[16]=0; B[17]=0; B[18]=0; B[19]=0; B[20]=0; B[21]=0; B[22]=0; Len=Len+7;
|
|
b=6; for (a=0; a<7; a++) {B[a+23]=bitRead(Loc,b); b--;} Len=Len+7;
|
|
b=7; for (a=0; a<8; a++) {B[a+30]=bitRead(Lo[Loc].UID0,b); b--;} Len=Len+8; // UID
|
|
b=7; for (a=0; a<8; a++) {B[a+38]=bitRead(Lo[Loc].UID1,b); b--;} Len=Len+8;
|
|
b=7; for (a=0; a<8; a++) {B[a+46]=bitRead(Lo[Loc].UID2,b); b--;} Len=Len+8;
|
|
b=7; for (a=0; a<8; a++) {B[a+54]=bitRead(Lo[Loc].UID3,b); b--;} Len=Len+8; //=61
|
|
B[0]=Len; // Length in FIRST BYTE
|
|
CRC(); ToSend=1;
|
|
}
|
|
|
|
void Zentrale() { // p23
|
|
byte a,b;
|
|
if (Trace) {Serial.println("Zentrale");}
|
|
do {} while (Busy); Busy=1;
|
|
Len=0;
|
|
Adr0();
|
|
B[10]=1; B[11]=1; B[12]=1; B[13]=1; B[14]=0; B[15]=1; Len=Len+6;
|
|
|
|
B[16]=0; B[17]=1; B[18]=0; B[19]=0; B[20]=0; B[21]=1; B[22]=1; B[23]=1; Len=Len+8; // Zentrale UID (32 bit)
|
|
B[24]=0; B[25]=1; B[26]=1; B[27]=0; B[28]=1; B[29]=0; B[30]=1; B[31]=1; Len=Len+8;
|
|
B[32]=1; B[33]=0; B[34]=1; B[35]=1; B[36]=0; B[37]=1; B[38]=1; B[39]=1; Len=Len+8;
|
|
B[40]=1; B[41]=1; B[42]=0; B[43]=1; B[44]=1; B[45]=1; B[46]=0; B[47]=0; Len=Len+8;
|
|
|
|
B[48]=1; B[49]=0; B[50]=0; B[51]=0; B[52]=0; B[53]=0; B[54]=0; B[55]=0; Len=Len+8; // Zähler (16 bit)
|
|
b=7; for (a=0; a<8; a++) {B[a+56]=bitRead(Za,b); b--;} Len=Len+8;
|
|
|
|
B[0]=Len; // Length in FIRST BYTE
|
|
CRC(); ToSend=1;
|
|
}
|
|
|
|
|
|
void loop() {
|
|
byte a, cmd;
|
|
|
|
switch (S) {
|
|
|
|
case 0 : // stay here after PowerOFF
|
|
break;
|
|
|
|
case 1 : if (Serial.available() > 0) {cmd = Serial.read(); // start here from setup
|
|
if (cmd==13) {SM=10; delay(200); S=2;}} // send Sync & PowerON
|
|
break;
|
|
|
|
case 2 : if (Power) {delay(500); digitalWrite(Red_Led,1); Serial.println("PowerON (Enter to PowerOFF)"); S=3;}
|
|
break;
|
|
|
|
case 3 : Za=1; Zentrale(); delay(500); Zentrale(); delay(500); Zentrale(); delay(500); S=4; // §4.2
|
|
break;
|
|
|
|
case 4 : getSID(Rob); delay(200); Za++; Zentrale(); delay(200); Serial.println("getSID Robel");
|
|
getSID(BLS); delay(200); Za++; Zentrale(); delay(200); Serial.println("getSID BLS");
|
|
|
|
Lo[Rob].Vit=0; Speed(Rob,Lo[Rob].Vit); Lo[Rob].Dir=0; Lo[Rob].Light=1; Func(Rob,0,Lo[Rob].Light);
|
|
Lo[BLS].Vit=0; Speed(BLS,Lo[BLS].Vit); Lo[BLS].Dir=0; Lo[BLS].Light=1; Func(BLS,0,Lo[BLS].Light);
|
|
|
|
LoR=1;
|
|
TP=millis()+50; TZ=millis()+500; T_S88 = millis()+1000; // set Timing
|
|
S=5;
|
|
break;
|
|
|
|
case 5 :
|
|
|
|
if (millis() > T_S88) {T_S88 = millis()+1000; S88();}
|
|
if (millis() > TZ) {TZ = millis()+500; Zentrale();}
|
|
if (millis() > TP) {TP = millis()+50; Periodic(LoR);
|
|
LoR++; if (LoR==LoMax+1) {LoR=1;}} // Find Next Decoder
|
|
|
|
if (Serial.available() > 0) {
|
|
cmd = Serial.read();
|
|
if (cmd>64 && cmd<91) {Serial.println("CapsLock !!!");}
|
|
if (cmd >= '0' && cmd <= '7') {Lo[LoA].Vit=cmd-48; Speed(LoA,Lo[LoA].Vit); Serial.print("Speed="); Serial.println(Lo[LoA].Vit);}
|
|
switch (cmd) {
|
|
case '8': LoA=Rob; Serial.println("Robel"); break;
|
|
case '9': LoA=BLS; Serial.println("BLS"); break;
|
|
case 'l': if (Lo[LoA].Light) {Lo[LoA].Light=0;} else {Lo[LoA].Light=1;} Func(LoA,0,Lo[LoA].Light); Serial.println("Toggle Light"); break;
|
|
case 'd': if (Lo[LoA].Dir) {Lo[LoA].Dir=0;} else {Lo[LoA].Dir=1;} Speed(LoA,0); Serial.println("Toggle Direction"); break;
|
|
case 't': TurnVal=!TurnVal; Turn(TurnAdr,TurnVal); delay(250); Serial.print("TurnVal="); Serial.println(TurnVal); break;
|
|
case 's': Serial.print("Power="); Serial.print(Power); Serial.print(" S="); Serial.print(S); Serial.print(" SM="); Serial.println(SM);
|
|
Serial.print("Len="); Serial.print(B[0]); Serial.print(" CRC="); Serial.println(crc,HEX);
|
|
for (a=1; a<=B[0]; a++) {Serial.print(" "); Serial.print(B[a]);} Serial.println();
|
|
Serial.print("MM2= "); for (a=0; a<=18; a++) {Serial.print(Pa[a]); Serial.print(" ");} Serial.println();
|
|
Serial.print("S88 "); for (a=1; a<=16; a++) {Serial.print(a); Serial.print("="); Serial.print(P[a]); Serial.print(" ");} Serial.println();
|
|
break;
|
|
case 13 : if (Power) {S=1; SM=1; digitalWrite(PowerPin,0); Power=0; digitalWrite(Red_Led,0); Serial.println("PowerOFF");} break;
|
|
case 'h': Serial.println("Speed:0-7 Robel:8 BLS:9 l:Light d:Direction t:Turn s:Statistics"); break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void T() {
|
|
|
|
N++; // Important !
|
|
switch (SM) {
|
|
|
|
case 1 : break; // stay here
|
|
|
|
case 3 : switch (N) { // §2.2.9 "mindestens 2 Sync"
|
|
case 1 : case 3 : case 4 : case 6 : case 8 : case 9 : C(); break;
|
|
case 10 : NSync++; if (NSync>1) {N=0; SM=4;} else {N=0;} break;
|
|
}
|
|
break;
|
|
|
|
case 4 : if (N==125) {Ti=0; SM=5; // §2.2.9 Pause=6.25 ms --> 6250/50=125
|
|
Timer3.start(104);} break; // Interrupt = 104 us
|
|
|
|
case 5 : // Send First Turn Packet
|
|
if (Pa[Ti]) {C(); delayMicroseconds(85); C();} // HIGH : 85 --> Scope=91
|
|
else {C(); delayMicroseconds(9); C();} // LOW : 9 13
|
|
Ti++;
|
|
if (Ti == 18) {N=0; SM=6;}
|
|
break;
|
|
|
|
case 6 : if (N==15) {Ti=0; SM=7;} break; // §2.2.9 Pause=1.5 ms
|
|
|
|
case 7 : // Send Second Turn Packet
|
|
if (Pa[Ti]) {C(); delayMicroseconds(85); C();}
|
|
else {C(); delayMicroseconds(9); C();}
|
|
Ti++;
|
|
if (Ti == 18) {N=0; SM=8;
|
|
Timer3.start(50);} // Interrupt = 50 us
|
|
break;
|
|
|
|
case 8 : if (N==124) { // §2.2.9 Pause=6.18 ms --> 6180/50=124
|
|
TuCmd=0;
|
|
N=0; NSync=0; SM=10;}
|
|
break;
|
|
|
|
|
|
case 10 : switch (N) { // Synchro
|
|
case 1 : C(); if (!Power && S!=0) {digitalWrite(PowerPin,1); Power=1; digitalWrite(Red_Led,1);} break;
|
|
case 3 : case 4 : case 6 : case 8 : case 9 : C(); break;
|
|
|
|
case 10 : if (ToSend && NSync>3) {ToSend=0; NSync=0; StufN=0; F=1; I=1; Len=B[0]; SM=20;}
|
|
else if (TuCmd) {N=0; NSync=0; SM=3;}
|
|
else {N=0; NSync++;}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 20 : if (F) {C();} // Data stream with CRC
|
|
else {
|
|
if (B[I]) {C(); StufN++; if (StufN==8) {StufN=0; F=0; SM=30;}} // F=0; + F=!F; --> F=1
|
|
else {StufN=0;}
|
|
I++; Len--; if (Len==0) { SM=10; N=0; Busy=0;} // NEW
|
|
}
|
|
F=!F;
|
|
break;
|
|
|
|
case 30 : if (F) {C();} // Send "0" after 8 "1"
|
|
else {SM=20;}
|
|
F=!F;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void C() {if (Le) {digitalWrite(Pin2_L293,0); digitalWrite(Pin7_L293,1);} else {digitalWrite(Pin2_L293,1); digitalWrite(Pin7_L293,0);} Le=!Le;} // Change Level
|
|
|
|
void CRC() { // avoid to compute in interrupt ! --> easier for bit stuffing !
|
|
byte a,b;
|
|
crc=0x007F;
|
|
for (a=1; a<B[0]+1; a++) {bCRC(B[a]);} // CRC
|
|
for (a=0; a<8; a++) {bCRC(0);} // Krauss p13 "diese bit müssen zuerst mit 0 belegt ..."}
|
|
b=8; for (a=0; a<8; a++) {B[Len+1+a]=bitRead(crc,b-1); b--;}
|
|
B[0]=B[0]+8; // Length
|
|
}
|
|
|
|
void bCRC(bool b) { // Krauss p13
|
|
crc = (crc << 1) + b;
|
|
if ((crc & 0x0100) > 0) {crc = (crc & 0x00FF) ^ 0x07;}
|
|
}
|
|
|
|
|
|
void Turn(int dev, bool val) { // Device Value
|
|
int p[10]; int a; int q[10];
|
|
|
|
if (dev > 0 && dev < 320) {
|
|
p[0] = 1; p[1] = 2; p[2] = 1 * 4; p[3] = 3 * 4; p[4] = 3 * 3 * 4; p[5] = 3 * 3 * 3 * 4; p[6] = 3 * 3 * 3 * 3 * 4;
|
|
dev += 3;
|
|
for (a=6; a>=2; a--) {q[a]=0; if (dev>=p[a]) {q[a]++; dev=dev-p[a];} if (dev>=p[a]) {q[a]++; dev=dev-p[a];} switch (a) {case 2 : Tri(q[2],0); break; // MM2 Adr become HIGH adr
|
|
case 3 : Tri(q[3],2); break;
|
|
case 4 : Tri(q[4],4); break;
|
|
case 5 : Tri(q[5],6); break;
|
|
case 6 : Tri(q[6],8); break;
|
|
}
|
|
}
|
|
for (a=1;a>=0;a--) {q[a]=0; if (dev>=p[a]) {q[a]++; dev=dev-p[a];} switch (a) {case 0 : Tri(q[0],12); break; // Factor 4 implemented with bit 12 - 15
|
|
case 1 : Tri(q[1],14); break;
|
|
}
|
|
}
|
|
Tri(val,10); // Value
|
|
Tri(1,16); // always 1
|
|
|
|
TuCmd=1; // TURN CMD
|
|
}
|
|
}
|
|
|
|
void Tri(int v, int b) { // Value, Bit
|
|
switch (v) {
|
|
case 0 : Pa[b]=0; Pa[b+1]=0; break; // MC 145026 encoding
|
|
case 1 : Pa[b]=1; Pa[b+1]=1; break;
|
|
case 2 : Pa[b]=1; Pa[b+1]=0; break;
|
|
}
|
|
}
|
|
|
|
void S88() {
|
|
const byte TIME = 10;
|
|
int j, k;
|
|
|
|
j = 1;
|
|
digitalWrite(LOAD, HIGH); delayMicroseconds(TIME); // from Railuino
|
|
digitalWrite(CLOCK, HIGH); delayMicroseconds(TIME);
|
|
digitalWrite(CLOCK, LOW); delayMicroseconds(TIME);
|
|
digitalWrite(RESET, HIGH); delayMicroseconds(TIME);
|
|
digitalWrite(RESET, LOW); delayMicroseconds(TIME);
|
|
digitalWrite(LOAD, LOW); delayMicroseconds(TIME / 2);
|
|
P[j] = digitalRead(DATA); j++; delayMicroseconds(TIME / 2);
|
|
for (k = 1; k <= S88_Nb; k++) {
|
|
digitalWrite(CLOCK, HIGH); delayMicroseconds(TIME);
|
|
digitalWrite(CLOCK, LOW); delayMicroseconds(TIME / 2);
|
|
P[j] = digitalRead(DATA); j++; delayMicroseconds(TIME / 2);
|
|
}
|
|
}
|