This commit is contained in:
Serge NOEL
2026-02-10 11:27:18 +01:00
parent 549c9f388e
commit 4423bb2de1
175 changed files with 238087 additions and 0 deletions

376
DirectMFX.ino Normal file
View File

@@ -0,0 +1,376 @@
// 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);
}
}