/* SX1 centrale Creator: Gerard van der Sel / R. Helder Version: 2.0 Date: 05-02-2025 This software forms a bridge between a DCC signal and the Selectrix SX bus on an Arduino. I originally tried to run the complete code on one Arduino Nano, but the frequent interrupts from the DCC signal made this impossible, probably also because there are in my case other protocols on the track This means that the code runs on 2 Arduino Nano board that are connected to each other by Tx/Rx pin. One board receives the DCC signals. This code is complete based on the NMRA DCC library. When a valid DCC accesory command is received, the function notifyDccAccTurnoutOutput() is called and a message with format S<adress> or R<adress> is sent from the Tx pin to the other board. Adress is in the DCC range 0 < a < 800 The second board generates the Sx Signal. It has a buffer of 112 adresses, pre formatted for the bus signal. When the S or R command is received, the function serialEvent() interprets the message and alters this buffer with the new data, which is one bit in this buffer for a specific adress. To compile for the DCC board, uncomment DCCSIDE below To compile for the SX board, uncomment SXSIDE below Remeber to disconnect the Tx-Rx line before uploading new code ! Extra hardware needed is de DCC-Arduino board which contains the hardware to read from the DCC-bus: the DCC signal is read through an opto-coupler on pin D2 Extra hardware needed is de SX-Arduino board which contains the hardware to communicate with the SX-bus: Connect Arduino pin D3 to pin 1 and 3 of a SN74HCT04. Pin 2 and 4 are connected through a 220 Ohm resistor each to the Sx T0 line. Connect Arduino pin D4 to pin 5 of a SN74HCT04. Pin 6 is connected through a 220 Ohm resistor to the Sx T1 line. Gnd must also be connected.*/#include <Arduino.h>#include "SXcentrale.h"#include <NmraDcc.h>NmraDcc Dcc; // Interface to the DCC track for readingSXcentrale SXcntrl; // Interface to the SX-bus and track//#define DCCSIDE // uncomment to compile for the DCC board#define SXSIDE // uncomment to compile for the SX boardISR(TIMER1_OVF_vect) { // interrupt service routine SXcntrl.isr();}//------------------------------------------------------------------------------void setup() // put your setup code here, to run once://------------------------------------------------------------------------------{#ifdef DCCSIDE // initialize Nmra DCC communication // for info: https://github.com/mrrwa/NmraDcc/blob/master/NmraDcc.h Dcc.pin(0, 2, 1); Dcc.init(MAN_ID_DIY, 10, FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER, 0);#endif // initialize serial: Serial.begin(115200); //Serial.println("Start SxBridge"); //delay(1000);#ifdef SXSIDE // initialize SX-bus SXcntrl.init();#endif // initialize application pinMode(LED_BUILTIN, OUTPUT);}//------------------------------------------------------------------------------void serialEvent() {uint8_t dccAdres[2];uint8_t cmd;static bool on = false;#ifdef SXSIDE // Read all the data. Data is sent in a frame that starts with 0xFD for synchronisation // Format: S001 for setting adres 1 // R001 for resetting adres 1 while (Serial.available()) { cmd = (uint8_t)Serial.read(); Serial.print("."); if (cmd == 'S' || cmd == 'R') { uint8_t nr = Serial.readBytes(dccAdres, 3); if (nr == 3) { uint16_t adres = 100*(dccAdres[0]-'0') + 10*(dccAdres[1]-'0') + dccAdres[2]-'0'; Serial.print(cmd);Serial.println(adres); if (adres < 8*SX_ADDRESS_NUMBER) { if (cmd == 'S') SXcntrl.setbit(0, adres, 1); else SXcntrl.setbit(0, adres, 0); digitalWrite(LED_BUILTIN, on); on = !on; } } } }#endif}//------------------------------------------------------------------------------void loop() // put your main code here, to run repeatedly://------------------------------------------------------------------------------{#ifdef DCCSIDE // DCC-Kommandos empfangen Dcc.process();#endif/* DEBUG: Blink 2 outputs on Sx decoder SXcntrl.set(0, 1, 2); delay(1000); // wait for a second SXcntrl.set(0, 1, 4); delay(1000); // wait for a second*/}//------------------------------------------------------------------------------void notifyDccAccTurnoutOutput (uint16_t Addr, uint8_t Direction, uint8_t OutputPower)/* notifyDccAccTurnoutOutput() Output oriented callback for a turnout accessory decoder. * Most useful when CV29_OUTPUT_ADDRESS_MODE IS set. * OutputPower is 1 if the power is on, and 0 otherwise. * * Inputs: * Addr - Per output address. There will be 4 Addr addresses * per board for a standard accessory decoder with 4 output pairs. * Direction - Turnout direction. It has a value of 0 or 1. * Equivalent to bit 0 of the 3 DDD bits in the accessory packet. * OutputPower - Output On/Off. Equivalent to packet C bit. It has these values: * 0 - Output is off. * 1 - Output is on. * * Returns: * None *///------------------------------------------------------------------------------{uint8_t SxAddress; // calculated Selectrix addressuint8_t SxData[4];static uint8_t prevAddr = -1;static uint8_t prevDirection = -1; if (!OutputPower) return; if (prevAddr != Addr || prevDirection != Direction) { // Centrale zend meerdere pakketten achter elkaar, filter deze prevAddr = Addr; prevDirection = Direction; if (Direction) SxData[0] = 'S'; else SxData[0] = 'R'; SxData[1] = (Addr / 100) + '0'; SxData[2] = (Addr % 100) / 10 + '0'; SxData[3] = (Addr % 10) + '0'; Serial.write(SxData, 4); }}
/* * SXmaster.cpp * * Version: 0.2 * Copyright: Gerard van der Sel / Ronald Helder * * Changed on: 27-01-2025 * Version: 0.2 * Changes: Complete rewritten for efficiency, but only for Sx0 * * * interface hardware needed ! Interface SX-bus voor de centrale: - SX T0 (Clock) must be connected to D3 ; - SX T1 must be connected to Pin D4 ; - SX D must be connected to Pin D5 . SX-bus interface (NEM 681) De clock lijn (T0) is verbonden met een interruptingang, zodat op de flanken van dit signaal een interrupt gegenereerd kan worden. Hierna kan data gelezen worden van T1 of data geschreven worden naar D. Klok: -- ---------------- ---------------- ---------------- ------ | | | | | | | | -- -- -- -- Data: -- ------------------- ------------------- ------------------- --------- X X X X -- ------------------- ------------------- ------------------- --------- ^ ^ ^ ^ ^ ^ ^ ^ W R W R W R W ROpbouw telegram (96 bits): 0 0 0 1 S 1 A3 A2 1 A1 A0 1 synchronisatie 'byte' D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 7 data 'bytes' D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 ieder 'byte' is de inhoud D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 van een adres D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 0 = Logische 0 1 = Logische 1 S = Spanning rails (0 = uit, 1= aan) Ax = Gezamelijk het nummer van het telegram Dx = D0 t/m D7 vormen de data op een Selectrix adres. Verdeling adressen over de verschillende telegrammen: telegram '0' : 111, 95, 79, 63, 47, 31, 15 telegram '1' : 110, 94, 78, 62, 46, 30, 14 telegram '2' : 109, 93, 77, 61, 45, 29, 13 telegram '3' : 108, 92, 76, 60, 44, 28, 12 telegram '4' : 107, 91, 75, 59, 43, 27, 11 telegram '5' : 106, 90, 74, 58, 42, 26, 10 telegram '6' : 105, 89, 73, 57, 41, 25, 9 telegram '7' : 104, 88, 72, 56, 40, 24, 8 telegram '8' : 103, 87, 71, 55, 39, 23, 7 telegram '9' : 102, 86, 70, 54, 38, 22, 6 telegram '10' : 101, 85, 69, 53, 37, 21, 5 telegram '11' : 100, 84, 68, 52, 36, 20, 4 telegram '12' : 99, 83, 67, 51, 35, 19, 3 telegram '13' : 98, 82, 66, 50, 34, 18, 2 telegram '14' : 97, 81, 65, 49, 33, 17, 1 telegram '15' : 96, 80, 64, 48, 32, 16, 0 In deze versie worden alle 16 telegrammen volgens bovenstaand patroon bij initialisatie al opgebouwd, zodat in de isr geen test of rekenwerk hoeft plaats te vinden, en de isr dus erg efficient wordt. Bij het schrijven naar de database set() wordt gelijk al in dit patroon geschreven. ** SX Timing ** 1 Bit 50 us 1 Kanal 600 us (= 12 Bit) 1 Grundrahmen ca. 4,8 ms 1 Gesamtrahmen ca. 80 ms (= 16 Grundrahmen) 0 0 0 1 S 1 A3 A2 1 A1 A0 1 == sync frame of 12 bitsThis library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <Arduino.h>#include "SXcentrale.h"SXcentrale::SXcentrale() {}//------------------------------------------------------------------------------void SXcentrale::init() // initialize function//------------------------------------------------------------------------------{ // Initialize pins and variables pinMode(SX_T0, OUTPUT); // SX-T0 is an output pinMode(SX0_T1, OUTPUT); // SX0_T1 is also an output pinMode(SX0_D, INPUT_PULLUP); // SX0_D is an input digitalWrite(SX_T0, LOW); digitalWrite(SX0_T1, LOW); // Create the datastructure that will be send 1:1 to the bus, bit by bit for (uint8_t sx_frame = 0; sx_frame < SX_TELCOUNT; sx_frame++) { // Fill first row with (LSB->MSB): 0 0 0 1 S 1 A3 A2 1 A1 A0 1 synchronisatie 'byte' int idx = sx_frame*SX_DATACOUNT; uint16_t adres = (sx_frame); sxbusSX0[idx] = 0x0928; // 1's: B0000100100101000 sxbusSX0[idx] |= 0x0010; // PWR: B0000000000010000 sxbusSX0[idx] |= (adres & 0x0008) << 3; // A3 sxbusSX0[idx] |= (adres & 0x0004) << 5; // A2 sxbusSX0[idx] |= (adres & 0x0002) << 8; // A1 sxbusSX0[idx] |= (adres & 0x0001) << 10; // A0 for (uint8_t sx_telgram = 1; sx_telgram < SX_DATACOUNT; sx_telgram++) { // Fill each datarow with D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1, with every D = 0 (for now) sxbusSX0[idx + sx_telgram] = 0x0924; } } /* DEBUG for (int i=0; i<48; i++) { Serial.println(sxbusSX0[i], BIN); delay(100); } Serial.println("---------------------------------------"); delay(2000); */ // initialize timer1 uint8_t oldSREG = SREG; noInterrupts(); // disable all interrupts // Set timer1 to the correct value for our interrupt interval TCCR1A = 0; TCCR1B = (1 << CS10); TCNT1 = Timer1_40; // preload timer with 40usec delay TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt SREG = oldSREG; // restore interrupts}void SXcentrale::initVar(void) {}// Local functions//------------------------------------------------------------------------------void SXcentrale::writeT0(void) // Write clock line high (inverted because hardware (TTL 7404) inverts again)//------------------------------------------------------------------------------{ uint8_t port = (SX_T0_PORT & B11110111); SX_T0_PORT = port;}//------------------------------------------------------------------------------void SXcentrale::writeT0T1(void) // Write clock low and transmit line according to data// (inverted because hardware (TTL 7404) inverts again)//------------------------------------------------------------------------------{ static uint16_t *pData = sxbusSX0; // pointer to data to be sent (for efficiency) static uint16_t sx_sendmask = 0x0001; // Start with sending LSB /* DEBUG x = 0; //(pData == &sxbusSX0[119]); if (x) Serial.print("Data:"); if (x) Serial.print(*pData, BIN); if (x) Serial.print(" SM:"); if (x) Serial.print(sx_sendmask, BIN); if (x) Serial.print(":"); if (x) Serial.println((*pData & sx_sendmask) ? 1:0); */ uint8_t port = (SX_T0_PORT & B11100111); if (*pData & sx_sendmask) port |= B00001000; else port |= B00011000; SX_T0_PORT = port; sx_sendmask = sx_sendmask << 1; if (sx_sendmask & 0x1000) { // End of group (line / adres) sx_sendmask = 0x0001; pData++; // point to next channel if (pData >= (&sxbusSX0[SX_TELCOUNT * SX_DATACOUNT])) { pData = sxbusSX0; // been round all channels/adresses, start again // Serial.println("****** ROUND *******"); } }}//------------------------------------------------------------------------------void SXcentrale::readD(void) // Read data line// Data reading is not done in this version [because I don't need it ;-) ]//------------------------------------------------------------------------------{}//------------------------------------------------------------------------------void SXcentrale::isr(void) // Interrupt service routine (AVR OVERFLOW T1)//------------------------------------------------------------------------------{ static bool _writeFase = true; if (_writeFase) { // 10 usec fase (make clock low and write data) writeT0T1(); // Clock low and write 1 databit _writeFase = false; TCNT1 += Timer1_10; // reload timer } else { // 40 usec fase (make clock high and read data) writeT0(); // Clock high readD(); _writeFase = true; TCNT1 += Timer1_40; // reload timer } }//------------------------------------------------------------------------------uint8_t SXcentrale::calcIndex(uint8_t SXadr) // Convert from SX-bus addresses to index in array.//------------------------------------------------------------------------------{ uint8_t frame = 15 - (SXadr & 0x0F); // Get the frame number uint8_t offset = 7 - (SXadr >> 4); // Get the offset in the frame// Serial.print(SXadr);// Serial.print("=");// Serial.print(frame);// Serial.print(".");// Serial.println(offset); return frame * 8 + offset; // Calculate the index in the array}// functions 'accessing' the SX-bus//------------------------------------------------------------------------------int SXcentrale::get(uint8_t bus, uint8_t adr) // Read data from the array, filled by the isr.// In:// bus: 0 - 3, bus to read from.// adr: 0 - 111, address value read from.// Return value:// -1: Address out of range// -3: SX-bus out of range// 0 - 255: Value from SX-bus//------------------------------------------------------------------------------{ // returns the value of a SX address if (adr < SX_ADDRESS_NUMBER) { switch (bus) { case 0: return sxbusSX0[calcIndex(adr)] & 0xff; // Return value default: return -3; // Report bus out of range } } return -1; // Report address out of range}//------------------------------------------------------------------------------int SXcentrale::set(uint8_t bus, uint8_t adr, uint8_t data) // Write data to the array, writing to the SX-bus is done by the isr.// In:// bus: 0 - 3, bus to send value to, only 0 is a valid number in this version.// adr: 0 - 111, address value send to.// data: 0 - 255, value send// Return value:// 0: Success// -1: Address out of range// -3: SX-bus out of range//------------------------------------------------------------------------------{ if (adr < SX_ADDRESS_NUMBER && bus == 0) { // store in pattern D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 (D0 = LSB) uint8_t idx = calcIndex(adr); sxbusSX0[idx] = 0x0924; sxbusSX0[idx] |= data & 0x03; sxbusSX0[idx] |= (data & 0x0C) << 1; sxbusSX0[idx] |= (data & 0x30) << 2; sxbusSX0[idx] |= (data & 0xC0) << 3; return 0; // success } return -1; // address out of range}//------------------------------------------------------------------------------int SXcentrale::setbit(uint8_t bus, uint16_t dccAdr, uint8_t data) // Write one bit to the array, writing to the SX-bus is done by the isr.// In:// bus: 0 - 3, bus to send value to, only 0 is a valid number in this version.// dccAdr: 0 - 895, DCC bit-address value send to (adres * 8 + bitnummer).// data: 0 - 1, reset or set// Return value:// 0: Success// -1: Address out of range// -3: SX-bus out of range//------------------------------------------------------------------------------{ if (dccAdr < 8*SX_ADDRESS_NUMBER && bus == 0) { // store in pattern D0 D1 1 D2 D3 1 D4 D5 1 D6 D7 1 (D0 = LSB) uint16_t adres = dccAdr / 8; uint16_t bit = dccAdr % 8; uint8_t idx = calcIndex(adres); if (data) { // Set bit data = (0x01 << bit); sxbusSX0[idx] |= data & 0x03; sxbusSX0[idx] |= (data & 0x0C) << 1; sxbusSX0[idx] |= (data & 0x30) << 2; sxbusSX0[idx] |= (data & 0xC0) << 3; //Serial.print(" a:");Serial.print(adres);Serial.print(" b:");Serial.print(data); } else { // Reset bit uint16_t mask = 0; data = (0x01 << bit); mask |= data & 0x03; mask |= (data & 0x0C) << 1; mask |= (data & 0x30) << 2; mask |= (data & 0xC0) << 3; sxbusSX0[idx] &= ~mask; //Serial.print(" a:");Serial.print(adres);Serial.print(" m:");Serial.print(mask); } return 0; // success } return -1; // address out of range}//------------------------------------------------------------------------------uint8_t SXcentrale::getPWR() // Read POWER status from the SX-bus// Return value:// 0: Power off// 1: Power on//------------------------------------------------------------------------------{ return (sxbusSX0[0] & SX_BIT_PWR);}//------------------------------------------------------------------------------int SXcentrale::setPWR(uint8_t val) // Write POWER status to the SX-bus and control a connected central.// Return value:// 0: Success// -2: Illegal power value//------------------------------------------------------------------------------{for (uint8_t sx_frame = 0; sx_frame < SX_TELCOUNT; sx_frame++) { int idx = sx_frame*SX_DATACOUNT; if (val == 1) sxbusSX0[idx] |= SX_BIT_PWR; else sxbusSX0[idx] &= (~SX_BIT_PWR); }}
/* * SXcentrale.cpp * * Version: 0.2 * Copyright: Gerard van der Sel / Ronald Helder * * Changed on: 27-01-2025 * Version: 0.2 * Changes: Complete rewritten for efficiency, but only for Sx0 * * * interface hardware needed ! Hardware: Connect T0 (Timing Signal) to D3 Connect T1 (Write Data) to D4 Connect D (Read Data) to D5 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */#ifndef SXcentrale_H_#define SXcentrale_H_#include <Arduino.h>// Define arduino pins and ports// The io pins for the busses#if (1)#define SX_T0 3 // Clock (for all)#define SX_T0_PORT PORTD#define SX_T0_PORTPIN PORTD3#define SX0_T1 4 // Data write#define SX0_T1_PORT PORTD#define SX0_T1_PORTPIN PORTD4#define SX0_D 5 // Data read#define SX0_D_INPORT PIND#define SX0_D_INPORTPIN PIND5#else#define SX_T0 8 // Clock (for all)#define SX_T0_PORT PORTB#define SX_T0_PORTPIN PORTB0#define SX0_T1 9 // Data write#define SX0_T1_PORT PORTB#define SX0_T1_PORTPIN PORTB1#define SX0_D 10 // Data read#define SX0_D_INPORT PINC#define SX0_D_INPORTPIN PINB2#endif// defines for Selectrix constants#define SX_DATACOUNT 8 // 7 dataframes in 1 SYNC Channel#define SX_TELCOUNT 16 // 16 SYNC Channels to transmit all data#define SX_BIT_PWR 0x0010#define SX_ADDRESS_NUMBER 112 // number SX channels#define Timer1_10 -160 // 10 usec delay#define Timer1_40 -640 // 40 usec delay//#define Timer1_10 -160 // 20 usec delay//#define Timer1_40 -960 // 60 usec delay//#define Timer1_10 60000 // 10 usec delay (debug, also set CS12)//#define Timer1_40 60000 // 40 usec delay (debug, also set CS12)class SXcentrale {public: SXcentrale(); void init(void); int get(uint8_t, uint8_t); int set(uint8_t, uint8_t, uint8_t); int setbit(uint8_t, uint16_t, uint8_t); uint8_t getPWR(void); int setPWR(uint8_t); void isr(void); private: void writeT0(void); void writeT0T1(void); void readD(void); void initVar(void); uint8_t calcIndex(uint8_t adr); uint16_t sxbusSX0[SX_TELCOUNT * SX_DATACOUNT]; // to store the SX-bus data in signal format (see .cpp file), with D0 LSB in _sxbus (same as normal var) uint16_t sx_sendmask; // specifies bit to be sent};#endif /* SXcentrale_H_ */