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