commit 549c9f388e06659f49b484ef65123bfb01dbcd4f Author: Serge NOEL Date: Tue Feb 10 11:05:54 2026 +0100 Initialisation depot diff --git a/DCC-Centrale/Firmware/.github/copilot-instructions.md b/DCC-Centrale/Firmware/.github/copilot-instructions.md new file mode 100644 index 0000000..b227b33 --- /dev/null +++ b/DCC-Centrale/Firmware/.github/copilot-instructions.md @@ -0,0 +1,106 @@ + +- [ ] Verify that the copilot-instructions.md file in the .github directory is created. + +- [ ] Clarify Project Requirements + + +- [ ] Scaffold the Project + + +- [ ] Customize the Project + + +- [ ] Install Required Extensions + + +- [ ] Compile the Project + + +- [ ] Create and Run Task + + +- [ ] Launch the Project + + +- [ ] Ensure Documentation is Complete + + + +- Work through each checklist item systematically. +- Keep communication concise and focused. +- Follow development best practices. diff --git a/DCC-Centrale/Firmware/BaseStation/DCC++ Arduino Sketch.pdf b/DCC-Centrale/Firmware/BaseStation/DCC++ Arduino Sketch.pdf new file mode 100644 index 0000000..ae83b4c Binary files /dev/null and b/DCC-Centrale/Firmware/BaseStation/DCC++ Arduino Sketch.pdf differ diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp new file mode 100644 index 0000000..b027508 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp @@ -0,0 +1,239 @@ +/********************************************************************** + +Accessories.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION can keep track of the direction of any turnout that is controlled +by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories +connected in this fashion, can always be operated using the DCC BASE STATION Accessory command: + + + +However, this general command simply sends the appropriate DCC instruction packet to the main tracks +to operate connected accessories. It does not store or retain any information regarding the current +status of that accessory. + +To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically +invoke the required command as needed, first define/edit/delete such turnouts using the following +variations of the "T" command: + + : creates a new turnout ID, with specified ADDRESS and SUBADDRESS + if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of turnout ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined turnouts + returns: for each defined turnout or if no turnouts defined + +where + + ID: the numeric ID (0-32767) of the turnout to control + ADDRESS: the primary address of the decoder controlling this turnout (0-511) + SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3) + +Once all turnouts have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the turnout definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +To "throw" turnouts that have been defined use: + + : sets turnout ID to either the "thrown" or "unthrown" position + returns: , or if turnout ID does not exist + +where + + ID: the numeric ID (0-32767) of the turnout to control + THROW: 0 (unthrown) or 1 (thrown) + +When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so +that it is retained even without power. A list of the current directions of each Turnout in the form is generated +by this sketch whenever the status command is invoked. This provides an efficient way of initializing +the directions of any Turnouts being monitored or controlled by a separate interface or GUI program. + +**********************************************************************/ + +#include "Accessories.h" +#include "SerialCommand.h" +#include "DCCpp_Uno.h" +#include "EEStore.h" +#include +#include "Comm.h" + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::activate(int s){ + char c[20]; + data.tStatus=(s>0); // if s>0 set turnout=ON, else if zero or negative set turnout=OFF + sprintf(c,"a %d %d %d",data.address,data.subAddress,data.tStatus); + SerialCommand::parse(c); + if(num>0) + EEPROM.put(num,data.tStatus); + INTERFACE.print(""); + else + INTERFACE.print(" 1>"); +} + +/////////////////////////////////////////////////////////////////////////////// + +Turnout* Turnout::get(int n){ + Turnout *tt; + for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::remove(int n){ + Turnout *tt,*pp; + + for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout); + + if(tt==NULL){ + INTERFACE.print(""); + return; + } + + if(tt==firstTurnout) + firstTurnout=tt->nextTurnout; + else + pp->nextTurnout=tt->nextTurnout; + + free(tt); + + INTERFACE.print(""); +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::show(int n){ + Turnout *tt; + + if(firstTurnout==NULL){ + INTERFACE.print(""); + return; + } + + for(tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){ + INTERFACE.print("data.id); + if(n==1){ + INTERFACE.print(" "); + INTERFACE.print(tt->data.address); + INTERFACE.print(" "); + INTERFACE.print(tt->data.subAddress); + } + if(tt->data.tStatus==0) + INTERFACE.print(" 0>"); + else + INTERFACE.print(" 1>"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::parse(char *c){ + int n,s,m; + Turnout *t; + + switch(sscanf(c,"%d %d %d",&n,&s,&m)){ + + case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown) + t=get(n); + if(t!=NULL) + t->activate(s); + else + INTERFACE.print(""); + break; + + case 3: // argument is string with id number of turnout followed by an address and subAddress + create(n,s,m,1); + break; + + case 1: // argument is a string with id number only + remove(n); + break; + + case -1: // no arguments + show(1); // verbose show + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::load(){ + struct TurnoutData data; + Turnout *tt; + + for(int i=0;idata.nTurnouts;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.id,data.address,data.subAddress); + tt->data.tStatus=data.tStatus; + tt->num=EEStore::pointer(); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::store(){ + Turnout *tt; + + tt=firstTurnout; + EEStore::eeStore->data.nTurnouts=0; + + while(tt!=NULL){ + tt->num=EEStore::pointer(); + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextTurnout; + EEStore::eeStore->data.nTurnouts++; + } + +} +/////////////////////////////////////////////////////////////////////////////// + +Turnout *Turnout::create(int id, int add, int subAdd, int v){ + Turnout *tt; + + if(firstTurnout==NULL){ + firstTurnout=(Turnout *)calloc(1,sizeof(Turnout)); + tt=firstTurnout; + } else if((tt=get(id))==NULL){ + tt=firstTurnout; + while(tt->nextTurnout!=NULL) + tt=tt->nextTurnout; + tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout)); + tt=tt->nextTurnout; + } + + if(tt==NULL){ // problem allocating memory + if(v==1) + INTERFACE.print(""); + return(tt); + } + + tt->data.id=id; + tt->data.address=add; + tt->data.subAddress=subAdd; + tt->data.tStatus=0; + if(v==1) + INTERFACE.print(""); + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Turnout *Turnout::firstTurnout=NULL; + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h new file mode 100644 index 0000000..bbb9246 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h @@ -0,0 +1,39 @@ +/********************************************************************** + +Accessories.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Arduino.h" + +#ifndef Accessories_h +#define Accessories_h + +struct TurnoutData { + byte tStatus; + byte subAddress; + int id; + int address; +}; + +struct Turnout{ + static Turnout *firstTurnout; + int num; + struct TurnoutData data; + Turnout *nextTurnout; + void activate(int s); + static void parse(char *c); + static Turnout* get(int); + static void remove(int); + static void load(); + static void store(); + static Turnout *create(int, int, int, int=0); + static void show(int=0); +}; // Turnout + +#endif + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h new file mode 100644 index 0000000..d45a90e --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h @@ -0,0 +1,35 @@ + + +/********************************************************************** + +Comm.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Config.h" + +#if COMM_TYPE == 1 // Ethernet Shield Card Selected + + #if COMM_INTERFACE == 1 + #define COMM_SHIELD_NAME "ARDUINO-CC ETHERNET SHIELD (WIZNET 5100)" + #include // built-in Arduino.cc library + + #elif COMM_INTERFACE == 2 + #define COMM_SHIELD_NAME "ARDUINO-ORG ETHERNET-2 SHIELD (WIZNET 5500)" + #include // https://github.com/arduino-org/Arduino + + #elif COMM_INTERFACE == 3 + #define COMM_SHIELD_NAME "SEEED STUDIO ETHERNET SHIELD (WIZNET 5200)" + #include // https://github.com/Seeed-Studio/Ethernet_Shield_W5200 + + #endif + + extern EthernetServer INTERFACE; +#endif + + + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h new file mode 100644 index 0000000..126f909 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h @@ -0,0 +1,58 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: +// +// 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) +// 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL) + +#define MOTOR_SHIELD_TYPE 0 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE NUMBER OF MAIN TRACK REGISTER + +#define MAX_MAIN_REGISTERS 12 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE COMMUNICATIONS INTERFACE +// +// 0 = Built-in Serial Port +// 1 = Arduino.cc Ethernet/SD-Card Shield +// 2 = Arduino.org Ethernet/SD-Card Shield +// 3 = Seeed Studio Ethernet/SD-Card Shield W5200 + +#define COMM_INTERFACE 0 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// + +//#define IP_ADDRESS { 192, 168, 1, 200 } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE +// + +#define ETHERNET_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// + +#define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } + +///////////////////////////////////////////////////////////////////////////////////// + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.cpp new file mode 100644 index 0000000..5149d2a --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.cpp @@ -0,0 +1,39 @@ +/********************************************************************** + +CurrentMonitor.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "DCCpp_Uno.h" +#include "CurrentMonitor.h" +#include "Comm.h" + +/////////////////////////////////////////////////////////////////////////////// + +CurrentMonitor::CurrentMonitor(int pin, char *msg){ + this->pin=pin; + this->msg=msg; + current=0; + } // CurrentMonitor::CurrentMonitor + +boolean CurrentMonitor::checkTime(){ + if(millis()-sampleTimeCURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){ // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together) + digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); // disable both Motor Shield Channels + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); // regardless of which caused current overload + INTERFACE.print(msg); // print corresponding error message + } +} // CurrentMonitor::check + +long int CurrentMonitor::sampleTime=0; + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h new file mode 100644 index 0000000..f4c301a --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h @@ -0,0 +1,35 @@ +/********************************************************************** + +CurrentMonitor.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef CurrentMonitor_h +#define CurrentMonitor_h + +#include "Arduino.h" + +#define CURRENT_SAMPLE_SMOOTHING 0.01 +#define CURRENT_SAMPLE_MAX 300 + +#ifdef ARDUINO_AVR_UNO // Configuration for UNO + #define CURRENT_SAMPLE_TIME 10 +#else // Configuration for MEGA + #define CURRENT_SAMPLE_TIME 1 +#endif + +struct CurrentMonitor{ + static long int sampleTime; + int pin; + float current; + char *msg; + CurrentMonitor(int, char *); + static boolean checkTime(); + void check(); +}; + +#endif + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h new file mode 100644 index 0000000..d0f5f8b --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h @@ -0,0 +1,135 @@ +/********************************************************************** + +DCCpp_Uno.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Config.h" + +#ifndef DCCpp_Uno_h +#define DCCpp_Uno_h + +///////////////////////////////////////////////////////////////////////////////////// +// RELEASE VERSION +///////////////////////////////////////////////////////////////////////////////////// + +#define VERSION "1.2.1+" + +///////////////////////////////////////////////////////////////////////////////////// +// AUTO-SELECT ARDUINO BOARD +///////////////////////////////////////////////////////////////////////////////////// + +#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) + #define ARDUINO_AVR_MEGA2560 +#endif + +#if defined ARDUINO_AVR_UNO + + #define ARDUINO_TYPE "UNO" + + #define DCC_SIGNAL_PIN_MAIN 10 // Ardunio Uno - uses OC1B + #define DCC_SIGNAL_PIN_PROG 5 // Arduino Uno - uses OC0B + + #if COMM_INTERFACE != 0 // Serial was not selected + + #error CANNOT COMPILE - DCC++ FOR THE UNO CAN ONLY USE SERIAL COMMUNICATION - PLEASE SELECT THIS IN THE CONFIG FILE + + #endif + +#elif defined ARDUINO_AVR_MEGA2560 + + #define ARDUINO_TYPE "MEGA" + + #define DCC_SIGNAL_PIN_MAIN 12 // Arduino Mega - uses OC1B + #define DCC_SIGNAL_PIN_PROG 2 // Arduino Mega - uses OC3B + +#else + + #error CANNOT COMPILE - DCC++ ONLY WORKS WITH AN ARDUINO UNO OR AN ARDUINO MEGA 1280/2560 + +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// SELECT MOTOR SHIELD +///////////////////////////////////////////////////////////////////////////////////// + +#if MOTOR_SHIELD_TYPE == 0 + + #define MOTOR_SHIELD_NAME "ARDUINO MOTOR SHIELD" + + #define SIGNAL_ENABLE_PIN_MAIN 3 + #define SIGNAL_ENABLE_PIN_PROG 11 + + #define CURRENT_MONITOR_PIN_MAIN A0 + #define CURRENT_MONITOR_PIN_PROG A1 + + #define DIRECTION_MOTOR_CHANNEL_PIN_A 12 + #define DIRECTION_MOTOR_CHANNEL_PIN_B 13 + +#elif MOTOR_SHIELD_TYPE == 1 + + #define MOTOR_SHIELD_NAME "POLOLU MC33926 MOTOR SHIELD" + + #define SIGNAL_ENABLE_PIN_MAIN 9 + #define SIGNAL_ENABLE_PIN_PROG 11 + + #define CURRENT_MONITOR_PIN_MAIN A0 + #define CURRENT_MONITOR_PIN_PROG A1 + + #define DIRECTION_MOTOR_CHANNEL_PIN_A 7 + #define DIRECTION_MOTOR_CHANNEL_PIN_B 8 + +#else + + #error CANNOT COMPILE - PLEASE SELECT A PROPER MOTOR SHIELD TYPE + +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// SELECT COMMUNICATION INTERACE +///////////////////////////////////////////////////////////////////////////////////// + +#if COMM_INTERFACE == 0 + + #define COMM_TYPE 0 + #define INTERFACE Serial + +#elif (COMM_INTERFACE==1) || (COMM_INTERFACE==2) || (COMM_INTERFACE==3) + + #define COMM_TYPE 1 + #define INTERFACE eServer + #define SDCARD_CS 4 + +#else + + #error CANNOT COMPILE - Please select a proper value for COMM_INTERFACE in CONFIG.H file + +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// SET WHETHER TO SHOW PACKETS - DIAGNOSTIC MODE ONLY +///////////////////////////////////////////////////////////////////////////////////// + +// If SHOW_PACKETS is set to 1, then for select main operations track commands that modify an internal DCC packet register, +// if printFlag for that command is also set to 1, DCC++ BASE STATION will additionally return the +// DCC packet contents of the modified register in the following format: + +// <* REG: B1 B2 ... Bn CSUM / REPEAT> +// +// REG: the number of the main operations track packet register that was modified +// B1: the first hexidecimal byte of the DCC packet +// B2: the second hexidecimal byte of the DCC packet +// Bn: the nth hexidecimal byte of the DCC packet +// CSUM: a checksum byte that is required to be the final byte in any DCC packet +// REPEAT: the number of times the DCC packet was re-transmitted to the tracks after its iniital transmission + +#define SHOW_PACKETS 0 // set to zero to disable printing of every packet for select main operations track commands + +///////////////////////////////////////////////////////////////////////////////////// + +#endif + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino new file mode 100644 index 0000000..9378374 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino @@ -0,0 +1,563 @@ +/********************************************************************** + +DCC++ BASE STATION +COPYRIGHT (c) 2013-2016 Gregg E. Berman + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega +using the Arduino IDE 1.6.6. + +It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others) +to be used as a fully-functioning digital command and control (DCC) base station +for controlling model train layouts that conform to current National Model +Railroad Association (NMRA) DCC standards. + +This version of DCC++ BASE STATION supports: + + * 2-byte and 4-byte locomotive addressing + * Simultaneous control of multiple locomotives + * 128-step speed throttling + * Cab functions F0-F28 + * Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses + - includes optional functionailty to monitor and store of the direction of any connected turnouts + * Programming on the Main Operations Track + - write configuration variable bytes + - set/clear specific configuration variable bits + * Programming on the Programming Track + - write configuration variable bytes + - set/clear specific configuration variable bits + - read configuration variable bytes + +DCC++ BASE STATION is controlled with simple text commands received via +the Arduino's serial interface. Users can type these commands directly +into the Arduino IDE Serial Monitor, or can send such commands from another +device or computer program. + +When compiled for the Arduino Mega, an Ethernet Shield can be used for network +communications instead of using serial communications. + +DCC++ CONTROLLER, available separately under a similar open-source +license, is a Java program written using the Processing library and Processing IDE +that provides a complete and configurable graphic interface to control model train layouts +via the DCC++ BASE STATION. + +With the exception of a standard 15V power supply that can be purchased in +any electronics store, no additional hardware is required. + +Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or +commercial hardware, software, interfaces, specifications, or methods related +to the control of model trains using NMRA DCC standards. Both programs are wholly +original, developed by the author, and are not derived from any known commercial, +free, or open-source model railroad control packages by any other parties. + +However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and +embedded libraries associated with Arduino and Processing. Tremendous thanks to those +responsible for these terrific open-source initiatives that enable programs like +DCC++ to be developed and distributed in the same fashion. + +REFERENCES: + + NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices + Arduino: http://www.arduino.cc/ + Processing: http://processing.org/ + GNU General Public License: http://opensource.org/licenses/GPL-3.0 + +BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION: + +DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0, +and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V +unipolar signals that each properly encode zero and one bits conforming with +DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B. + +Series of DCC bit streams are bundled into Packets that each form the basis of +a standard DCC instruction. Packets are stored in Packet Registers that contain +methods for updating and queuing according to text commands sent by the user +(or another program) over the serial interface. There is one set of registers that controls +the main operations track and one that controls the programming track. + +For the main operations track, packets to store cab throttle settings are stored in +registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp_Uno.h). +It is generally considered good practice to continuously send throttle control packets +to every cab so that if an engine should momentarily lose electrical connectivity with the tracks, +it will very quickly receive another throttle control signal as soon as connectivity is +restored (such as when a trin passes over rough portion of track or the frog of a turnout). + +DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter +that has been loaded with a throttle control setting for a given cab. For each register, it +transmits the appropriate DCC packet bits to the track, then moves onto the next register +without any pausing to ensure continuous bi-polar power is being provided to the tracks. +Updates to the throttle setting stored in any given packet register are done in a double-buffered +fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits +can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal. +The cabs identified in each stored throttle setting should be unique across registers. If two registers +contain throttle setting for the same cab, the throttle in the engine will oscillate between the two, +which is probably not a desireable outcome. + +For both the main operations track and the programming track there is also a special packet register with id=0 +that is used to store all other DCC packets that do not require continious transmittal to the tracks. +This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables. +It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure +proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet +and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself. +Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of +verifying proper transmittal before acting on the instructions contained in those packets + +An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached +on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals +produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals. + +This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10) +to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect +the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13). + +For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the +Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2) +to the Motor Shield's DIRECTION B input (pin 13). + +Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp_Uno.h for details). + +When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be +connected directly to the tracks. This software assumes CHANNEL A is connected +to the Main Operations Track, and CHANNEL B is connected to the Programming Track. + +DCC++ BASE STATION in split into multiple modules, each with its own header file: + + DCCpp_Uno: declares required global objects and contains initial Arduino setup() + and Arduino loop() functions, as well as interrput code for OC0B and OC1B. + Also includes declarations of optional array of Turn-Outs and optional array of Sensors + + SerialCommand: contains methods to read and interpret text commands from the serial line, + process those instructions, and, if necessary call appropriate Packet RegisterList methods + to update either the Main Track or Programming Track Packet Registers + + PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions + + CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and + CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload + is detected + + Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled + by a DCC stationary accessory decoder. + + Sensor: contains methods to monitor and report on the status of optionally-defined infrared + sensors embedded in the Main Track and connected to various pins on the Arudino Uno + + Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use + + EEStore: contains methods to store, update, and load various DCC settings and status + (e.g. the states of all defined turnouts) in the EEPROM for recall after power-up + +DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters + +**********************************************************************/ + +// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE + +#include "DCCpp_Uno.h" +#include "PacketRegister.h" +#include "CurrentMonitor.h" +#include "Sensor.h" +#include "SerialCommand.h" +#include "Accessories.h" +#include "EEStore.h" +#include "Config.h" +#include "Comm.h" + +void showConfiguration(); + +// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE + +#if COMM_TYPE == 1 + byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server) + EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer +#endif + +// NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS. +// NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES + +volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets +volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets + +CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track +CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track + +/////////////////////////////////////////////////////////////////////////////// +// MAIN ARDUINO LOOP +/////////////////////////////////////////////////////////////////////////////// + +void loop(){ + + SerialCommand::process(); // check for, and process, and new serial commands + + if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks + mainMonitor.check(); + progMonitor.check(); + } + + Sensor::check(); // check sensors for activate/de-activate + +} // loop + +/////////////////////////////////////////////////////////////////////////////// +// INITIAL SETUP +/////////////////////////////////////////////////////////////////////////////// + +void setup(){ + + Serial.begin(115200); // configure serial interface + Serial.flush(); + + #ifdef SDCARD_CS + pinMode(SDCARD_CS,OUTPUT); + digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card + #endif + + EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM + + pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt + digitalWrite(A5,HIGH); + if(!digitalRead(A5)) + showConfiguration(); + + Serial.print(""); + + #if COMM_TYPE == 1 + #ifdef IP_ADDRESS + Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address + #else + Ethernet.begin(mac); // Start networking using DHCP to get an IP Address + #endif + INTERFACE.begin(); + #endif + + SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line + + Serial.print(""); + #elif COMM_TYPE == 1 + Serial.print(Ethernet.localIP()); + Serial.print(">"); + #endif + + // CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS + + // Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK + // Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin + // Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency + // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle + + #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199 + #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599 + + #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855 + #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927 + + pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) + digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW); + + pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A + + bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A + bitSet(TCCR1A,WGM11); + bitSet(TCCR1B,WGM12); + bitSet(TCCR1B,WGM13); + + bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary) + bitSet(TCCR1A,COM1B0); + + bitClear(TCCR1B,CS12); // set Timer 1 prescale=1 + bitClear(TCCR1B,CS11); + bitSet(TCCR1B,CS10); + + OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1; + OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1; + + pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A + + mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 + + bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B) + + // CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS + +#ifdef ARDUINO_AVR_UNO // Configuration for UNO + + // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK + // Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin + // Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency + // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with as-close-as-possible to 50% duty cycle + + #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49 + #define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24 + + #define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28 + #define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14 + + pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) + digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); + + pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B + + bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A + bitSet(TCCR0A,WGM01); + bitSet(TCCR0B,WGM02); + + bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary) + bitSet(TCCR0A,COM0B0); + + bitClear(TCCR0B,CS02); // set Timer 0 prescale=64 + bitSet(TCCR0B,CS01); + bitSet(TCCR0B,CS00); + + OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0; + OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0; + + pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B + + progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 + + bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B) + +#else // Configuration for MEGA + + // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK + // Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin + // Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency + // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle + + #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199 + #define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599 + + #define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855 + #define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927 + + pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) + digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); + + pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B + + bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A + bitSet(TCCR3A,WGM31); + bitSet(TCCR3B,WGM32); + bitSet(TCCR3B,WGM33); + + bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary) + bitSet(TCCR3A,COM3B0); + + bitClear(TCCR3B,CS32); // set Timer 3 prescale=1 + bitClear(TCCR3B,CS31); + bitSet(TCCR3B,CS30); + + OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3; + OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3; + + pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B + + progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 + + bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B) + +#endif + +} // setup + +/////////////////////////////////////////////////////////////////////////////// +// DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL +/////////////////////////////////////////////////////////////////////////////// + +// The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1. +// It is designed to read the current bit of the current register packet and +// updates the OCNA and OCNB counters of Timer-N to values that will either produce +// a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent +// DCC ZERO and DCC ONE bits. + +// These are hardware-driven interrupts that will be called automatically when triggered regardless of what +// DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled. +// Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts +// (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete +// in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic +// of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB +// registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the +// interrupt code completes and can be called again. + +// A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed +// DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible. + +// Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the +// Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro +// that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating +// some of the logic for each interrupt, but saves additional time. + +// As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds +// when a new register is loaded and the logic needs to switch active register packet pointers. + +// THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1) + +#define DCC_SIGNAL(R,N) \ + if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \ + R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \ + if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \ + R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \ + } else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \ + R.currentReg=R.nextReg; /* update currentReg to nextReg */ \ + R.nextReg=NULL; /* reset nextReg to NULL */ \ + R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \ + R.currentReg->activePacket=R.currentReg->updatePacket; \ + R.currentReg->updatePacket=R.tempPacket; \ + } else{ /* ELSE simply move to next Register */ \ + if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \ + R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \ + R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \ + } /* END-ELSE */ \ + } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \ + \ + if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \ + OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \ + OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \ + } else{ /* ELSE it is a ZERO */ \ + OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \ + OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \ + } /* END-ELSE */ \ + \ + R.currentBit++; /* point to next bit in current Packet */ + +/////////////////////////////////////////////////////////////////////////////// + +// NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT + +ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track + DCC_SIGNAL(mainRegs,1) +} + +#ifdef ARDUINO_AVR_UNO // Configuration for UNO + +ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track + DCC_SIGNAL(progRegs,0) +} + +#else // Configuration for MEGA + +ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track + DCC_SIGNAL(progRegs,3) +} + +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE +// - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH + +void showConfiguration(){ + + int mac_address[]=MAC_ADDRESS; + + Serial.print("\n*** DCC++ CONFIGURATION ***\n"); + + Serial.print("\nVERSION: "); + Serial.print(VERSION); + Serial.print("\nCOMPILED: "); + Serial.print(__DATE__); + Serial.print(" "); + Serial.print(__TIME__); + + Serial.print("\nARDUINO: "); + Serial.print(ARDUINO_TYPE); + + Serial.print("\n\nMOTOR SHIELD: "); + Serial.print(MOTOR_SHIELD_NAME); + + Serial.print("\n\nDCC SIG MAIN: "); + Serial.print(DCC_SIGNAL_PIN_MAIN); + Serial.print("\n DIRECTION: "); + Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A); + Serial.print("\n ENABLE: "); + Serial.print(SIGNAL_ENABLE_PIN_MAIN); + Serial.print("\n CURRENT: "); + Serial.print(CURRENT_MONITOR_PIN_MAIN); + + Serial.print("\n\nDCC SIG PROG: "); + Serial.print(DCC_SIGNAL_PIN_PROG); + Serial.print("\n DIRECTION: "); + Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B); + Serial.print("\n ENABLE: "); + Serial.print(SIGNAL_ENABLE_PIN_PROG); + Serial.print("\n CURRENT: "); + Serial.print(CURRENT_MONITOR_PIN_PROG); + + Serial.print("\n\nNUM TURNOUTS: "); + Serial.print(EEStore::eeStore->data.nTurnouts); + Serial.print("\n SENSORS: "); + Serial.print(EEStore::eeStore->data.nSensors); + Serial.print("\n OUTPUTS: "); + Serial.print(EEStore::eeStore->data.nOutputs); + + Serial.print("\n\nINTERFACE: "); + #if COMM_TYPE == 0 + Serial.print("SERIAL"); + #elif COMM_TYPE == 1 + Serial.print(COMM_SHIELD_NAME); + Serial.print("\nMAC ADDRESS: "); + for(int i=0;i<5;i++){ + Serial.print(mac_address[i],HEX); + Serial.print(":"); + } + Serial.print(mac_address[5],HEX); + Serial.print("\nPORT: "); + Serial.print(ETHERNET_PORT); + Serial.print("\nIP ADDRESS: "); + + #ifdef IP_ADDRESS + Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address + #else + Ethernet.begin(mac); // Start networking using DHCP to get an IP Address + #endif + + Serial.print(Ethernet.localIP()); + + #ifdef IP_ADDRESS + Serial.print(" (STATIC)"); + #else + Serial.print(" (DHCP)"); + #endif + + #endif + Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO"); + + while(true); +} + +/////////////////////////////////////////////////////////////////////////////// + + + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp new file mode 100644 index 0000000..a58d431 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp @@ -0,0 +1,83 @@ +/********************************************************************** + +EEStore.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "DCCpp_Uno.h" +#include "EEStore.h" +#include "Accessories.h" +#include "Sensor.h" +#include "Outputs.h" +#include + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::init(){ + + + eeStore=(EEStore *)calloc(1,sizeof(EEStore)); + + EEPROM.get(0,eeStore->data); // get eeStore data + + if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID + sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM + eeStore->data.nTurnouts=0; + eeStore->data.nSensors=0; + eeStore->data.nOutputs=0; + EEPROM.put(0,eeStore->data); + } + + reset(); // set memory pointer to first free EEPROM space + Turnout::load(); // load turnout definitions + Sensor::load(); // load sensor definitions + Output::load(); // load output definitions + +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::clear(){ + + sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM + eeStore->data.nTurnouts=0; + eeStore->data.nSensors=0; + eeStore->data.nOutputs=0; + EEPROM.put(0,eeStore->data); + +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::store(){ + reset(); + Turnout::store(); + Sensor::store(); + Output::store(); + EEPROM.put(0,eeStore->data); +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::advance(int n){ + eeAddress+=n; +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::reset(){ + eeAddress=sizeof(EEStore); +} +/////////////////////////////////////////////////////////////////////////////// + +int EEStore::pointer(){ + return(eeAddress); +} +/////////////////////////////////////////////////////////////////////////////// + +EEStore *EEStore::eeStore=NULL; +int EEStore::eeAddress=0; + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h new file mode 100644 index 0000000..f7d2dec --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h @@ -0,0 +1,35 @@ +/********************************************************************** + +EEStore.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef EEStore_h +#define EEStore_h + +#define EESTORE_ID "DCC++" + +struct EEStoreData{ + char id[sizeof(EESTORE_ID)]; + int nTurnouts; + int nSensors; + int nOutputs; +}; + +struct EEStore{ + static EEStore *eeStore; + EEStoreData data; + static int eeAddress; + static void init(); + static void reset(); + static int pointer(); + static void advance(int); + static void store(); + static void clear(); +}; + +#endif + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp new file mode 100644 index 0000000..fe2ddef --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp @@ -0,0 +1,256 @@ +/********************************************************************** + +Outputs.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes. +Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW. +However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH. + +Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up. +The default is to set each defined pin to active or inactive according to its restored state. +However, the default behavior can be modified so that any pin can be forced to be either active or inactive +upon power-up regardless of its previous state before power-down. + +To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete +output definitions using the following variation of the "Z" command: + + : creates a new output ID, with specified PIN and IFLAG values. + if output ID already exists, it is updated with specificed PIN and IFLAG. + note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW + according to IFLAG value specifcied (see below). + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of output ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined output pins + returns: for each defined output pin or if no output pins defined + +where + + ID: the numeric ID (0-32767) of the output + PIN: the arduino pin number to use for the output + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows: + + IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW) + 1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH) + + IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending + on state before power-down; state of pin set to INACTIVE when first created + 1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE + depending on IFLAG, bit 2 + + IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created + 1 = state of pin set to ACTIVE uponm power-up or when first created + +Once all outputs have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the output definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +To change the state of outputs that have been defined use: + + : sets output ID to either ACTIVE or INACTIVE state + returns: , or if turnout ID does not exist + +where + + ID: the numeric ID (0-32767) of the turnout to control + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + +When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so +that it is retained even without power. A list of the current states of each output in the form is generated +by this sketch whenever the status command is invoked. This provides an efficient way of initializing +the state of any outputs being monitored or controlled by a separate interface or GUI program. + +**********************************************************************/ + +#include "Outputs.h" +#include "SerialCommand.h" +#include "DCCpp_Uno.h" +#include "EEStore.h" +#include +#include "Comm.h" + +/////////////////////////////////////////////////////////////////////////////// + +void Output::activate(int s){ + data.oStatus=(s>0); // if s>0, set status to active, else inactive + digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) + if(num>0) + EEPROM.put(num,data.oStatus); + INTERFACE.print(""); + else + INTERFACE.print(" 1>"); +} + +/////////////////////////////////////////////////////////////////////////////// + +Output* Output::get(int n){ + Output *tt; + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +void Output::remove(int n){ + Output *tt,*pp; + + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput); + + if(tt==NULL){ + INTERFACE.print(""); + return; + } + + if(tt==firstOutput) + firstOutput=tt->nextOutput; + else + pp->nextOutput=tt->nextOutput; + + free(tt); + + INTERFACE.print(""); +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::show(int n){ + Output *tt; + + if(firstOutput==NULL){ + INTERFACE.print(""); + return; + } + + for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ + INTERFACE.print("data.id); + if(n==1){ + INTERFACE.print(" "); + INTERFACE.print(tt->data.pin); + INTERFACE.print(" "); + INTERFACE.print(tt->data.iFlag); + } + if(tt->data.oStatus==0) + INTERFACE.print(" 0>"); + else + INTERFACE.print(" 1>"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::parse(char *c){ + int n,s,m; + Output *t; + + switch(sscanf(c,"%d %d %d",&n,&s,&m)){ + + case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) + t=get(n); + if(t!=NULL) + t->activate(s); + else + INTERFACE.print(""); + break; + + case 3: // argument is string with id number of output followed by a pin number and invert flag + create(n,s,m,1); + break; + + case 1: // argument is a string with id number only + remove(n); + break; + + case -1: // no arguments + show(1); // verbose show + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::load(){ + struct OutputData data; + Output *tt; + + for(int i=0;idata.nOutputs;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.id,data.pin,data.iFlag); + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + tt->num=EEStore::pointer(); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::store(){ + Output *tt; + + tt=firstOutput; + EEStore::eeStore->data.nOutputs=0; + + while(tt!=NULL){ + tt->num=EEStore::pointer(); + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextOutput; + EEStore::eeStore->data.nOutputs++; + } + +} +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::create(int id, int pin, int iFlag, int v){ + Output *tt; + + if(firstOutput==NULL){ + firstOutput=(Output *)calloc(1,sizeof(Output)); + tt=firstOutput; + } else if((tt=get(id))==NULL){ + tt=firstOutput; + while(tt->nextOutput!=NULL) + tt=tt->nextOutput; + tt->nextOutput=(Output *)calloc(1,sizeof(Output)); + tt=tt->nextOutput; + } + + if(tt==NULL){ // problem allocating memory + if(v==1) + INTERFACE.print(""); + return(tt); + } + + tt->data.id=id; + tt->data.pin=pin; + tt->data.iFlag=iFlag; + tt->data.oStatus=0; + + if(v==1){ + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + INTERFACE.print(""); + } + + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::firstOutput=NULL; + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h new file mode 100644 index 0000000..1748bce --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h @@ -0,0 +1,39 @@ +/********************************************************************** + +Outputs.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Arduino.h" + +#ifndef Outputs_h +#define Outputs_h + +struct OutputData { + byte oStatus; + int id; + byte pin; + byte iFlag; +}; + +struct Output{ + static Output *firstOutput; + int num; + struct OutputData data; + Output *nextOutput; + void activate(int s); + static void parse(char *c); + static Output* get(int); + static void remove(int); + static void load(); + static void store(); + static Output *create(int, int, int, int=0); + static void show(int=0); +}; // Output + +#endif + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp new file mode 100644 index 0000000..2d69d60 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp @@ -0,0 +1,476 @@ +/********************************************************************** + +PacketRegister.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "DCCpp_Uno.h" +#include "PacketRegister.h" +#include "Comm.h" + +/////////////////////////////////////////////////////////////////////////////// + +void Register::initPackets(){ + activePacket=packet; + updatePacket=packet+1; +} // Register::initPackets + +/////////////////////////////////////////////////////////////////////////////// + +RegisterList::RegisterList(int maxNumRegs){ + this->maxNumRegs=maxNumRegs; + reg=(Register *)calloc((maxNumRegs+1),sizeof(Register)); + for(int i=0;i<=maxNumRegs;i++) + reg[i].initPackets(); + regMap=(Register **)calloc((maxNumRegs+1),sizeof(Register *)); + speedTable=(int *)calloc((maxNumRegs+1),sizeof(int *)); + currentReg=reg; + regMap[0]=reg; + maxLoadedReg=reg; + nextReg=NULL; + currentBit=0; + nRepeat=0; +} // RegisterList::RegisterList + +/////////////////////////////////////////////////////////////////////////////// + +// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE) +// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS +// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS) + +void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile { + + nReg=nReg%((maxNumRegs+1)); // force nReg to be between 0 and maxNumRegs, inclusive + + while(nextReg!=NULL); // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed + + if(regMap[nReg]==NULL) // first time this Register Number has been called + regMap[nReg]=maxLoadedReg+1; // set Register Pointer for this Register Number to next available Register + + Register *r=regMap[nReg]; // set Register to be updated + Packet *p=r->updatePacket; // set Packet in the Register to be updated + byte *buf=p->buf; // set byte buffer in the Packet to be updated + + b[nBytes]=b[0]; // copy first byte into what will become the checksum byte + for(int i=1;i>1; // b[2], bits 7-1 + buf[6]=b[2]<<7; // b[2], bit 0 + + if(nBytes==3){ + p->nBits=49; + } else{ + buf[6]+=b[3]>>2; // b[3], bits 7-2 + buf[7]=b[3]<<6; // b[3], bit 1-0 + if(nBytes==4){ + p->nBits=58; + } else{ + buf[7]+=b[4]>>3; // b[4], bits 7-3 + buf[8]=b[4]<<5; // b[4], bits 2-0 + if(nBytes==5){ + p->nBits=67; + } else{ + buf[8]+=b[5]>>4; // b[5], bits 7-4 + buf[9]=b[5]<<4; // b[5], bits 3-0 + p->nBits=76; + } // >5 bytes + } // >4 bytes + } // >3 bytes + + nextReg=r; + this->nRepeat=nRepeat; + maxLoadedReg=max(maxLoadedReg,nextReg); + + if(printFlag && SHOW_PACKETS) // for debugging purposes + printPacket(nReg,b,nBytes,nRepeat); + +} // RegisterList::loadPacket + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::setThrottle(char *s) volatile{ + byte b[5]; // save space for checksum byte + int nReg; + int cab; + int tSpeed; + int tDirection; + byte nB=0; + + if(sscanf(s,"%d %d %d %d",&nReg,&cab,&tSpeed,&tDirection)!=4) + return; + + if(nReg<1 || nReg>maxNumRegs) + return; + + if(cab>127) + b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address + + b[nB++]=lowByte(cab); + b[nB++]=0x3F; // 128-step speed control byte + if(tSpeed>=0) + b[nB++]=tSpeed+(tSpeed>0)+tDirection*128; // max speed is 126, but speed codes range from 2-127 (0=stop, 1=emergency stop) + else{ + b[nB++]=1; + tSpeed=0; + } + + loadPacket(nReg,b,nB,0,1); + + INTERFACE.print(""); + + speedTable[nReg]=tDirection==1?tSpeed:-tSpeed; + +} // RegisterList::setThrottle() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::setFunction(char *s) volatile{ + byte b[5]; // save space for checksum byte + int cab; + int fByte, eByte; + int nParams; + byte nB=0; + + nParams=sscanf(s,"%d %d %d",&cab,&fByte,&eByte); + + if(nParams<2) + return; + + if(cab>127) + b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address + + b[nB++]=lowByte(cab); + + if(nParams==2){ // this is a request for functions FL,F1-F12 + b[nB++]=(fByte | 0x80) & 0xBF; // for safety this guarantees that first nibble of function byte will always be of binary form 10XX which should always be the case for FL,F1-F12 + } else { // this is a request for functions F13-F28 + b[nB++]=(fByte | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28) + b[nB++]=eByte; + } + + loadPacket(0,b,nB,4,1); + +} // RegisterList::setFunction() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::setAccessory(char *s) volatile{ + byte b[3]; // save space for checksum byte + int aAdd; // the accessory address (0-511 = 9 bits) + int aNum; // the accessory number within that address (0-3) + int activate; // flag indicated whether accessory should be activated (1) or deactivated (0) following NMRA recommended convention + + if(sscanf(s,"%d %d %d",&aAdd,&aNum,&activate)!=3) + return; + + b[0]=aAdd%64+128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address + b[1]=((((aAdd/64)%8)<<4) + (aNum%4<<1) + activate%2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate + + loadPacket(0,b,2,4,1); + +} // RegisterList::setAccessory() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::writeTextPacket(char *s) volatile{ + + int nReg; + byte b[6]; + int nBytes; + volatile RegisterList *regs; + + nBytes=sscanf(s,"%d %x %x %x %x %x",&nReg,b,b+1,b+2,b+3,b+4)-1; + + if(nBytes<2 || nBytes>5){ // invalid valid packet + INTERFACE.print(""); + return; + } + + loadPacket(nReg,b,nBytes,0,1); + +} // RegisterList::writeTextPacket() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::readCV(char *s) volatile{ + byte bRead[4]; + int bValue; + int c,d,base; + int cv, callBack, callBackSub; + + if(sscanf(s,"%d %d %d",&cv,&callBack,&callBackSub)!=3) // cv = 1-1024 + return; + cv--; // actual CV addresses are cv-1 (0-1023) + + bRead[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + bRead[1]=lowByte(cv); + + bValue=0; + + for(int i=0;i<8;i++){ + + c=0; + d=0; + base=0; + + for(int j=0;jACK_SAMPLE_THRESHOLD) + d=1; + } + + bitWrite(bValue,i,d); + } + + c=0; + d=0; + base=0; + + for(int j=0;jACK_SAMPLE_THRESHOLD) + d=1; + } + + if(d==0) // verify unsuccessful + bValue=-1; + + INTERFACE.print(""); + +} // RegisterList::readCV() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::writeCVByte(char *s) volatile{ + byte bWrite[4]; + int bValue; + int c,d,base; + int cv, callBack, callBackSub; + + if(sscanf(s,"%d %d %d %d",&cv,&bValue,&callBack,&callBackSub)!=4) // cv = 1-1024 + return; + cv--; // actual CV addresses are cv-1 (0-1023) + + bWrite[0]=0x7C+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + bWrite[1]=lowByte(cv); + bWrite[2]=bValue; + + loadPacket(0,resetPacket,2,1); + loadPacket(0,bWrite,3,4); + loadPacket(0,resetPacket,2,1); + loadPacket(0,idlePacket,2,10); + + c=0; + d=0; + base=0; + + for(int j=0;jACK_SAMPLE_THRESHOLD) + d=1; + } + + if(d==0) // verify unsuccessful + bValue=-1; + + INTERFACE.print(""); + +} // RegisterList::writeCVByte() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::writeCVBit(char *s) volatile{ + byte bWrite[4]; + int bNum,bValue; + int c,d,base; + int cv, callBack, callBackSub; + + if(sscanf(s,"%d %d %d %d %d",&cv,&bNum,&bValue,&callBack,&callBackSub)!=5) // cv = 1-1024 + return; + cv--; // actual CV addresses are cv-1 (0-1023) + bValue=bValue%2; + bNum=bNum%8; + + bWrite[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + bWrite[1]=lowByte(cv); + bWrite[2]=0xF0+bValue*8+bNum; + + loadPacket(0,resetPacket,2,1); + loadPacket(0,bWrite,3,4); + loadPacket(0,resetPacket,2,1); + loadPacket(0,idlePacket,2,10); + + c=0; + d=0; + base=0; + + for(int j=0;jACK_SAMPLE_THRESHOLD) + d=1; + } + + if(d==0) // verify unsuccessful + bValue=-1; + + INTERFACE.print(""); + +} // RegisterList::writeCVBit() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::writeCVByteMain(char *s) volatile{ + byte b[6]; // save space for checksum byte + int cab; + int cv; + int bValue; + byte nB=0; + + if(sscanf(s,"%d %d %d",&cab,&cv,&bValue)!=3) + return; + cv--; + + if(cab>127) + b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address + + b[nB++]=lowByte(cab); + b[nB++]=0xEC+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + b[nB++]=lowByte(cv); + b[nB++]=bValue; + + loadPacket(0,b,nB,4); + +} // RegisterList::writeCVByteMain() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::writeCVBitMain(char *s) volatile{ + byte b[6]; // save space for checksum byte + int cab; + int cv; + int bNum; + int bValue; + byte nB=0; + + if(sscanf(s,"%d %d %d %d",&cab,&cv,&bNum,&bValue)!=4) + return; + cv--; + + bValue=bValue%2; + bNum=bNum%8; + + if(cab>127) + b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address + + b[nB++]=lowByte(cab); + b[nB++]=0xE8+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + b[nB++]=lowByte(cv); + b[nB++]=0xF0+bValue*8+bNum; + + loadPacket(0,b,nB,4); + +} // RegisterList::writeCVBitMain() + +/////////////////////////////////////////////////////////////////////////////// + +void RegisterList::printPacket(int nReg, byte *b, int nBytes, int nRepeat) volatile { + + INTERFACE.print("<*"); + INTERFACE.print(nReg); + INTERFACE.print(":"); + for(int i=0;i"); +} // RegisterList::printPacket() + +/////////////////////////////////////////////////////////////////////////////// + +byte RegisterList::idlePacket[3]={0xFF,0x00,0}; // always leave extra byte for checksum computation +byte RegisterList::resetPacket[3]={0x00,0x00,0}; + +byte RegisterList::bitMask[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; // masks used in interrupt routine to speed the query of a single bit in a Packet diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h new file mode 100644 index 0000000..7132fec --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h @@ -0,0 +1,64 @@ +/********************************************************************** + +PacketRegister.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef PacketRegister_h +#define PacketRegister_h + +#include "Arduino.h" + +// Define constants used for reading CVs from the Programming Track + +#define ACK_BASE_COUNT 100 // number of analogRead samples to take before each CV verify to establish a baseline current +#define ACK_SAMPLE_COUNT 500 // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent +#define ACK_SAMPLE_SMOOTHING 0.2 // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent +#define ACK_SAMPLE_THRESHOLD 30 // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT + +// Define a series of registers that can be sequentially accessed over a loop to generate a repeating series of DCC Packets + +struct Packet{ + byte buf[10]; + byte nBits; +}; // Packet + +struct Register{ + Packet packet[2]; + Packet *activePacket; + Packet *updatePacket; + void initPackets(); +}; // Register + +struct RegisterList{ + int maxNumRegs; + Register *reg; + Register **regMap; + Register *currentReg; + Register *maxLoadedReg; + Register *nextReg; + Packet *tempPacket; + byte currentBit; + byte nRepeat; + int *speedTable; + static byte idlePacket[]; + static byte resetPacket[]; + static byte bitMask[]; + RegisterList(int); + void loadPacket(int, byte *, int, int, int=0) volatile; + void setThrottle(char *) volatile; + void setFunction(char *) volatile; + void setAccessory(char *) volatile; + void writeTextPacket(char *) volatile; + void readCV(char *) volatile; + void writeCVByte(char *) volatile; + void writeCVBit(char *) volatile; + void writeCVByteMain(char *) volatile; + void writeCVBitMain(char *s) volatile; + void printPacket(int, byte *, int, int) volatile; +}; + +#endif diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp new file mode 100644 index 0000000..11fca36 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp @@ -0,0 +1,248 @@ +/********************************************************************** + +Sensor.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION supports Sensor inputs that can be connected to any Aruidno Pin +not in use by this program. Sensors can be of any type (infrared, magentic, mechanical...). +The only requirement is that when "activated" the Sensor must force the specified Arduino +Pin LOW (i.e. to ground), and when not activated, this Pin should remain HIGH (e.g. 5V), +or be allowed to float HIGH if use of the Arduino Pin's internal pull-up resistor is specified. + +To ensure proper voltage levels, some part of the Sensor circuitry +MUST be tied back to the same ground as used by the Arduino. + +The Sensor code below utilizes exponential smoothing to "de-bounce" spikes generated by +mechanical switches and transistors. This avoids the need to create smoothing circuitry +for each sensor. You may need to change these parameters through trial and error for your specific sensors. + +To have this sketch monitor one or more Arduino pins for sensor triggers, first define/edit/delete +sensor definitions using the following variation of the "S" command: + + : creates a new sensor ID, with specified PIN and PULLUP + if sensor ID already exists, it is updated with specificed PIN and PULLUP + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of sensor ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined sensors + returns: for each defined sensor or if no sensors defined + +where + + ID: the numeric ID (0-32767) of the sensor + PIN: the arduino pin number the sensor is connected to + PULLUP: 1=use internal pull-up resistor for PIN, 0=don't use internal pull-up resistor for PIN + +Once all sensors have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the sensor definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +All sensors defined as per above are repeatedly and sequentially checked within the main loop of this sketch. +If a Sensor Pin is found to have transitioned from one state to another, one of the following serial messages are generated: + + - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered) + - for transition of Sensor ID from LOW state to HIGH state (i.e. the sensor is no longer triggered) + +Depending on whether the physical sensor is acting as an "event-trigger" or a "detection-sensor," you may +decide to ignore the return and only react to triggers. + +**********************************************************************/ + +#include "DCCpp_Uno.h" +#include "Sensor.h" +#include "EEStore.h" +#include +#include "Comm.h" + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::check(){ + Sensor *tt; + + for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; + + if(!tt->active && tt->signal<0.5){ + tt->active=true; + INTERFACE.print("data.snum); + INTERFACE.print(">"); + } else if(tt->active && tt->signal>0.9){ + tt->active=false; + INTERFACE.print("data.snum); + INTERFACE.print(">"); + } + } // loop over all sensors + +} // Sensor::check + +/////////////////////////////////////////////////////////////////////////////// + +Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ + Sensor *tt; + + if(firstSensor==NULL){ + firstSensor=(Sensor *)calloc(1,sizeof(Sensor)); + tt=firstSensor; + } else if((tt=get(snum))==NULL){ + tt=firstSensor; + while(tt->nextSensor!=NULL) + tt=tt->nextSensor; + tt->nextSensor=(Sensor *)calloc(1,sizeof(Sensor)); + tt=tt->nextSensor; + } + + if(tt==NULL){ // problem allocating memory + if(v==1) + INTERFACE.print(""); + return(tt); + } + + tt->data.snum=snum; + tt->data.pin=pin; + tt->data.pullUp=(pullUp==0?LOW:HIGH); + tt->active=false; + tt->signal=1; + pinMode(pin,INPUT); // set mode to input + digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor + + if(v==1) + INTERFACE.print(""); + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Sensor* Sensor::get(int n){ + Sensor *tt; + for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextSensor); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::remove(int n){ + Sensor *tt,*pp; + + for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor); + + if(tt==NULL){ + INTERFACE.print(""); + return; + } + + if(tt==firstSensor) + firstSensor=tt->nextSensor; + else + pp->nextSensor=tt->nextSensor; + + free(tt); + + INTERFACE.print(""); +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::show(){ + Sensor *tt; + + if(firstSensor==NULL){ + INTERFACE.print(""); + return; + } + + for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + INTERFACE.print("data.snum); + INTERFACE.print(" "); + INTERFACE.print(tt->data.pin); + INTERFACE.print(" "); + INTERFACE.print(tt->data.pullUp); + INTERFACE.print(">"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::status(){ + Sensor *tt; + + if(firstSensor==NULL){ + INTERFACE.print(""); + return; + } + + for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + INTERFACE.print(tt->active?"data.snum); + INTERFACE.print(">"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::parse(char *c){ + int n,s,m; + Sensor *t; + + switch(sscanf(c,"%d %d %d",&n,&s,&m)){ + + case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH) + create(n,s,m,1); + break; + + case 1: // argument is a string with id number only + remove(n); + break; + + case -1: // no arguments + show(); + break; + + case 2: // invalid number of arguments + INTERFACE.print(""); + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::load(){ + struct SensorData data; + Sensor *tt; + + for(int i=0;idata.nSensors;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.snum,data.pin,data.pullUp); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::store(){ + Sensor *tt; + + tt=firstSensor; + EEStore::eeStore->data.nSensors=0; + + while(tt!=NULL){ + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextSensor; + EEStore::eeStore->data.nSensors++; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +Sensor *Sensor::firstSensor=NULL; + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h new file mode 100644 index 0000000..aed60ee --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h @@ -0,0 +1,41 @@ +/********************************************************************** + +Sensor.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef Sensor_h +#define Sensor_h + +#include "Arduino.h" + +#define SENSOR_DECAY 0.03 + +struct SensorData { + int snum; + byte pin; + byte pullUp; +}; + +struct Sensor{ + static Sensor *firstSensor; + SensorData data; + boolean active; + float signal; + Sensor *nextSensor; + static void load(); + static void store(); + static Sensor *create(int, int, int, int=0); + static Sensor* get(int); + static void remove(int); + static void show(); + static void status(); + static void parse(char *c); + static void check(); +}; // Sensor + +#endif + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp new file mode 100644 index 0000000..4d3f9b2 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp @@ -0,0 +1,571 @@ +/********************************************************************** + +SerialCommand.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +// DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS +// WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS +// ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER +// COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. + +// See SerialCommand::parse() below for defined text commands. + +#include "SerialCommand.h" +#include "DCCpp_Uno.h" +#include "Accessories.h" +#include "Sensor.h" +#include "Outputs.h" +#include "EEStore.h" +#include "Comm.h" + +extern int __heap_start, *__brkval; + +/////////////////////////////////////////////////////////////////////////////// + +char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; +volatile RegisterList *SerialCommand::mRegs; +volatile RegisterList *SerialCommand::pRegs; +CurrentMonitor *SerialCommand::mMonitor; + +/////////////////////////////////////////////////////////////////////////////// + +void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){ + mRegs=_mRegs; + pRegs=_pRegs; + mMonitor=_mMonitor; + sprintf(commandString,""); +} // SerialCommand:SerialCommand + +/////////////////////////////////////////////////////////////////////////////// + +void SerialCommand::process(){ + char c; + + #if COMM_TYPE == 0 + + while(INTERFACE.available()>0){ // while there is data on the serial line + c=INTERFACE.read(); + if(c=='<') // start of new command + sprintf(commandString,""); + else if(c=='>') // end of new command + parse(commandString); + else if(strlen(commandString)') + } // while + + #elif COMM_TYPE == 1 + + EthernetClient client=INTERFACE.available(); + + if(client){ + while(client.connected() && client.available()){ // while there is data on the network + c=client.read(); + if(c=='<') // start of new command + sprintf(commandString,""); + else if(c=='>') // end of new command + parse(commandString); + else if(strlen(commandString)') + } // while + } + + #endif + +} // SerialCommand:process + +/////////////////////////////////////////////////////////////////////////////// + +void SerialCommand::parse(char *com){ + + switch(com[0]){ + +/***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ + + case 't': // +/* + * sets the throttle for a given register/cab combination + * + * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting + * CAB: the short (1-127) or long (128-10293) address of the engine decoder + * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) + * DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train + * + * returns: + * + */ + mRegs->setThrottle(com+1); + break; + +/***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ + + case 'f': // +/* + * turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL) + * NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program + * + * CAB: the short (1-127) or long (128-10293) address of the engine decoder + * + * To set functions F0-F4 on (=1) or off (=0): + * + * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 + * BYTE2: omitted + * + * To set functions F5-F8 on (=1) or off (=0): + * + * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 + * BYTE2: omitted + * + * To set functions F9-F12 on (=1) or off (=0): + * + * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 + * BYTE2: omitted + * + * To set functions F13-F20 on (=1) or off (=0): + * + * BYTE1: 222 + * BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128 + * + * To set functions F21-F28 on (=1) of off (=0): + * + * BYTE1: 223 + * BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128 + * + * returns: NONE + * + */ + mRegs->setFunction(com+1); + break; + +/***** OPERATE STATIONARY ACCESSORY DECODERS ****/ + + case 'a': // +/* + * turns an accessory (stationary) decoder on or off + * + * ADDRESS: the primary address of the decoder (0-511) + * SUBADDRESS: the subaddress of the decoder (0-3) + * ACTIVATE: 1=on (set), 0=off (clear) + * + * Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N, + * from 1 through a max of 2044, where + * + * N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0 + * + * OR + * + * ADDRESS = INT((N - 1) / 4) + 1 + * SUBADDRESS = (N - 1) % 4 + * + * returns: NONE + */ + mRegs->setAccessory(com+1); + break; + +/***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ + + case 'T': // +/* + * : sets turnout ID to either the "thrown" or "unthrown" position + * + * ID: the numeric ID (0-32767) of the turnout to control + * THROW: 0 (unthrown) or 1 (thrown) + * + * returns: or if turnout ID does not exist + * + * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND + * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS + */ + Turnout::parse(com+1); + break; + +/***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ + + case 'Z': // +/* + * : sets output ID to either the "active" or "inactive" state + * + * ID: the numeric ID (0-32767) of the output to control + * ACTIVATE: 0 (active) or 1 (inactive) + * + * returns: or if output ID does not exist + * + * *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND + * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS + */ + Output::parse(com+1); + break; + +/***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ + + case 'S': +/* + * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND + * USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS + */ + Sensor::parse(com+1); + break; + +/***** SHOW STATUS OF ALL SENSORS ****/ + + case 'Q': // +/* + * returns: the status of each sensor ID in the form (active) or (not active) + */ + Sensor::status(); + break; + +/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ + + case 'w': // +/* + * writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track + * + * CAB: the short (1-127) or long (128-10293) address of the engine decoder + * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) + * VALUE: the value to be written to the Configuration Variable memory location (0-255) + * + * returns: NONE +*/ + mRegs->writeCVByteMain(com+1); + break; + +/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ + + case 'b': // +/* + * writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track + * + * CAB: the short (1-127) or long (128-10293) address of the engine decoder + * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) + * BIT: the bit number of the Configurarion Variable regsiter to write (0-7) + * VALUE: the value of the bit to be written (0-1) + * + * returns: NONE +*/ + mRegs->writeCVBitMain(com+1); + break; + +/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'W': // +/* + * writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track + * + * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) + * VALUE: the value to be written to the Configuration Variable memory location (0-255) + * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function + * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function + * + * returns: writeCVByte(com+1); + break; + +/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'B': // +/* + * writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track + * + * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) + * BIT: the bit number of the Configurarion Variable memory location to write (0-7) + * VALUE: the value of the bit to be written (0-1) + * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function + * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function + * + * returns: writeCVBit(com+1); + break; + +/***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'R': // +/* + * reads a Configuration Variable from the decoder of an engine on the programming track + * + * CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024) + * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function + * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function + * + * returns: readCV(com+1); + break; + +/***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ + + case '1': // <1> +/* + * enables power from the motor shield to the main operations and programming tracks + * + * returns: + */ + digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); + INTERFACE.print(""); + break; + +/***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ + + case '0': // <0> +/* + * disables power from the motor shield to the main operations and programming tracks + * + * returns: + */ + digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); + INTERFACE.print(""); + break; + +/***** READ MAIN OPERATIONS TRACK CURRENT ****/ + + case 'c': // +/* + * reads current being drawn on main operations track + * + * returns: + * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme + */ + INTERFACE.print("current)); + INTERFACE.print(">"); + break; + +/***** READ STATUS OF DCC++ BASE STATION ****/ + + case 's': // +/* + * returns status messages containing track power status, throttle status, turn-out status, and a version number + * NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings + * + * returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings + */ + if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN + INTERFACE.print(""); + else + INTERFACE.print(""); + + for(int i=1;i<=MAX_MAIN_REGISTERS;i++){ + if(mRegs->speedTable[i]==0) + continue; + INTERFACE.print("speedTable[i]>0){ + INTERFACE.print(mRegs->speedTable[i]); + INTERFACE.print(" 1>"); + } else{ + INTERFACE.print(-mRegs->speedTable[i]); + INTERFACE.print(" 0>"); + } + } + INTERFACE.print(""); + + INTERFACE.print(""); + #elif COMM_TYPE == 1 + INTERFACE.print(Ethernet.localIP()); + INTERFACE.print(">"); + #endif + + Turnout::show(); + Output::show(); + + break; + +/***** STORE SETTINGS IN EEPROM ****/ + + case 'E': // +/* + * stores settings for turnouts and sensors EEPROM + * + * returns: +*/ + + EEStore::store(); + INTERFACE.print("data.nTurnouts); + INTERFACE.print(" "); + INTERFACE.print(EEStore::eeStore->data.nSensors); + INTERFACE.print(" "); + INTERFACE.print(EEStore::eeStore->data.nOutputs); + INTERFACE.print(">"); + break; + +/***** CLEAR SETTINGS IN EEPROM ****/ + + case 'e': // +/* + * clears settings for Turnouts in EEPROM + * + * returns: +*/ + + EEStore::clear(); + INTERFACE.print(""); + break; + +/***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ + + case ' ': // < > +/* + * simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window + * + * returns: a carriage return +*/ + INTERFACE.println(""); + break; + +/// +/// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES +/// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW +/// + +/***** ENTER DIAGNOSTIC MODE ****/ + + case 'D': // +/* + * changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED + * SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS + */ + + Serial.println("\nEntering Diagnostic Mode..."); + delay(1000); + + bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8 + bitSet(TCCR1B,CS11); + bitClear(TCCR1B,CS10); + + #ifdef ARDUINO_AVR_UNO // Configuration for UNO + + bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4 + bitClear(TCCR0B,CS01); + bitClear(TCCR0B,CS00); + + #else // Configuration for MEGA + + bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8 + bitSet(TCCR3B,CS31); + bitClear(TCCR3B,CS30); + + #endif + + CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256 + CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS + + break; + +/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ + + case 'M': // +/* + * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track + * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER + * + * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet + * BYTE1: first hexidecimal byte in the packet + * BYTE2: second hexidecimal byte in the packet + * BYTE3: optional third hexidecimal byte in the packet + * BYTE4: optional fourth hexidecimal byte in the packet + * BYTE5: optional fifth hexidecimal byte in the packet + * + * returns: NONE + */ + mRegs->writeTextPacket(com+1); + break; + +/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ + + case 'P': //

+/* + * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track + * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER + * + * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet + * BYTE1: first hexidecimal byte in the packet + * BYTE2: second hexidecimal byte in the packet + * BYTE3: optional third hexidecimal byte in the packet + * BYTE4: optional fourth hexidecimal byte in the packet + * BYTE5: optional fifth hexidecimal byte in the packet + * + * returns: NONE + */ + pRegs->writeTextPacket(com+1); + break; + +/***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/ + + case 'F': // +/* + * measure amount of free SRAM memory left on the Arduino based on trick found on the internet. + * Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM. + * Unfortunately not very reliable --- would be great to find a better method + * + * returns: + * where MEM is the number of free bytes remaining in the Arduino's SRAM + */ + int v; + INTERFACE.print(""); + break; + +/***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/ + + case 'L': // +/* + * lists the packet contents of the main operations track registers and the programming track registers + * FOR DIAGNOSTIC AND TESTING USE ONLY + */ + INTERFACE.println(""); + for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){ + INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t"); + INTERFACE.print((int)p); INTERFACE.print("\t"); + INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); + INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); + for(int i=0;i<10;i++){ + INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); + } + INTERFACE.println(""); + } + for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){ + INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t"); + INTERFACE.print((int)p); INTERFACE.print("\t"); + INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); + INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); + for(int i=0;i<10;i++){ + INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); + } + INTERFACE.println(""); + } + INTERFACE.println(""); + break; + + } // switch +}; // SerialCommand::parse + +/////////////////////////////////////////////////////////////////////////////// + + diff --git a/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h new file mode 100644 index 0000000..510ce15 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h @@ -0,0 +1,31 @@ +/********************************************************************** + +SerialCommand.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef SerialCommand_h +#define SerialCommand_h + +#include "PacketRegister.h" +#include "CurrentMonitor.h" + +#define MAX_COMMAND_LENGTH 30 + +struct SerialCommand{ + static char commandString[MAX_COMMAND_LENGTH+1]; + static volatile RegisterList *mRegs, *pRegs; + static CurrentMonitor *mMonitor; + static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *); + static void parse(char *); + static void process(); +}; // SerialCommand + +#endif + + + + diff --git a/DCC-Centrale/Firmware/BaseStation/README.md b/DCC-Centrale/Firmware/BaseStation/README.md new file mode 100644 index 0000000..9b63ac8 --- /dev/null +++ b/DCC-Centrale/Firmware/BaseStation/README.md @@ -0,0 +1,37 @@ +What’s DCC++ +------------ + +DCC++ is an open-source hardware and software system for the operation of DCC-equipped model railroads. + +The system consists of two parts, the DCC++ Base Station and the DCC++ Controller. + +The DCC++ Base Station consists of an Arduino micro controller fitted with an Arduino Motor Shield that can be connected directly to the tracks of a model railroad. + +The DCC++ Controller provides operators with a customizable GUI to control their model railroad. It is written in Java using the Processing graphics library and IDE and communicates with the DCC++ Base Station via a standard serial connection over a USB cable or wireless over BlueTooth. + +What’s in this Repository +------------------------- + +This repository, BaseStation-Uno, contains a complete DCC++ Base Station sketch designed for compiling and uploading into an Arduino Uno. All sketch files are in the folder named DCCpp_Uno. More information about the sketch can be found in the included PDF file. + +To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Uno.ino within the DCCpp_Uno folder using your Arduino IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. + +The latest production release of the Master branch is 1.2.1: + +* Supports both the Arduino Uno and Arduino Mega +* Built-in configuration for both the original Arduino Motor Shield as well as a Pololu MC33926 Motor Shield +* Built-in configuration and support of Ethernet Shields (for use with Mega only) + +For more information on the overall DCC++ system, please follow the links in the PDF file. + +Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository + +The Master branch contains all of the Base Station functionality showed in the DCC++ YouTube channel with the exception of 2 layout-specific modules: + +* Control for an RGB LED Light Strip using pins 44, 45, and 46 on the Mega +* An embedded AutoPilot routine that randomly selects a train to run through the entire layout, after which it is brought back into its original siding and the the patterns repeats with another randomly-selected train. This is the AutoPilot routine showed on the DCC++ YouTube channel. It does not require any computer, not DCC++ Controller to be running (DCC++ Controller contains a much more complicated 3-train Auto Pilot mode, also as shown on the DCC++ YouTube channel). + +Since these modules are very layout-specififc, they are not included in the Master branch. However, they are included in the Development branch. Please feel free to download and copy any relevant code to customize your own version of DCC++ Base Station. + +-December 27, 2015 + diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/DCCpp_Controller.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/DCCpp_Controller.pde new file mode 100644 index 0000000..1dc4d89 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/DCCpp_Controller.pde @@ -0,0 +1,198 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER +// COPYRIGHT (c) 2013-2015 Gregg E. Berman +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses +// +////////////////////////////////////////////////////////////////////////// +// +// DCC++ CONTROLLER is a Java program written using the 64-bit Processing Library +// and Processing IDE (version 3.01). +// +// DCC++ CONTROLLER provides users with a fully customizeable graphical +// front end for the total control of model trains and model train layouts +// via its companion program, DCC++ BASE STATION. +// +// DCC++ BASE STATION allows a standard Arduino Uno with an Arduino Motor Shield +// to be used as a fully-functioning digital command and control (DCC) base station +// for controlling model train layouts that conform to current National Model +// Railroad Association (NMRA) DCC standards. +// +// DCC++ CONTROLLER communicates with DCC++ BASE STATION using simple text commands sent +// via a standard USB Serial Cord at speeds of up to 115200 Baud. A Bluetooth Wireless +// Connection may be used in place of a USB Serial Cord without any software modification. +// +// This version of DCC++ CONTROLLER supports: +// +// * Multi-Cab / Multi-Throttle configurations using 128-step speed control +// * 2-byte and 4-byte cab addresses +// * Customizable cab function buttons F0-F12 +// * User-created multi-layout track plan +// * Customizeable turnouts and crossovers with controls integrated into track plan +// * Customizeable routes with configurable buttons +// * Customizeable routes with route buttons integrated into track plan +// * Master Power Button +// * Customizable key-controls +// * Real-time current monitor +// * Optional track-integrated sensors +// * Optional user-created Auto Pilot routines (when used with track-integrated sensors) +// * Manual activation/de-activation of accessory functions using 512 addresses, each with 4 sub-addresses +// * Programming on the Main Operations Track +// - write configuration variable bytes +// - set/clear specific configuration variable bits +// * Programming on the Programming Track +// - write configuration variable bytes +// - read configuration variable bytes +// +// With the exception of a standard 15V power supply for the Arduino Uno that can +// be purchased in any electronics store, no additional hardware is required. +// +// Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or +// commercial hardware, software, interfaces, specifications, or methods related +// to the control of model trains using NMRA DCC standards. Both programs are wholly +// original, developed by the author, and are not derived from any known commercial, +// free, or open-source model railroad control packages by any other parties. +// +// However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and +// embedded libraries associated with Arduino and Processing. Tremendous thanks to those +// responsible for these terrific open-source initiatives that enable programs like +// DCC++ to be developed and distributed in the same fashion. +// +// REFERENCES: +// +// NMRA DCC Standards: http://www.nmra.org/standards/DCC/standards_rps/DCCStds.html +// Arduino: http://www.arduino.cc/ +// Processing: http://processing.org/ +// GNU General Public License: http://opensource.org/licenses/GPL-3.0 +// +////////////////////////////////////////////////////////////////////////// + +import processing.serial.*; +import processing.net.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.*; + +final String CONTROLLER_VERSION = "3.0"; +final int BASE_BAUD = 115200; +final int SCREEN_WIDTH = 1366; +final int SCREEN_HEIGHT = 768; +final String STATUS_FILE = "dccStatus.xml"; + +////////////////////////////////////////////////////////////////////////// + +void settings(){ + size(SCREEN_WIDTH,SCREEN_HEIGHT); +} + +////////////////////////////////////////////////////////////////////////// + +void setup(){ + Initialize(); +} + +////////////////////////////////////////////////////////////////////////// + +void draw(){ + + background(backgroundColor); + + for(DccComponent dcc : dccComponents) + dcc.display(); + + if(frameCount==1) // if this is the first frame, just display components and return (otherwise user stare at a blank screen while serial is opening + return; + + if(frameCount==2) // is this is the second frame, open the serial port --- screen will have already been displayed in prior frame + aPort.open(arduinoPortXML.getContent()); + + for(int i=buttonQueue2.size()-1;i>=0;i--){ + buttonQueue2.get(i).init(); + buttonQueue2.remove(i); + } + + for(int i=buttonQueue.size()-1;i>=0;i--){ + buttonQueue2.add(buttonQueue.get(i));; + buttonQueue.remove(i); + } + + if(!mousePressed){ + cursorType=ARROW; + previousComponent=selectedComponent; + selectedComponent=null; + + int nComponents = dccComponents.size(); + + for(int i=nComponents-1;i>=0;i--) + dccComponents.get(i).check(); + + cursor(cursorType); + } + + int m=millis(); + if(m-lastTime>250 && aPort!=null && currentMeter.isOn){ + lastTime=m; + aPort.write(""); + } + + msgBoxClock.setMessage(nf(hour(),2)+":"+nf(minute(),2)+":"+nf(second(),2)); + + if(saveXMLFlag){ + try{ + saveXML(dccStatusXML,STATUS_FILE); + saveXMLFlag=false; + } catch(Exception e){ + println("Couldn't save. Will retry"); + } + } + + autoPilot.safetyCheck(); + +} // draw + +////////////////////////////////////////////////////////////////////////// + +abstract class DccComponent{ + Window window=null; + int xPos, yPos; + String componentName="NAME NOT DEFINED"; + abstract void display(); + void check(){}; + void pressed(){}; + void rightClick(){}; + void shiftPressed(){}; + void released(){}; + void drag(){}; + void init(){}; + + protected int xWindow(){ + if(window==null) + return 0; + return window.xPos; + } + + protected int yWindow(){ + if(window==null) + return 0; + return window.yPos; + } +} + +////////////////////////////////////////////////////////////////////////// + +interface CallBack{ + void execute(int n, String c); +} + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/controllerConfig.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/controllerConfig.pde new file mode 100644 index 0000000..a3a3985 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/controllerConfig.pde @@ -0,0 +1,1039 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Configuration and Initialization +// +// * Defines all global variables and objects +// +// * Reads and loads previous status data from status files +// +// * Implements track layout(s), throttles, track buttons, route buttons, +// cab buttons, function buttons, windows, current meter, +// and all other user-specified components +// +////////////////////////////////////////////////////////////////////////// + +// DECLARE "GLOBAL" VARIABLES and OBJECTS + + PApplet Applet = this; // Refers to this program --- needed for Serial class + + int cursorType; + String baseID; + boolean keyHold=false; + boolean saveXMLFlag=false; + int lastTime; + PFont throttleFont, messageFont, buttonFont; + color backgroundColor; + XML dccStatusXML, arduinoPortXML, sensorButtonsXML, autoPilotXML, cabDefaultsXML, serverListXML; + + DccComponent selectedComponent, previousComponent; + ArrayList dccComponents = new ArrayList(); + ArrayList cabButtons = new ArrayList(); + ArrayList callBacks = new ArrayList(); + ArrayList buttonQueue = new ArrayList(); + ArrayList buttonQueue2 = new ArrayList(); + HashMap remoteButtonsHM = new HashMap(); + ArrayList msgAutoCab = new ArrayList(); + HashMap sensorsHM = new HashMap(); + HashMap cabsHM = new HashMap(); + HashMap trackButtonsHM = new HashMap(); + + ArduinoPort aPort; + PowerButton powerButton; + AutoPilotButton autoPilot; + CleaningCarButton cleaningCab; + Throttle throttleA; + Layout layout,layout2,layoutBridge; + MessageBox msgBoxMain, msgBoxDiagIn, msgBoxDiagOut, msgBoxClock; + CurrentMeter currentMeter; + Window mainWindow, accWindow, progWindow, portWindow, extrasWindow, opWindow, diagWindow, autoWindow, sensorWindow, ledWindow; + ImageWindow imageWindow; + JPGWindow helpWindow; + MessageBox msgAutoState, msgAutoTimer; + InputBox activeInputBox; + InputBox accAddInput, accSubAddInput; + InputBox progCVInput, progHEXInput, progDECInput, progBINInput; + InputBox opCabInput, opCVInput, opHEXInput, opDECInput, opBINInput, opBitInput; + InputBox shortAddInput, longAddInput; + MessageBox activeAddBox; + MessageBox portBox, portNumBox; + MessageBox ledHueMsg, ledSatMsg, ledValMsg, ledRedMsg, ledGreenMsg, ledBlueMsg; + PortScanButton portScanButton; + LEDColorButton ledColorButton; + +// DECLARE TRACK BUTTONS, ROUTE BUTTONS, and CAB BUTTONS WHICH WILL BE DEFINED BELOW AND USED "GLOBALLY" + + TrackButton tButton1,tButton2,tButton3,tButton4,tButton5; + TrackButton tButton6,tButton7,tButton8,tButton9,tButton10; + TrackButton tButton20,tButton30,tButton40,tButton50; + + RouteButton rButton1,rButton2,rButton3,rButton4,rButton5,rButton6,rButton7; + RouteButton rButton10,rButton11,rButton12,rButton13,rButton14; + RouteButton rButtonR1,rButtonR2,rButton15,rButton16,rButton17,rButtonSpiral,rButtonReset,rButtonBridge; + + CabButton cab8601,cab54,cab1202,cab1506,cab622,cab2004,cab6021; + +//////////////////////////////////////////////////////////////////////// +// Initialize --- configures everything! +//////////////////////////////////////////////////////////////////////// + + void Initialize(){ + colorMode(RGB,255); + throttleFont=loadFont("OCRAExtended-26.vlw"); + messageFont=loadFont("LucidaConsole-18.vlw"); + buttonFont=loadFont("LucidaConsole-18.vlw"); + rectMode(CENTER); + textAlign(CENTER,CENTER); + backgroundColor=color(50,50,60); + + aPort=new ArduinoPort(); + +// READ, OR CREATE IF NEEDED, XML DCC STATUS FILE + + dccStatusXML=loadXML(STATUS_FILE); + if(dccStatusXML==null){ + dccStatusXML=new XML("dccStatus"); + } + + arduinoPortXML=dccStatusXML.getChild("arduinoPort"); + if(arduinoPortXML==null){ + arduinoPortXML=dccStatusXML.addChild("arduinoPort"); + arduinoPortXML.setContent("Emulator"); + } + + serverListXML=dccStatusXML.getChild("serverList"); + if(serverListXML==null){ + serverListXML=dccStatusXML.addChild("serverList"); + serverListXML.setContent("127.0.0.1"); + } + + sensorButtonsXML=dccStatusXML.getChild("sensorButtons"); + if(sensorButtonsXML==null){ + sensorButtonsXML=dccStatusXML.addChild("sensorButtons"); + } + + autoPilotXML=dccStatusXML.getChild("autoPilot"); + if(autoPilotXML==null){ + autoPilotXML=dccStatusXML.addChild("autoPilot"); + } + + cabDefaultsXML=dccStatusXML.getChild("cabDefaults"); + if(cabDefaultsXML==null){ + cabDefaultsXML=dccStatusXML.addChild("cabDefaults"); + } + + saveXMLFlag=true; + +// CREATE THE ACCESSORY CONTROL WINDOW + + accWindow = new Window(500,200,300,160,color(200,200,200),color(200,50,50)); + new DragBar(accWindow,0,0,300,10,color(200,50,50)); + new CloseButton(accWindow,288,0,10,10,color(200,50,50),color(255,255,255)); + new MessageBox(accWindow,150,22,0,0,color(200,200,200),20,"Accessory Control",color(200,50,50)); + new MessageBox(accWindow,20,60,-1,0,color(200,200,200),16,"Acc Address (0-511):",color(200,50,50)); + accAddInput = new InputBox(accWindow,230,60,16,color(200,200,200),color(50,50,200),3,InputType.DEC); + new MessageBox(accWindow,20,90,-1,0,color(200,200,200),16,"Sub Address (0-3):",color(200,50,50)); + accSubAddInput = new InputBox(accWindow,230,90,16,color(200,200,200),color(50,50,200),1,InputType.DEC); + new AccessoryButton(accWindow,90,130,55,25,100,18,"ON",accAddInput,accSubAddInput); + new AccessoryButton(accWindow,210,130,55,25,0,18,"OFF",accAddInput,accSubAddInput); + accAddInput.setNextBox(accSubAddInput); + accSubAddInput.setNextBox(accAddInput); + +// CREATE THE SERIAL PORT WINDOW + + portWindow = new Window(500,200,500,170,color(200,200,200),color(200,50,50)); + new DragBar(portWindow,0,0,500,10,color(200,50,50)); + new CloseButton(portWindow,488,0,10,10,color(200,50,50),color(255,255,255)); + new MessageBox(portWindow,250,22,0,0,color(200,200,200),20,"Select Arduino Port",color(200,50,50)); + portScanButton = new PortScanButton(portWindow,100,60,85,20,100,18,"SCAN"); + new PortScanButton(portWindow,400,60,85,20,0,18,"CONNECT"); + new PortScanButton(portWindow,120,140,15,20,120,18,"<"); + new PortScanButton(portWindow,380,140,15,20,120,18,">"); + portBox = new MessageBox(portWindow,250,100,380,25,color(250,250,250),20,"",color(50,150,50)); + portBox.setMessage("Please press SCAN",color(150,50,50)); + portNumBox = new MessageBox(portWindow,250,140,0,0,color(200,200,200),20,"",color(50,50,50)); + +// CREATE THE PROGRAMMING CVs ON THE PROGRAMMING TRACK WINDOW + + progWindow = new Window(500,100,500,400,color(200,180,200),color(50,50,200)); + new DragBar(progWindow,0,0,500,10,color(50,50,200)); + new CloseButton(progWindow,488,0,10,10,color(50,50,200),color(255,255,255)); + new RectButton(progWindow,250,30,210,30,40,color(0),18,"Programming Track",ButtonType.TI_COMMAND,101); + + new MessageBox(progWindow,20,90,-1,0,color(200,180,200),16,"CV (1-1024):",color(50,50,200)); + new MessageBox(progWindow,20,130,-1,0,color(200,180,200),16,"Value (HEX):",color(50,50,200)); + new MessageBox(progWindow,20,160,-1,0,color(200,180,200),16,"Value (DEC):",color(50,50,200)); + new MessageBox(progWindow,20,190,-1,0,color(200,180,200),16,"Value (BIN):",color(50,50,200)); + progCVInput = new InputBox(progWindow,150,90,16,color(200,180,200),color(200,50,50),4,InputType.DEC); + progHEXInput = new InputBox(progWindow,150,130,16,color(200,180,200),color(200,50,50),2,InputType.HEX); + progDECInput = new InputBox(progWindow,150,160,16,color(200,180,200),color(200,50,50),3,InputType.DEC); + progBINInput = new InputBox(progWindow,150,190,16,color(200,180,200),color(200,50,50),8,InputType.BIN); + progCVInput.setNextBox(progHEXInput); + progHEXInput.setNextBox(progDECInput); + progDECInput.setNextBox(progBINInput); + progDECInput.linkBox(progHEXInput); + progBINInput.setNextBox(progHEXInput); + progBINInput.linkBox(progHEXInput); + new ProgWriteReadButton(progWindow,300,90,65,25,100,14,"READ",progCVInput,progHEXInput); + new ProgWriteReadButton(progWindow,390,90,65,25,0,14,"WRITE",progCVInput,progHEXInput); + + new MessageBox(progWindow,20,240,-1,0,color(200,180,200),16,"ENGINE ADDRESSES",color(50,50,200)); + new MessageBox(progWindow,20,280,-1,0,color(200,180,200),16,"Short (1-127):",color(50,50,200)); + new MessageBox(progWindow,20,310,-1,0,color(200,180,200),16,"Long (0-10239):",color(50,50,200)); + new MessageBox(progWindow,20,340,-1,0,color(200,180,200),16,"Active :",color(50,50,200)); + shortAddInput = new InputBox(progWindow,190,280,16,color(200,180,200),color(200,50,50),3,InputType.DEC); + longAddInput = new InputBox(progWindow,190,310,16,color(200,180,200),color(200,50,50),5,InputType.DEC); + activeAddBox = new MessageBox(progWindow,190,340,-1,0,color(200,180,200),16,"?",color(200,50,50)); + new ProgAddReadButton(progWindow,300,240,65,25,100,14,"READ",shortAddInput,longAddInput,activeAddBox); + new ProgShortAddWriteButton(progWindow,300,280,65,25,0,14,"WRITE",shortAddInput); + new ProgLongAddWriteButton(progWindow,300,310,65,25,0,14,"WRITE",longAddInput); + new ProgLongShortButton(progWindow,300,340,65,25,0,14,"Long",activeAddBox); + new ProgLongShortButton(progWindow,390,340,65,25,0,14,"Short",activeAddBox); + +// CREATE THE PROGRAMMING CVs ON THE MAIN OPERATIONS TRACK WINDOW + + opWindow = new Window(500,100,500,300,color(220,200,200),color(50,50,200)); + new DragBar(opWindow,0,0,500,10,color(50,50,200)); + new CloseButton(opWindow,488,0,10,10,color(50,50,200),color(255,255,255)); + new MessageBox(opWindow,250,30,0,0,color(220,200,200),20,"Operations Programming",color(50,100,50)); + new MessageBox(opWindow,20,90,-1,0,color(220,200,200),16,"Cab Number :",color(50,50,200)); + new MessageBox(opWindow,20,120,-1,0,color(220,200,200),16,"CV (1-1024):",color(50,50,200)); + new MessageBox(opWindow,20,160,-1,0,color(220,200,200),16,"Value (HEX):",color(50,50,200)); + new MessageBox(opWindow,20,190,-1,0,color(220,200,200),16,"Value (DEC):",color(50,50,200)); + new MessageBox(opWindow,20,220,-1,0,color(220,200,200),16,"Value (BIN):",color(50,50,200)); + opCabInput = new InputBox(opWindow,150,90,16,color(220,200,200),color(200,50,50),5,InputType.DEC); + opCVInput = new InputBox(opWindow,150,120,16,color(220,200,200),color(200,50,50),4,InputType.DEC); + opHEXInput = new InputBox(opWindow,150,160,16,color(220,200,200),color(200,50,50),2,InputType.HEX); + opDECInput = new InputBox(opWindow,150,190,16,color(220,200,200),color(200,50,50),3,InputType.DEC); + opBINInput = new InputBox(opWindow,150,220,16,color(220,200,200),color(200,50,50),8,InputType.BIN); + opCVInput.setNextBox(opHEXInput); + opHEXInput.setNextBox(opDECInput); + opDECInput.setNextBox(opBINInput); + opDECInput.linkBox(opHEXInput); + opBINInput.setNextBox(opHEXInput); + opBINInput.linkBox(opHEXInput); + new OpWriteButton(opWindow,300,90,65,25,0,14,"WRITE",opCVInput,opHEXInput); + new MessageBox(opWindow,20,260,-1,0,color(220,200,200),16," Bit (0-7):",color(50,50,200)); + opBitInput = new InputBox(opWindow,150,260,16,color(220,200,200),color(200,50,50),1,InputType.DEC); + new OpWriteButton(opWindow,300,260,65,25,50,14,"SET",opCVInput,opBitInput); + new OpWriteButton(opWindow,390,260,65,25,150,14,"CLEAR",opCVInput,opBitInput); + +// CREATE THE DCC++ CONTROL <-> DCC++ BASE STATION COMMUNICATION DIAGNOSTICS WINDOW + + diagWindow = new Window(400,300,500,120,color(175),color(50,200,50)); + new DragBar(diagWindow,0,0,500,10,color(50,200,50)); + new CloseButton(diagWindow,488,0,10,10,color(50,200,50),color(255,255,255)); + new MessageBox(diagWindow,250,20,0,0,color(175),18,"Diagnostics Window",color(50,50,200)); + new MessageBox(diagWindow,10,60,-1,0,color(175),18,"Sent:",color(50,50,200)); + msgBoxDiagOut=new MessageBox(diagWindow,250,60,0,0,color(175),18,"---",color(50,50,200)); + new MessageBox(diagWindow,10,90,-1,0,color(175),18,"Proc:",color(50,50,200)); + msgBoxDiagIn=new MessageBox(diagWindow,250,90,0,0,color(175),18,"---",color(50,50,200)); + +// CREATE THE AUTOPILOT DIAGNOSTICS WINDOW + + autoWindow = new Window(400,300,500,330,color(175),color(50,200,50)); + new DragBar(autoWindow,0,0,500,10,color(50,200,50)); + new CloseButton(autoWindow,488,0,10,10,color(50,200,50),color(255,255,255)); + new MessageBox(autoWindow,250,20,0,0,color(175),18,"AutoPilot Window",color(50,50,150)); + msgAutoState=new MessageBox(autoWindow,0,180,-1,0,color(175),18,"?",color(50,50,250)); + msgAutoTimer=new MessageBox(autoWindow,55,310,-1,0,color(175),18,"Timer =",color(50,50,250)); + +// CREATE THE SENSORS DIAGNOSTICS WINDOW + + sensorWindow = new Window(400,300,500,350,color(175),color(50,200,50)); + new DragBar(sensorWindow,0,0,500,10,color(50,200,50)); + new CloseButton(sensorWindow,488,0,10,10,color(50,200,50),color(255,255,255)); + new MessageBox(sensorWindow,250,20,0,0,color(175),18,"Sensors Window",color(50,50,150)); + +// CREATE THE HELP WINDOW + + helpWindow=new JPGWindow("helpMenu.jpg",1000,650,100,50,color(0,100,0)); + +// CREATE THE EXTRAS WINDOW: + + extrasWindow = new Window(500,200,500,250,color(255,255,175),color(100,100,200)); + new DragBar(extrasWindow,0,0,500,10,color(100,100,200)); + new CloseButton(extrasWindow,488,0,10,10,color(100,100,200),color(255,255,255)); + new MessageBox(extrasWindow,250,20,0,0,color(175),18,"Extra Functions",color(50,50,200)); +// new RectButton(extrasWindow,260,80,120,50,85,color(0),16,"Sound\nEffects",0); + +// CREATE THE LED LIGHT-STRIP WINDOW: + + ledWindow = new Window(500,200,550,425,color(0),color(0,0,200)); + new DragBar(ledWindow,0,0,550,10,color(0,0,200)); + new CloseButton(ledWindow,538,0,10,10,color(0,0,200),color(200,200,200)); + new MessageBox(ledWindow,275,20,0,0,color(175),18,"LED Light Strip",color(200,200,200)); + ledColorButton=new LEDColorButton(ledWindow,310,175,30,201,0.0,0.0,1.0); + new LEDColorSelector(ledWindow,150,175,100,ledColorButton); + new LEDValSelector(ledWindow,50,330,200,30,ledColorButton); + ledHueMsg = new MessageBox(ledWindow,360,80,-1,0,color(175),18,"Hue: -",color(200,200,200)); + ledSatMsg = new MessageBox(ledWindow,360,115,-1,0,color(175),18,"Sat: -",color(200,200,200)); + ledValMsg = new MessageBox(ledWindow,360,150,-1,0,color(175),18,"Val: -",color(200,200,200)); + ledRedMsg = new MessageBox(ledWindow,360,185,-1,0,color(175),18,"Red: -",color(200,200,200)); + ledGreenMsg = new MessageBox(ledWindow,360,220,-1,0,color(175),18,"Green: -",color(200,200,200)); + ledBlueMsg = new MessageBox(ledWindow,360,255,-1,0,color(175),18,"Blue: -",color(200,200,200)); + +// CREATE TOP-OF-SCREEN MESSAGE BAR AND HELP BUTTON + + msgBoxMain=new MessageBox(width/2,12,width,25,color(200),20,"Searching for Base Station: "+arduinoPortXML.getContent(),color(30,30,150)); + new HelpButton(width-50,12,22,22,150,20,"?"); + +// CREATE CLOCK + + msgBoxClock=new MessageBox(30,700,-100,30,backgroundColor,30,"00:00:00",color(255,255,255)); + +// CREATE POWER BUTTON, QUIT BUTTON, and CURRENT METER + + powerButton=new PowerButton(75,475,100,30,100,18,"POWER"); + new QuitButton(200,475,100,30,250,18,"QUIT"); + currentMeter = new CurrentMeter(25,550,150,100,675,5); + +// CREATE THROTTLE, DEFINE CAB BUTTONS, and SET FUNCTIONS FOR EACH CAB + + int tAx=175; + int tAy=225; + int rX=800; + int rY=550; + + throttleA=new Throttle(tAx,tAy,1.3); + + cab2004 = new CabButton(tAx-125,tAy-150,50,30,150,15,2004,throttleA); + cab2004.setThrottleDefaults(100,50,-50,-45); + cab2004.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab2004.setFunction(35,15,60,22,60,10,0,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab2004.setFunction(35,45,60,22,60,10,1,"Tailight",ButtonType.NORMAL,CabFunction.R_LIGHT); + + cab622 = new CabButton(tAx-125,tAy-100,50,30,150,15,622,throttleA); + cab622.setThrottleDefaults(53,30,-20,-13); + cab622.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab622.setFunction(35,15,60,22,60,10,0,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab622.setFunction(35,45,60,22,60,10,1,"Tailight",ButtonType.NORMAL,CabFunction.R_LIGHT); + + cab8601 = new CabButton(tAx-125,tAy-50,50,30,150,15,8601,throttleA); + cab8601.setThrottleDefaults(77,46,-34,-30); + cab8601.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab8601.setFunction(35,15,60,22,60,10,0,"Lights",ButtonType.NORMAL,CabFunction.F_LIGHT,CabFunction.R_LIGHT); + + cab6021 = new CabButton(tAx-125,tAy,50,30,150,15,6021,throttleA); + cab6021.setThrottleDefaults(50,25,-25,-15); + cab6021.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab6021.setFunction(35,15,60,22,60,10,0,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab6021.setFunction(35,45,60,22,60,10,1,"Tailight",ButtonType.NORMAL,CabFunction.R_LIGHT); + + cab54 = new CabButton(tAx-125,tAy+50,50,30,150,15,54,throttleA); + cab54.setThrottleDefaults(34,14,-5,-3); + cab54.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab54.setFunction(35,15,60,22,60,10,10,"Radiator\nFan",ButtonType.NORMAL); + cab54.setFunction(35,45,60,22,60,10,11,"Air Fill\n/Release",ButtonType.ONESHOT); + cab54.setFunction(35,75,60,22,60,10,14,"Passenger\nDep/Arr",ButtonType.ONESHOT); + cab54.setFunction(35,105,60,22,60,10,18,"City\nSounds",ButtonType.ONESHOT); + cab54.setFunction(35,135,60,22,60,10,19,"Farm\nSounds",ButtonType.ONESHOT); + cab54.setFunction(35,165,60,22,60,10,21,"Lumber\nMill",ButtonType.ONESHOT); + cab54.setFunction(35,195,60,22,60,10,20,"Industry\nSounds",ButtonType.ONESHOT); + cab54.setFunction(35,225,60,22,60,10,13,"Crossing\nHorn",ButtonType.ONESHOT,CabFunction.S_HORN); + cab54.setFunction(35,255,60,22,60,10,22,"Alternate\nHorn",ButtonType.NORMAL); + cab54.setFunction(35,285,60,22,60,10,8,"Mute",ButtonType.NORMAL); + cab54.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab54.setFunction(35,15,60,22,60,10,0,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab54.setFunction(35,45,60,22,60,10,1,"Bell",ButtonType.NORMAL,CabFunction.BELL); + cab54.setFunction(35,75,60,22,60,10,2,"Horn",ButtonType.HOLD,CabFunction.HORN); + cab54.setFunction(35,105,60,22,60,10,3,"MARS\nLight",ButtonType.REVERSE,CabFunction.D_LIGHT); + cab54.setFunction(35,135,16,22,60,10,9,"1",ButtonType.NORMAL); + cab54.setFunction(14,135,16,22,60,10,5,"+",ButtonType.ONESHOT); + cab54.setFunction(56,135,16,22,60,10,6,"-",ButtonType.ONESHOT); + cab54.setFunction(35,165,60,22,60,10,15,"Freight\nDep/Arr",ButtonType.ONESHOT); + cab54.setFunction(35,195,60,22,60,10,16,"Facility\nShop",ButtonType.ONESHOT); + cab54.setFunction(35,225,60,22,60,10,17,"Crew\nRadio",ButtonType.ONESHOT); + cab54.setFunction(35,255,60,22,60,10,7,"Coupler",ButtonType.ONESHOT); + cab54.setFunction(35,285,60,22,60,10,4,"Dynamic\nBrake",ButtonType.NORMAL); + cab54.setFunction(35,315,60,22,60,10,12,"Brake\nSqueal",ButtonType.ONESHOT); + + cab1202 = new CabButton(tAx-125,tAy+100,50,30,150,15,1202,throttleA); + cab1202.setThrottleDefaults(34,25,-24,-18); + cab1202.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab1202.setFunction(35,15,60,22,60,10,0,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab1202.setFunction(35,45,60,22,60,10,1,"Tailight",ButtonType.NORMAL,CabFunction.R_LIGHT); + + cab1506 = new CabButton(tAx-125,tAy+150,50,30,150,15,1506,throttleA); + cab1506.setThrottleDefaults(61,42,-30,-22); + cab1506.functionButtonWindow(220,59,70,340,backgroundColor,backgroundColor); + cab1506.setFunction(35,15,60,22,60,10,1,"Headlight",ButtonType.NORMAL,CabFunction.F_LIGHT); + cab1506.setFunction(35,45,60,22,60,10,0,"Tailight",ButtonType.NORMAL,CabFunction.R_LIGHT); + cab1506.setFunction(35,75,60,22,60,10,3,"D-Lights",ButtonType.NORMAL,CabFunction.D_LIGHT); + +// CREATE THE IMAGE WINDOW FOR THROTTLE A (must be done AFTER throttle A is defined above) + + imageWindow=new ImageWindow(throttleA,975,450,200,50,color(200,50,50)); + +// CREATE AUTO PILOT BUTTON and CLEANING CAR BUTTON (must be done AFTER cab buttons are defined above) + + autoPilot=new AutoPilotButton(325,550,100,50,30,18,"AUTO\nPILOT"); + cleaningCab=new CleaningCarButton(extrasWindow,28,80,80,120,50,40,16,"Cleaning\nCar"); + +// CREATE MAIN LAYOUT AND DEFINE ALL TRACKS + + layout=new Layout(325,50,1000,80*25.4,36*25.4); + + Track bridgeA = new Track(layout,20,450,62,90); + Track bridgeB = new Track(bridgeA,1,348,-90); + Track bridgeC = new Track(bridgeB,1,399); + Track t5A = new Track(bridgeC,1,126); + Track loop3A = new Track(t5A,1,682); + Track loop3B = new Track(loop3A,1,381,-180); + Track loop3C = new Track(loop3B,1,124); + Track t20A2 = new Track(loop3C,1,126); + Track t20B2 = new Track(loop3C,1,481,15); + Track t20B1 = new Track(t20B2,1,481,-15); + Track loop2A3A = new Track(t20A2,1,120); + Track t30A1 = new Track(loop2A3A,1,126); + Track t30A2 = new Track(t30A1,1,126); + Track t30B1 = new Track(loop2A3A,1,481,-15); + Track t30B4 = new Track(t30B1,1,481,15); + Track loop2C = new Track(t30A2,1,122); + Track t10A3 = new Track(loop2C,1,126); + Track t10B3 = new Track(loop2C,1,481,15); + Track t10A4 = new Track(t10A3,1,126); + Track t10B2 = new Track(t10B3,1,481,-15); + Track loop2D = new Track(t10A4,1,62); + Track loop2E = new Track(loop2D,1,315,-165); + Track loop2F = new Track(loop2E,1,128); + Track loop2G = new Track(loop2F,1,315,-15); + Track loop2H = new Track(loop2G,1,742); + Track t50A2 = new Track(loop2H,1,126); + Track loop2A = new Track(t50A2,1,315,-180); + Track loop2B = new Track(loop2A,1,308); + Track t30A3 = new Track(loop2B,1,126); + Track t30A4 = new Track(t30A3,1,126); + Track t30B3 = new Track(loop2B,1,481,15); + Track t30B2 = new Track(t30B3,1,481,-15); + Track loop1A2A = new Track(t30A4,1,60); + Track t40A1 = new Track(loop1A2A,1,126); + Track loop1B = new Track(t40A1,1,248); + Track loop1C = new Track(loop1B,1,282,-165); + Track loop1D = new Track(loop1C,1,128); + Track loop1E = new Track(loop1D,1,282,-15); + Track t4A = new Track(loop1E,1,126); + Track t4B = new Track(loop1E,1,481,-15); + Track loop1F = new Track(t4A,1,494); + Track t50A1 = new Track(loop1F,1,126); + Track t50B1 = new Track(loop1F,1,481,15); + Track t50B2 = new Track(t50B1,1,481,-15); + Track loop1G = new Track(t50A1,1,122); + Track loop1H = new Track(loop1G,1,282,-180); + Track loop1I = new Track(loop1H,1,62); + Track t1A = new Track(loop1I,1,126); + Track t1B = new Track(loop1I,1,481,-15); + Track loop1A = new Track(t1A,1,308); + Track t40A2 = new Track(loop1A,1,126); + Track t40B2 = new Track(loop1A,1,481,15); + Track t40B1 = new Track(t40B2,1,481,-15); + Track s1A = new Track(t40A2,1,60); + Track s1B = new Track(s1A,1,481,-15); + Track s1C = new Track(s1B,1,339); + Track s1 = new Track(s1C,1,50); + Track loop3D = new Track(t20B1,1,370); + Track t20A1 = new Track(loop3D,0,126); + Track t10A1 = new Track(loop3D,1,126); + Track t10B1 = new Track(loop3D,1,481,-15); + Track t10B4 = new Track(t10B1,1,481,15); + Track t10A2 = new Track(t10A1,1,126); + Track loop3E = new Track(t10A2,1,62); + Track loop3F = new Track(loop3E,1,381,-180); + Track loop3G = new Track(loop3F,1,124); + Track loop3H = new Track(loop3G,1,481,15); + Track t5B = new Track(loop3H,1,481,-15); + Track s7A = new Track(t20A1,1,337); + Track s7B = new Track(s7A,1,348,90); + Track s7C = new Track(s7B,1,124); + Track s7D = new Track(s7C,1,481,15); + Track s7E = new Track(s7D,1,124); + Track s7 = new Track(s7E,1,62); + Track t2A = new Track(t1B,1,126); + Track t2B = new Track(t1B,1,481,-15); + Track s2_3_4_5A = new Track(t2A,1,64); + Track t3A = new Track(s2_3_4_5A,1,126); + Track t3B = new Track(s2_3_4_5A,1,481,15); + Track s2A = new Track(t3B,1,30); + Track s2B = new Track(s2A,1,481,15); + Track s2C = new Track(s2B,1,481,-30); + Track s2D = new Track(s2C,1,248); + Track s2 = new Track(s2D,1,50); + Track t6A = new Track(t3A,1,126); + Track t6B = new Track(t3A,1,481,-15); + Track s3A = new Track(t6A,1,556); + Track s3 = new Track(s3A,1,50); + Track t9A = new Track(t6B,1,126); + Track t9B = new Track(t6B,1,481,15); + Track s4A = new Track(t9B,1,479); + Track s4 = new Track(s4A,1,50); + Track s5A = new Track(t9A,1,481,15); + Track s5B = new Track(s5A,1,341); + Track s5 = new Track(s5B,1,50); + Track rLoopA = new Track(t4B,1,282,-45); + Track rLoopB = new Track(rLoopA,1,87); + Track t7A = new Track(rLoopB,1,126); + Track t7B = new Track(rLoopB,1,481,15); + Track rLoopC = new Track(t7A,1,481,15); + Track rLoopD = new Track(rLoopC,1,425,15); + Track s6A = new Track(t7B,1,60); + Track s6B = new Track(s6A,1,282,45); + Track s6C = new Track(s6B,1,481,30); + Track s6D = new Track(s6C,1,188); + Track s6 = new Track(s6D,1,50); + Track bridgeD = new Track(bridgeA,0,348,60); + +// CREATE SECOND LAYOUT FOR SKY BRIDGE AND DEFINE TRACKS + + layout2=new Layout(325,500,400,80*25.4,36*25.4); + layoutBridge=new Layout(layout2); + + Track bridgeE = new Track(bridgeD,1,348,60,layoutBridge); + Track bridgeF = new Track(bridgeE,1,248); + Track t8A = new Track(bridgeF,1,200); + Track t8B = new Track(bridgeF,1,400,-35); + Track bridgeG = new Track(t8A,1,618); + Track bridgeH = new Track(bridgeG,1,282,-226); + Track bridgeI = new Track(bridgeH,1,558); + +// DEFINE SENSORS, MAP TO ARDUINO NUMBERS, AND INDICATE THEIR TRACK LOCATIONS + + new TrackSensor(loop3B,1,30,20,20,1,false); // mappings from Sensor numbers (1..N) to Arduino Pins + new TrackSensor(t50A2,1,315,-174,20,20,2,false); + new TrackSensor(loop2D,1,315,-47,20,20,3,false); + new TrackSensor(loop1B,1,282,-45,20,20,4,false); + new TrackSensor(loop3E,1,381,-45,20,20,5,false); + new TrackSensor(bridgeA,1,348,-10,20,20,6,false); + new TrackSensor(s1A,1,481,-5,20,20,7,true); + new TrackSensor(s2B,1,481,-5,20,20,8,true); + new TrackSensor(t6A,1,175,20,20,9,true); + new TrackSensor(s6A,1,282,10,20,20,10,true); + new TrackSensor(loop1G,1,282,-137,20,20,11,false); + new TrackSensor(t9B,1,100,20,20,12,true); + new TrackSensor(s5A,1,30,20,20,13,true); + new TrackSensor(s7A,1,348,50,20,20,14,true); + +// CREATE TURNOUT BUTTONS and ADD TRACKS FOR EACH TURNOUT + + tButton1 = new TrackButton(20,20,1); + tButton1.addTrack(t1A,0); + tButton1.addTrack(t1B,1); + + tButton2 = new TrackButton(20,82,2); + tButton2.addTrack(t2A,0); + tButton2.addTrack(t2B,1); + + tButton3 = new TrackButton(20,20,3); + tButton3.addTrack(t3A,0); + tButton3.addTrack(t3B,1); + + tButton4 = new TrackButton(20,20,4); + tButton4.addTrack(t4A,0); + tButton4.addTrack(t4B,1); + + tButton5 = new TrackButton(20,20,5); + tButton5.addTrack(t5A,0); + tButton5.addTrack(t5B,1); + + tButton6 = new TrackButton(20,20,6); + tButton6.addTrack(t6A,0); + tButton6.addTrack(t6B,1); + + tButton7 = new TrackButton(20,20,7); + tButton7.addTrack(t7A,0); + tButton7.addTrack(t7B,1); + + tButton8 = new TrackButton(20,20,8); + tButton8.addTrack(t8A,0); + tButton8.addTrack(t8B,1); + + tButton9 = new TrackButton(20,20,9); + tButton9.addTrack(t9A,0); + tButton9.addTrack(t9B,1); + + tButton10 = new TrackButton(20,20,10); + tButton10.addTrack(t10A1,0); + tButton10.addTrack(t10A2,0); + tButton10.addTrack(t10A3,0); + tButton10.addTrack(t10A4,0); + tButton10.addTrack(t10B1,1); + tButton10.addTrack(t10B2,1); + tButton10.addTrack(t10B3,1); + tButton10.addTrack(t10B4,1); + + tButton20 = new TrackButton(20,20,20); + tButton20.addTrack(t20A1,0); + tButton20.addTrack(t20A2,0); + tButton20.addTrack(t20B1,1); + tButton20.addTrack(t20B2,1); + + tButton30 = new TrackButton(20,20,30); + tButton30.addTrack(t30A1,0); + tButton30.addTrack(t30A2,0); + tButton30.addTrack(t30A3,0); + tButton30.addTrack(t30A4,0); + tButton30.addTrack(t30B1,1); + tButton30.addTrack(t30B2,1); + tButton30.addTrack(t30B3,1); + tButton30.addTrack(t30B4,1); + + tButton40 = new TrackButton(20,20,40); + tButton40.addTrack(t40A1,0); + tButton40.addTrack(t40A2,0); + tButton40.addTrack(t40B1,1); + tButton40.addTrack(t40B2,1); + + tButton50 = new TrackButton(20,20,50); + tButton50.addTrack(t50A1,0); + tButton50.addTrack(t50A2,0); + tButton50.addTrack(t50B1,1); + tButton50.addTrack(t50B2,1); + +// CREATE ROUTE BUTTONS and ADD TRACKS and TURNOUT BUTTONS + + rButton1 = new RouteButton(s1,20,20); + rButton1.addTrackButton(tButton40,0); + rButton1.addTrackButton(tButton1,0); + rButton1.addTrack(t1A); + rButton1.addTrack(loop1A); + rButton1.addTrack(t40A2); + rButton1.addTrack(s1A); + rButton1.addTrack(s1B); + rButton1.addTrack(s1C); + rButton1.addTrack(s1); + + rButton2 = new RouteButton(s2,20,20); + rButton2.addTrackButton(tButton3,1); + rButton2.addTrackButton(tButton2,0); + rButton2.addTrackButton(tButton1,1); + rButton2.addTrack(t1B); + rButton2.addTrack(t2A); + rButton2.addTrack(s2_3_4_5A); + rButton2.addTrack(t3B); + rButton2.addTrack(s2A); + rButton2.addTrack(s2B); + rButton2.addTrack(s2C); + rButton2.addTrack(s2D); + rButton2.addTrack(s2); + + rButton3 = new RouteButton(s3,20,20); + rButton3.addTrackButton(tButton6,0); + rButton3.addTrackButton(tButton3,0); + rButton3.addTrackButton(tButton2,0); + rButton3.addTrackButton(tButton1,1); + rButton3.addTrack(t1B); + rButton3.addTrack(t2A); + rButton3.addTrack(s2_3_4_5A); + rButton3.addTrack(t3A); + rButton3.addTrack(t6A); + rButton3.addTrack(s3A); + rButton3.addTrack(s3); + + rButton4 = new RouteButton(s4,20,20); + rButton4.addTrackButton(tButton9,1); + rButton4.addTrackButton(tButton6,1); + rButton4.addTrackButton(tButton3,0); + rButton4.addTrackButton(tButton2,0); + rButton4.addTrackButton(tButton1,1); + rButton4.addTrack(t1B); + rButton4.addTrack(t2A); + rButton4.addTrack(s2_3_4_5A); + rButton4.addTrack(t3A); + rButton4.addTrack(t6B); + rButton4.addTrack(t9B); + rButton4.addTrack(s4A); + rButton4.addTrack(s4); + + rButton5 = new RouteButton(s5,20,20); + rButton5.addTrackButton(tButton9,0); + rButton5.addTrackButton(tButton6,1); + rButton5.addTrackButton(tButton3,0); + rButton5.addTrackButton(tButton2,0); + rButton5.addTrackButton(tButton1,1); + rButton5.addTrack(t1B); + rButton5.addTrack(t2A); + rButton5.addTrack(s2_3_4_5A); + rButton5.addTrack(t3A); + rButton5.addTrack(t6B); + rButton5.addTrack(t9A); + rButton5.addTrack(s5A); + rButton5.addTrack(s5B); + rButton5.addTrack(s5); + + rButton6 = new RouteButton(s6,20,20); + rButton6.addTrackButton(tButton7,1); + rButton6.addTrackButton(tButton4,1); + rButton6.addTrack(t7B); + rButton6.addTrack(t4B); + rButton6.addTrack(rLoopA); + rButton6.addTrack(rLoopB); + rButton6.addTrack(s6A); + rButton6.addTrack(s6B); + rButton6.addTrack(s6C); + rButton6.addTrack(s6D); + rButton6.addTrack(s6); + + rButton7 = new RouteButton(s7,20,20); + rButton7.addTrackButton(tButton20,0); + rButton7.addTrackButton(tButton10,0); + rButton7.addTrack(t20A1); + rButton7.addTrack(t10A1); + rButton7.addTrack(t10A2); + rButton7.addTrack(s7A); + rButton7.addTrack(s7B); + rButton7.addTrack(s7C); + rButton7.addTrack(s7D); + rButton7.addTrack(s7E); + rButton7.addTrack(s7); + rButton7.addTrack(loop3D); + + rButton10 = new RouteButton(rX,rY,80,40,"Inner\nLoop"); + rButton10.addTrackButton(tButton50,0); + rButton10.addTrackButton(tButton1,0); + rButton10.addTrackButton(tButton40,1); + rButton10.addTrackButton(tButton4,0); + rButton10.addTrack(t40B1); + rButton10.addTrack(t40B2); + rButton10.addTrack(t4A); + rButton10.addTrack(t50A1); + rButton10.addTrack(t1A); + rButton10.addTrack(loop1A); + rButton10.addTrack(loop1B); + rButton10.addTrack(loop1C); + rButton10.addTrack(loop1D); + rButton10.addTrack(loop1E); + rButton10.addTrack(loop1F); + rButton10.addTrack(loop1G); + rButton10.addTrack(loop1H); + rButton10.addTrack(loop1I); + + rButton11 = new RouteButton(rX+200,rY,80,40,"Middle\nLoop"); + rButton11.addTrackButton(tButton50,0); + rButton11.addTrackButton(tButton30,1); + rButton11.addTrackButton(tButton10,0); + rButton11.addTrack(t50A2); + rButton11.addTrack(t30B3); + rButton11.addTrack(t30B2); + rButton11.addTrack(t10A3); + rButton11.addTrack(t10A4); + rButton11.addTrack(loop2A); + rButton11.addTrack(loop2B); + rButton11.addTrack(loop2C); + rButton11.addTrack(loop2D); + rButton11.addTrack(loop2E); + rButton11.addTrack(loop2F); + rButton11.addTrack(loop2G); + rButton11.addTrack(loop2H); + + rButton12 = new RouteButton(rX+400,rY,80,40,"Outer\nLoop"); + rButton12.addTrackButton(tButton20,1); + rButton12.addTrackButton(tButton5,1); + rButton12.addTrackButton(tButton10,0); + rButton12.addTrack(t20B2); + rButton12.addTrack(t20B1); + rButton12.addTrack(t10A1); + rButton12.addTrack(t10A2); + rButton12.addTrack(t5B); + rButton12.addTrack(loop3A); + rButton12.addTrack(loop3B); + rButton12.addTrack(loop3C); + rButton12.addTrack(loop3D); + rButton12.addTrack(loop3E); + rButton12.addTrack(loop3F); + rButton12.addTrack(loop3G); + rButton12.addTrack(loop3H); + + rButton13 = new RouteButton(rX+100,rY,80,40,"Inner/Mid"); + rButton13.addTrackButton(tButton50,1); + rButton13.addTrackButton(tButton30,0); + rButton13.addTrackButton(tButton40,0); + rButton13.addTrackButton(tButton4,0); + rButton13.addTrack(t40A1); + rButton13.addTrack(loop1B); + rButton13.addTrack(loop1C); + rButton13.addTrack(loop1D); + rButton13.addTrack(loop1E); + rButton13.addTrack(t4A); + rButton13.addTrack(loop1F); + rButton13.addTrack(t50B1); + rButton13.addTrack(t50B2); + rButton13.addTrack(loop2A); + rButton13.addTrack(loop2B); + rButton13.addTrack(t30A3); + rButton13.addTrack(t30A4); + rButton13.addTrack(loop1A2A); + + rButton14 = new RouteButton(rX+300,rY,80,40,"Mid/Outer"); + rButton14.addTrackButton(tButton5,1); + rButton14.addTrackButton(tButton20,0); + rButton14.addTrackButton(tButton30,0); + rButton14.addTrackButton(tButton10,1); + rButton14.addTrack(t5B); + rButton14.addTrack(loop3A); + rButton14.addTrack(loop3B); + rButton14.addTrack(loop3C); + rButton14.addTrack(t20A2); + rButton14.addTrack(loop2A3A); + rButton14.addTrack(t30A1); + rButton14.addTrack(t30A2); + rButton14.addTrack(loop2C); + rButton14.addTrack(t10B3); + rButton14.addTrack(t10B2); + rButton14.addTrack(loop3E); + rButton14.addTrack(loop3F); + rButton14.addTrack(loop3G); + rButton14.addTrack(loop3H); + + rButtonR1 = new RouteButton(rX,rY+60,80,40,"Reverse+"); + rButtonR1.addTrackButton(tButton4,1); + rButtonR1.addTrackButton(tButton7,0); + rButtonR1.addTrackButton(tButton1,0); + rButtonR1.addTrack(t4B); + rButtonR1.addTrack(rLoopA); + rButtonR1.addTrack(rLoopB); + rButtonR1.addTrack(t7A); + rButtonR1.addTrack(t1A); + + rButtonR2 = new RouteButton(rX+100,rY+60,80,40,"Reverse-"); + rButtonR2.addTrackButton(tButton1,1); + rButtonR2.addTrackButton(tButton2,1); + rButtonR2.addTrackButton(tButton4,0); + rButtonR2.addTrack(t4A); + rButtonR2.addTrack(t1B); + rButtonR2.addTrack(t2B); + rButtonR2.addTrack(rLoopC); + rButtonR2.addTrack(rLoopD); + + rButton15 = new RouteButton(rX+200,rY+60,80,40,"Mid+Outer"); + rButton15.addTrackButton(tButton5,1); + rButton15.addTrackButton(tButton10,1); + rButton15.addTrackButton(tButton20,1); + rButton15.addTrackButton(tButton30,1); + rButton15.addTrackButton(tButton50,0); + rButton15.addTrack(t20B2); + rButton15.addTrack(t20B1); + rButton15.addTrack(t10B1); + rButton15.addTrack(t10B2); + rButton15.addTrack(t5B); + rButton15.addTrack(loop3A); + rButton15.addTrack(loop3B); + rButton15.addTrack(loop3C); + rButton15.addTrack(loop3D); + rButton15.addTrack(loop3E); + rButton15.addTrack(loop3F); + rButton15.addTrack(loop3G); + rButton15.addTrack(loop3H); + rButton15.addTrack(t50A2); + rButton15.addTrack(t30B3); + rButton15.addTrack(t30B2); + rButton15.addTrack(t10B3); + rButton15.addTrack(t10B4); + rButton15.addTrack(loop2A); + rButton15.addTrack(loop2B); + rButton15.addTrack(loop2C); + rButton15.addTrack(loop2D); + rButton15.addTrack(loop2E); + rButton15.addTrack(loop2F); + rButton15.addTrack(loop2G); + rButton15.addTrack(loop2H); + + rButton16 = new RouteButton(rX+300,rY+60,80,40,"In+Outer"); + rButton16.addTrackButton(tButton4,0); + rButton16.addTrackButton(tButton5,1); + rButton16.addTrackButton(tButton10,1); + rButton16.addTrackButton(tButton20,0); + rButton16.addTrackButton(tButton30,1); + rButton16.addTrackButton(tButton40,0); + rButton16.addTrackButton(tButton50,1); + rButton16.addTrack(t40A1); + rButton16.addTrack(loop1B); + rButton16.addTrack(loop1C); + rButton16.addTrack(loop1D); + rButton16.addTrack(loop1E); + rButton16.addTrack(t4A); + rButton16.addTrack(loop1F); + rButton16.addTrack(t50B1); + rButton16.addTrack(t50B2); + rButton16.addTrack(loop2A); + rButton16.addTrack(loop2B); + rButton16.addTrack(t30B1); + rButton16.addTrack(t30B2); + rButton16.addTrack(t30B3); + rButton16.addTrack(t30B4); + rButton16.addTrack(loop1A2A); + rButton16.addTrack(t5B); + rButton16.addTrack(loop3A); + rButton16.addTrack(loop3B); + rButton16.addTrack(loop3C); + rButton16.addTrack(t20A2); + rButton16.addTrack(loop2A3A); + rButton16.addTrack(loop2C); + rButton16.addTrack(t10B3); + rButton16.addTrack(t10B2); + rButton16.addTrack(loop3E); + rButton16.addTrack(loop3F); + rButton16.addTrack(loop3G); + rButton16.addTrack(loop3H); + + rButton17 = new RouteButton(rX,rY+120,80,40,"Double X"); + rButton17.addTrackButton(tButton5,0); + rButton17.addTrackButton(tButton20,0); + rButton17.addTrackButton(tButton30,1); + rButton17.addTrackButton(tButton40,0); + rButton17.addTrackButton(tButton50,0); + rButton17.addTrackButton(tButton4,1); + rButton17.addTrackButton(tButton7,0); + rButton17.addTrackButton(tButton1,0); + rButton17.addTrack(t4B); + rButton17.addTrack(rLoopA); + rButton17.addTrack(rLoopB); + rButton17.addTrack(t7A); + rButton17.addTrack(t1A); + rButton17.addTrack(loop1B); + rButton17.addTrack(loop1C); + rButton17.addTrack(loop1D); + rButton17.addTrack(loop1E); + rButton17.addTrack(loop1F); + rButton17.addTrack(loop1G); + rButton17.addTrack(loop1H); + rButton17.addTrack(loop1I); + rButton17.addTrack(t50A1); + rButton17.addTrack(t40A1); + rButton17.addTrack(loop1A2A); + rButton17.addTrack(t30B4); + rButton17.addTrack(t30B1); + rButton17.addTrack(loop3A); + rButton17.addTrack(loop3B); + rButton17.addTrack(loop3C); + rButton17.addTrack(t20A2); + rButton17.addTrack(loop2A3A); + rButton17.addTrack(t5A); + rButton17.addTrack(bridgeA); + rButton17.addTrack(bridgeB); + rButton17.addTrack(bridgeC); + rButton17.addTrack(bridgeD); + rButton17.addTrack(bridgeE); + rButton17.addTrack(bridgeF); + rButton17.addTrack(bridgeG); + rButton17.addTrack(bridgeH); + rButton17.addTrack(bridgeI); + + rButtonSpiral = new RouteButton(rX+100,rY+120,80,40,"Spiral"); + rButtonSpiral.addTrackButton(tButton4,1); + rButtonSpiral.addTrackButton(tButton5,0); + rButtonSpiral.addTrackButton(tButton7,0); + rButtonSpiral.addTrackButton(tButton1,0); + rButtonSpiral.addTrackButton(tButton10,0); + rButtonSpiral.addTrackButton(tButton20,0); + rButtonSpiral.addTrackButton(tButton30,0); + rButtonSpiral.addTrackButton(tButton40,0); + rButtonSpiral.addTrackButton(tButton50,0); + rButtonSpiral.addTrack(t4B); + rButtonSpiral.addTrack(rLoopA); + rButtonSpiral.addTrack(rLoopB); + rButtonSpiral.addTrack(t7A); + rButtonSpiral.addTrack(t1A); + rButtonSpiral.addTrack(loop1B); + rButtonSpiral.addTrack(loop1C); + rButtonSpiral.addTrack(loop1D); + rButtonSpiral.addTrack(loop1E); + rButtonSpiral.addTrack(loop1F); + rButtonSpiral.addTrack(loop1G); + rButtonSpiral.addTrack(loop1H); + rButtonSpiral.addTrack(loop1I); + rButtonSpiral.addTrack(t50A1); + rButtonSpiral.addTrack(t40A1); + rButtonSpiral.addTrack(loop1A2A); + rButtonSpiral.addTrack(t30A4); + rButtonSpiral.addTrack(t30A3); + rButtonSpiral.addTrack(t50A2); + rButtonSpiral.addTrack(loop2A); + rButtonSpiral.addTrack(loop2B); + rButtonSpiral.addTrack(loop2C); + rButtonSpiral.addTrack(loop2D); + rButtonSpiral.addTrack(loop2E); + rButtonSpiral.addTrack(loop2F); + rButtonSpiral.addTrack(loop2G); + rButtonSpiral.addTrack(loop2H); + rButtonSpiral.addTrack(t10A3); + rButtonSpiral.addTrack(t10A4); + rButtonSpiral.addTrack(t30A1); + rButtonSpiral.addTrack(t30A2); + rButtonSpiral.addTrack(loop2A3A); + rButtonSpiral.addTrack(t20A2); + rButtonSpiral.addTrack(loop3A); + rButtonSpiral.addTrack(loop3B); + rButtonSpiral.addTrack(loop3C); + rButtonSpiral.addTrack(t5A); + rButtonSpiral.addTrack(bridgeA); + rButtonSpiral.addTrack(bridgeB); + rButtonSpiral.addTrack(bridgeC); + rButtonSpiral.addTrack(bridgeD); + rButtonSpiral.addTrack(bridgeE); + rButtonSpiral.addTrack(bridgeF); + rButtonSpiral.addTrack(bridgeG); + rButtonSpiral.addTrack(bridgeH); + rButtonSpiral.addTrack(bridgeI); + + rButtonReset = new RouteButton(rX+400,rY+120,80,40,"RESET"); + rButtonReset.addTrackButton(tButton40,0); + rButtonReset.addTrackButton(tButton3,0); + rButtonReset.addTrackButton(tButton2,0); + rButtonReset.addTrackButton(tButton8,0); + rButtonReset.addTrackButton(tButton10,0); + rButtonReset.addTrackButton(tButton20,0); + rButtonReset.addTrackButton(tButton9,0); + rButtonReset.addTrackButton(tButton4,0); + rButtonReset.addTrackButton(tButton1,0); + rButtonReset.addTrackButton(tButton5,0); + rButtonReset.addTrackButton(tButton50,0); + rButtonReset.addTrackButton(tButton6,0); + rButtonReset.addTrackButton(tButton7,0); + rButtonReset.addTrackButton(tButton30,0); + rButtonReset.addTrack(t1A); + rButtonReset.addTrack(t2A); + rButtonReset.addTrack(t3A); + rButtonReset.addTrack(t4A); + rButtonReset.addTrack(t5A); + rButtonReset.addTrack(t6A); + rButtonReset.addTrack(t7A); + rButtonReset.addTrack(t8A); + rButtonReset.addTrack(t9A); + rButtonReset.addTrack(t10A1); + rButtonReset.addTrack(t10A2); + rButtonReset.addTrack(t10A3); + rButtonReset.addTrack(t10A4); + rButtonReset.addTrack(t20A1); + rButtonReset.addTrack(t20A2); + rButtonReset.addTrack(t30A1); + rButtonReset.addTrack(t30A2); + rButtonReset.addTrack(t30A3); + rButtonReset.addTrack(t30A4); + rButtonReset.addTrack(t40A1); + rButtonReset.addTrack(t40A2); + rButtonReset.addTrack(t50A1); + rButtonReset.addTrack(t50A2); + + rButtonBridge = new RouteButton(bridgeA,20,20); + rButtonBridge.addTrackButton(tButton5,0); + rButtonBridge.addTrackButton(tButton8,0); + rButtonBridge.addTrack(t5A); + rButtonBridge.addTrack(bridgeA); + rButtonBridge.addTrack(bridgeB); + rButtonBridge.addTrack(bridgeC); + rButtonBridge.addTrack(bridgeD); + rButtonBridge.addTrack(bridgeE); + rButtonBridge.addTrack(bridgeF); + rButtonBridge.addTrack(bridgeG); + rButtonBridge.addTrack(bridgeH); + rButtonBridge.addTrack(bridgeI); + rButtonBridge.addTrack(t8A); + + cab622.setSidingDefaults(rButton6,4,10); // must set default sidings AFTER rButtons are defined above + cab6021.setSidingDefaults(rButton1,11,7); + cab54.setSidingDefaults(rButton2,11,8); + cab1506.setSidingDefaults(rButton3,11,9); + cab8601.setSidingDefaults(rButton4,11,12); + cab1202.setSidingDefaults(rButton5,11,13); + cab2004.setSidingDefaults(rButton7,5,14); + + } // Initialize + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreComponents.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreComponents.pde new file mode 100644 index 0000000..dc81cfa --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreComponents.pde @@ -0,0 +1,491 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Core Components +// +// PowerButton - send power on/off command to the DCC++ Base Station +// +// CurrentMeter - monitors main track current draw from the DCC++ Base Station +// - displays scrolling bar chart of current measured +// +// HelpButton - toggles Help Window +// +// QuitButton - quits DCC++ Controller +// - connection to DCC++ Base Station terminated +// - NOTE: track power remains on and trains will continue to operate +// since DCC+ Base Station operates independently! +// +// AccessoryButton - sends a DCC ACCESSORY COMMAND to the DCC++ Base Station +// to either activate or de-activate an accessory depending on +// whether the button is labeled "ON" or "OFF" +// - two pre-specified input boxes are used: one for the user +// to input the desired accessory address, and one for +// accessory number (sub-address) +// - the default configuration of DCC++ Controller defines an +// Accessory Window that includes these two input boxes as well +// as ON and OFF buttons. +// +// CleaningCarButton - sends a DCC THROTTLE COMMAND to the DCC++ Base Station that operates +// a mobile decoder with a pre-specified cab number +// - this decoder drives a motor that spins a cleaning pad in a +// track-cleaning car +// - clicking the button toggles the throttle between either 0 or 126 (max speed) +// - the default configuration of DCC++ Controller defines an +// Extras Window that includes this button +// +// LEDColorButton - provide for interactive control of an LED-RGB Light Strip + +////////////////////////////////////////////////////////////////////////// +// DCC Component: PowerButton +////////////////////////////////////////////////////////////////////////// + +class PowerButton extends RectButton{ + + PowerButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText); + } + + PowerButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL); + } // PowerButton + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + aPort.write("<1>"); + } + +////////////////////////////////////////////////////////////////////////// + + void shiftPressed(){ + aPort.write(""); + exit(); + } + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + aPort.write("<0>"); + } + +} // PowerButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: CurrentMeter +////////////////////////////////////////////////////////////////////////// + +class CurrentMeter extends DccComponent{ + int nSamples, kHeight; + int maxCurrent; + int[] samples; + int sampleIndex; + int nGridLines; + boolean isOn; + + CurrentMeter(int xPos, int yPos, int nSamples, int kHeight, int maxCurrent, int nGridLines){ + this.xPos=xPos; + this.yPos=yPos; + this.nSamples=nSamples; + this.kHeight=kHeight; + this.maxCurrent=maxCurrent; + this.nGridLines=nGridLines; + this.isOn=true; + samples=new int[nSamples]; + sampleIndex=nSamples-1; + dccComponents.add(this); + } // CurrentMeter + +////////////////////////////////////////////////////////////////////////// + + void display(){ + int i; + rectMode(CORNER); + noFill(); + strokeWeight(1); + textFont(buttonFont,8); + textAlign(LEFT,CENTER); + stroke(200); + rect(xPos,yPos,nSamples+1,kHeight+2); + if(isOn) + stroke(50,200,100); + else + stroke(200,100,100); + for(i=0;i511) + msgBoxMain.setMessage("Error - Accessory Address must be in range 0-511",color(255,30,30)); + else if(accSubAddress>3) + msgBoxMain.setMessage("Error - Accessory Sub Address must be in range 0-3",color(255,30,30)); + else + aPort.write(""); + } + +} // AccessoryButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: Quit Button +////////////////////////////////////////////////////////////////////////// + +class QuitButton extends RectButton{ + + QuitButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText); + } + + QuitButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.NORMAL); + } // PowerButton + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + super.turnOn(); + exit(); + } + +} // QuitButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: Help Button +////////////////////////////////////////////////////////////////////////// + +class HelpButton extends EllipseButton{ + + HelpButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText); + } + + HelpButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + } // PowerButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + helpWindow.toggle(); + } + +} // HelpButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: CleaningCar Button +////////////////////////////////////////////////////////////////////////// + +class CleaningCarButton extends RectButton{ + int cab; + int reg; + + CleaningCarButton(int cab, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + this(null, cab, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText); + } + + CleaningCarButton(Window window, int cab, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL); + reg=cabButtons.size()+1; + this.cab=cab; + } // PowerButton + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + super.turnOn(); + aPort.write(""); + + } + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + super.turnOff(); + aPort.write(""); + } + +////////////////////////////////////////////////////////////////////////// + + void shiftPressed(){ + autoPilot.clean(); + } + +} // CleaningCarButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: LED Color Button +////////////////////////////////////////////////////////////////////////// + +class LEDColorButton extends DccComponent{ + + int bWidth, bHeight; + float hue; + float sat; + float val; + + LEDColorButton(Window window, int xPos, int yPos, int bWidth, int bHeight, float hue, float sat, float val){ + this.xPos=xPos; + this.yPos=yPos; + this.bWidth=bWidth; + this.bHeight=bHeight; + this.hue=hue; + this.sat=sat; + this.val=val; + this.window=window; + window.windowComponents.add(this); + } + +////////////////////////////////////////////////////////////////////////// + + void display(){ + rectMode(CENTER); + colorMode(HSB,1.0,1.0,1.0); + fill(hue,sat,val); + rect(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight); + colorMode(RGB,255); + } + +////////////////////////////////////////////////////////////////////////// + + void update(int s){ + color c; + colorMode(HSB,1.0,1.0,1.0); + c=color(hue,sat,val); + colorMode(RGB,255); + aPort.write(""); + ledHueMsg.setMessage("Hue: "+int(hue*360),color(200,200,200)); + ledSatMsg.setMessage("Sat: "+int(sat*100),color(200,200,200)); + ledValMsg.setMessage("Val: "+int(val*100),color(200,200,200)); + ledRedMsg.setMessage("Red: "+int(red(c)),color(200,200,200)); + ledGreenMsg.setMessage("Green: "+int(green(c)),color(200,200,200)); + ledBlueMsg.setMessage("Blue: "+int(blue(c)),color(200,200,200)); + } + +} // LEDColorButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: LED Value Selector +////////////////////////////////////////////////////////////////////////// + +class LEDValSelector extends DccComponent{ + + int bWidth, bHeight; + LEDColorButton cButton; + PImage valBox; + + LEDValSelector(Window window, int xPos, int yPos, int bWidth, int bHeight, LEDColorButton cButton){ + this.xPos=xPos; + this.yPos=yPos; + this.bWidth=bWidth; + this.bHeight=bHeight; + this.cButton=cButton; + valBox = createImage(bWidth+1,bHeight+1,RGB); + this.window=window; + window.windowComponents.add(this); + + colorMode(HSB,1.0,1.0,1.0); + valBox.loadPixels(); + + for(int y=0;y=xPos+xWindow()+cButton.val*float(bWidth)-5 && mouseX<=xPos+xWindow()+cButton.val*float(bWidth)+5 && mouseY>=yPos+yWindow()+bHeight+2 && mouseY<=yPos+yWindow()+bHeight+22){ + cursorType=HAND; + selectedComponent=this; + } + } + +////////////////////////////////////////////////////////////////////////// + + void drag(){ + cButton.val=constrain(float(mouseX-xPos-xWindow())/bWidth,0.0,1.0); + cButton.update(0); + } + +////////////////////////////////////////////////////////////////////////// + + void released(){ + cButton.update(1); + } + +} // LEDValSelector Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: LED Color Selector +////////////////////////////////////////////////////////////////////////// + +class LEDColorSelector extends DccComponent{ + + PImage colorWheel; + int radius; + LEDColorButton cButton; + + LEDColorSelector(Window window, int xPos, int yPos, int radius, LEDColorButton cButton){ + float d, h; + + this.xPos=xPos; + this.yPos=yPos; + this.radius=radius; + this.cButton=cButton; + colorWheel=createImage(radius*2+1,radius*2+1,RGB); + this.window=window; + window.windowComponents.add(this); + + colorWheel.loadPixels(); + colorMode(HSB,1.0,1.0,1.0); + + for(int i=0, y=radius;y>=-radius;y--){ + for(int x=-radius;x<=radius;x++){ + d=sqrt(x*x+y*y); + if(d<0.5){ + colorWheel.pixels[i]=color(0.0,0.0,1.0); // center of wheel always has zero saturation (hue does not matter) + } else + if(d>radius){ + colorWheel.pixels[i]=color(0.0,0.0,0.0); // outside of wheel is always fully black (hue and saturation does not matter) + } else { + h=acos(float(x)/d); // find angle in radians + if(y<0) // adjust angle to reflect lower half of wheel + h=TWO_PI-h; + colorWheel.pixels[i]=color(h/TWO_PI,d/float(radius),1.0); // hue is based on angle normalized to 1.0, saturation is based on distance to center normalized to 1.0, brightness is always 1.0 + } + i++; + } // x-loop + } // y-loop + + colorMode(RGB,255); + colorWheel.updatePixels(); + } + +////////////////////////////////////////////////////////////////////////// + + void display(){ + imageMode(CENTER); + colorMode(HSB,1.0,1.0,1.0); + image(colorWheel,xPos+xWindow(),yPos+yWindow()); + colorMode(RGB,255); + + } + +////////////////////////////////////////////////////////////////////////// + + void check(){ + + if(selectedComponent==null && ((pow(mouseX-xPos-xWindow(),2)+pow(mouseY-yPos-yWindow(),2))<=pow(radius,2))){ + cursorType=CROSS; + selectedComponent=this; + } + + } // check + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + drag(); + } + +////////////////////////////////////////////////////////////////////////// + + void drag(){ + float d,h; + color selectedColor; + + d=sqrt(pow(mouseX-xPos-xWindow(),2)+pow(mouseY-yPos-yWindow(),2)); + if(d<0.5){ + h=0.0; + } else { + h=acos(float(mouseX-xPos-xWindow())/d); + if(mouseY>(yPos+yWindow())) + h=TWO_PI-h; + cButton.hue=h/TWO_PI; + cButton.sat=constrain(d/float(radius),0.0,1.0); + } + + cButton.update(0); + + } + +////////////////////////////////////////////////////////////////////////// + + void released(){ + cButton.update(1); + } + +} // LEDColorSelector Class + +////////////////////////////////////////////////////////////////////////// + \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreConstants.java b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreConstants.java new file mode 100644 index 0000000..833697e --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/coreConstants.java @@ -0,0 +1,75 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Constants +////////////////////////////////////////////////////////////////////////// + +enum ButtonType{ + NORMAL, + ONESHOT, + HOLD, + REVERSE, + T_COMMAND, + TI_COMMAND, + Z_COMMAND +} + +enum InputType{ + BIN ("[01]"), + DEC ("[0-9]"), + HEX ("[A-Fa-f0-9]"); + + final String regexp; + InputType(String regexp){ + this.regexp=regexp; + } +} + +enum CabFunction{ + F_LIGHT, + R_LIGHT, + D_LIGHT, + BELL, + HORN, + S_HORN +} + +enum ThrottleSpeed{ + FULL, + SLOW, + STOP, + REVERSE, + REVERSE_SLOW; + + static ThrottleSpeed index(String findName){ + for(ThrottleSpeed p : ThrottleSpeed.values()){ + if(p.name().equals(findName)) + return(p); + } + return(null); + } +} + +enum AutoProgram{ + NONE ("NONE"), + ALL_CABS_RUN ("ALL CABS RUN"), + ALL_CABS_PARK ("ALL CABS PARK"), + SINGLE_CAB_PARK ("SINGLE CAB PARK"), + AUTO_CLEAN ("AUTO CLEAN"), + SINGLE_CAB_RUN ("SINGLE CAB RUN"); + + String name; + AutoProgram(String name){ + this.name=name; + } + static AutoProgram index(String findName){ + for(AutoProgram p : AutoProgram.values()){ + if(p.name.equals(findName)) + return(p); + } + return(null); + } + + boolean equals(AutoProgram p){ + return(this==p); + } + +} \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dCabs.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dCabs.pde new file mode 100644 index 0000000..243b823 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dCabs.pde @@ -0,0 +1,528 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Classes for Cab Throttle and Cab Function Controls +// +// Throttle - creates a sliding throttle to set the speed and direction +// of one or more locomotive cabs +// - cabs are selected by clicking any of the cab buttons +// that have been associated with the throttle +// - multiple throttles, each with a distinct set of cab buttons, +// is allowed. It is also possible to define one throttle per +// cab, in which case a visible cab button would not be needed +// since there is nothing to select +// - moving the slider up or down sends a DCC THROTTLE COMMAND to +// the DCC++ Base Station with the cab addres and register number +// specified by the selected can button +// - throttle commands assume mobile decoders are configured for 128-step speed control +// with speeds ranging from a minimum of 0 to a maximum of 126. +// - the throttle command sent to the DCC++ Base Station also codes whether motion +// is forward or backward +// - negative throttle numbers are NOT used to indicate reverse motion +// - a negative throttle number is used to instruct the DCC++ Base Station +// to initiate an immediate emergency stop of the specified cab. +// - this is in contrast to setting the throttle to 0, in which case the +// cab will stop according to any deceleration parameters (which may allow the locomotive +// to coast before stopping) +// - throttle slider can also be controlled with arrows as follows: +// +// * UP ARROW = increase speed by one unit in the forward direction +// (which decreases speed if already moving in the reverse direction) +// * DOWN ARROW = increase speed by one unit in the reverse direction +// (which decreases speed if already moving in the forward direction) +// * LEFT ARROW = set speed to zero (locomotive will coast to stop if configured with deceleration) +// * RIGHT ARROW = emergency stop (locomotive will stop immediately, ignoring any deceleration parameters) +// +// - Note: throttle slider and arrow buttons will not permit single action that causes locomotive +// to stop and then reverse. This allows users to move slider or press arrow keys to slow +// locomotive to zero without worrying about overshooting and reversing direction. Once slider is +// at zero, reclick to start sliding in reverse direction. +// +// CabButton - defines a button to activate a given cab address for a given throttle +// - in addition to the cab address (which can be short or long), the button +// contains: +// +// * informaiton on which register number the DCC++ Base Station +// should use for throttle commands to this cab +// * a data structure indicating which cab functions (lights, horns, etc.) +// are defined for this cab +// +// FunctionButton - sends a CAB FUNCTION COMMMAND to the DCC++ Base Station to +// activate or de-activate any cab function F0-F12 +// - function buttons are always associated with a particular throttle, but +// are dynamically configured according to the cab selected +// to be active for that throttle +// - configuration information for each function button is stored in +// a data structure contained within each cab button +// - configuration data includes the name of each button and whether the function +// should: +// +// * be toggled from on to off, or off to on, with each mouse click (e.g. a headlight) +// * be activated upon pressing the mouse button but de-active when the mouse +// button is released (e.g. a horn) +// * be turned on and then immediately off with a single mouse click (e.g. coupler sounds) + +////////////////////////////////////////////////////////////////////////// +// DCC Component: Throttle +////////////////////////////////////////////////////////////////////////// + +class Throttle extends DccComponent{ + final int KMAXPOS=126; + final int KMINPOS=-126; + int kWidth=50; + int kHeight=15; + int sPos,sSpeed; + int kMaxTemp, kMinTemp; + float tScale; + CabButton cabButton=null; + + Throttle(int xPos, int yPos, float tScale){ + this.xPos=xPos; + this.yPos=yPos; + this.tScale=tScale; + dccComponents.add(this); + } // Throttle + +////////////////////////////////////////////////////////////////////////// + + void display(){ + int i; + + rectMode(CENTER); + ellipseMode(CENTER); + strokeWeight(1); + noStroke(); + fill(255); + rect(xPos,yPos,kWidth/2.0,(KMAXPOS-KMINPOS)*tScale); + fill(0); + rect(xPos,yPos,kWidth/4.0,(KMAXPOS-KMINPOS)*tScale); + + stroke(0); + for(i=0;iKMINPOS*tScale;i-=10*tScale) + line(xPos-kWidth/4.0,yPos-i,xPos+kWidth/4.0,yPos-i); + + if(cabButton==null) + return; + + noStroke(); + for(i=kWidth;i>0;i--){ + fill(230-(i*2),230-(i*2),255-(i*3)); + ellipse(xPos,yPos-cabButton.speed*tScale,i,i/2); + } + + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + + if(cabButton==null) + return; + + if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-(yPos-cabButton.speed*tScale))*(mouseY-(yPos-cabButton.speed*tScale))/(kWidth*kWidth/16.0)<=1){ + cursorType=HAND; + selectedComponent=this; + } + } // check + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + sPos=mouseY; + sSpeed=cabButton.speed; + + if(sSpeed>0){ + kMaxTemp=KMAXPOS; + kMinTemp=0; + } + else if(sSpeed<0){ + kMaxTemp=0; + kMinTemp=KMINPOS; + } + else{ + kMaxTemp=KMAXPOS; + kMinTemp=KMINPOS; + } + + noCursor(); + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void drag(){ + int tPos; + + tPos=constrain(int((sPos-mouseY)/tScale)+sSpeed,kMinTemp,kMaxTemp); + + if(tPos>0) + kMinTemp=0; + else if(tPos<0) + kMaxTemp=0; + + cabButton.setThrottle(tPos); + } // drag + +////////////////////////////////////////////////////////////////////////// + + void keyControl(int m){ + int tPos; + + if(m==0){ // emergency stop + tPos=0; + cabButton.throttleSpeed=ThrottleSpeed.STOP; + } else { + tPos=constrain(sSpeed+=m,kMinTemp,kMaxTemp); + } + + if(tPos>0) + kMinTemp=0; + else if(tPos<0) + kMaxTemp=0; + + cabButton.setThrottle(tPos); + + } // keyControl + +} // Throttle Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: CabButton +////////////////////////////////////////////////////////////////////////// + +class CabButton extends RectButton{ + int reg, cab; + int speed=0; + String name; + RouteButton sidingRoute; + int sidingSensor=0; + int parkingSensor=0; + XML speedXML, cabDefaultXML; + XML throttleDefaultsXML; + ThrottleSpeed throttleSpeed=ThrottleSpeed.STOP; + Window fbWindow; + ArrayList windowList = new ArrayList(); + int[] fStatus = new int[29]; + HashMap functionsHM = new HashMap(); + Throttle throttle; + PImage cabImage; + String cabFile; + Window editWindow; + InputBox cabNumInput; + + CabButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, int cab, Throttle throttle){ + super(null, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, str(cab), ButtonType.NORMAL); + this.cab=cab; + this.throttle=throttle; + cabButtons.add(this); + reg=cabButtons.size(); + cabFile=("cab-"+cab+".jpg"); + cabImage=loadImage(cabFile); + name="Cab"+cab; + cabsHM.put(name,this); + colorMode(HSB,255); + editWindow = new Window(xPos-(bWidth/2),yPos-(bHeight/2),bWidth,bHeight,color(baseHue,255,255),color(baseHue,255,125)); + cabNumInput = new InputBox(this); + + speedXML=autoPilotXML.getChild(name); + if(speedXML==null){ + speedXML=autoPilotXML.addChild(name); + speedXML.setContent(ThrottleSpeed.STOP.name()); + } + + cabDefaultXML=cabDefaultsXML.getChild(name); + if(cabDefaultXML==null){ + cabDefaultXML=cabDefaultsXML.addChild(name); + } + + } // CabButton + +////////////////////////////////////////////////////////////////////////// + + void display(){ + super.display(); + + imageMode(CENTER); + fill(30); + rect(xPos+bWidth/2+30,yPos,42,20); + stroke(backgroundColor); + line(xPos+bWidth/2+23,yPos-10,xPos+bWidth/2+23,yPos+10); + line(xPos+bWidth/2+37,yPos-10,xPos+bWidth/2+37,yPos+10); + textFont(throttleFont,22); + if(speed>0) + fill(0,255,0); + else if(speed<0) + fill(255,0,0); + else + fill(255,255,0); + text(nf(abs(speed),3),xPos+bWidth/2+30,yPos); + + } // display + +////////////////////////////////////////////////////////////////////////// + + void functionButtonWindow(int xPos, int yPos, int kWidth, int kHeight, color backgroundColor, color outlineColor){ + if(windowList.size()==1) // there is already one defined window and another is requested -- add a NextFunctionsButton to the original window + new NextFunctionsButton(fbWindow, this, kWidth/2, kHeight+5, 40, 15, 60, 8, "More..."); + + fbWindow=new Window(xPos,yPos,kWidth,kHeight,backgroundColor,outlineColor); + windowList.add(fbWindow); + + if(windowList.size()>1) // there are at least two defined windows -- add a NextFunctionsButton to this window + new NextFunctionsButton(fbWindow, this, kWidth/2, kHeight+5, 40, 15, 60, 8, "More..."); + } + +////////////////////////////////////////////////////////////////////////// + + void setFunction(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, int fNum, String name, ButtonType buttonType, CabFunction ... cFunc){ + new FunctionButton(fbWindow,xPos,yPos,bWidth,bHeight,baseHue,fontSize,this,fNum,name,buttonType,cFunc); + } + +////////////////////////////////////////////////////////////////////////// + + void activateFunction(CabFunction cFunc, boolean s){ + if(functionsHM.containsKey(cFunc)) + functionsHM.get(cFunc).activateFunction(s); + } + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + if(throttle.cabButton!=null){ + throttle.cabButton.fbWindow.close(); + throttle.cabButton.turnOff(); + } + + super.turnOn(); + fbWindow.show(); + throttle.cabButton=this; + opCabInput.setIntValue(cab); + } + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + super.turnOff(); + fbWindow.close(); + throttle.cabButton=null; + } + +////////////////////////////////////////////////////////////////////////// + + void shiftPressed(){ + autoPilot.parkCab(this); + } + +////////////////////////////////////////////////////////////////////////// + + void rightClick(){ + editWindow.open(); + } + +////////////////////////////////////////////////////////////////////////// + + void setThrottle(int tPos){ + aPort.write("0)+">"); + + if(throttleSpeed!=ThrottleSpeed.STOP) + throttleDefaultsXML.setInt(throttleSpeed.name(),tPos); + + } + +////////////////////////////////////////////////////////////////////////// + + void setThrottle(ThrottleSpeed throttleSpeed){ + this.throttleSpeed=throttleSpeed; + setThrottle(throttleDefaultsXML.getInt(throttleSpeed.name())); + speedXML.setContent(throttleSpeed.name()); + activateFunction(CabFunction.F_LIGHT,true); + activateFunction(CabFunction.R_LIGHT,true); + activateFunction(CabFunction.D_LIGHT,true); + } + +////////////////////////////////////////////////////////////////////////// + + void setThrottleDefaults(int fullSpeed, int slowSpeed, int reverseSpeed, int reverseSlowSpeed){ + + throttleDefaultsXML=cabDefaultXML.getChild("throttleDefaults"); + + if(throttleDefaultsXML==null){ + throttleDefaultsXML=cabDefaultXML.addChild("throttleDefaults"); + throttleDefaultsXML.setInt(ThrottleSpeed.FULL.name(),fullSpeed); + throttleDefaultsXML.setInt(ThrottleSpeed.SLOW.name(),slowSpeed); + throttleDefaultsXML.setInt(ThrottleSpeed.REVERSE.name(),reverseSpeed); + throttleDefaultsXML.setInt(ThrottleSpeed.REVERSE_SLOW.name(),reverseSlowSpeed); + throttleDefaultsXML.setInt(ThrottleSpeed.STOP.name(),0); + } + + } + +////////////////////////////////////////////////////////////////////////// + + void setSidingDefaults(RouteButton sidingRoute, int parkingSensor, int sidingSensor){ + this.sidingRoute=sidingRoute; + this.parkingSensor=parkingSensor; + this.sidingSensor=sidingSensor; + } + +////////////////////////////////////////////////////////////////////////// + + void stopThrottle(){ + aPort.write(""); + throttleSpeed=ThrottleSpeed.STOP; + } + +////////////////////////////////////////////////////////////////////////// + + String toString(){ + return(name); + } + +} // CabButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: FunctionButton +////////////////////////////////////////////////////////////////////////// + +class FunctionButton extends RectButton{ + int fNum; + CabButton cabButton; + String name; + int oneShotCount; + int fPolarity; + + FunctionButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, CabButton cabButton, int fNum, String name, ButtonType buttonType, CabFunction[] cFunc){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, name, buttonType); + this.fNum=abs(fNum)%29; // ensures fNum is always between 0 and 28, inclusive + this.cabButton=cabButton; + this.name=name; + for(int i=0;i0){ + oneShotCount--; + isOn=true; + } + else + isOn=(cabButton.fStatus[fNum]!=fPolarity); + + super.display(); + + } // display + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + activateFunction(true); + } + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + activateFunction(false); + } + +////////////////////////////////////////////////////////////////////////// + + void released(){ + if(buttonType==ButtonType.HOLD) + turnOff(); + } + +////////////////////////////////////////////////////////////////////////// + + void activateFunction(boolean s){ + int f=0; + int e=0; + + if(s){ + cabButton.fStatus[fNum]=1-fPolarity; + if(buttonType==ButtonType.ONESHOT){ + fPolarity=1-fPolarity; + oneShotCount=1; + } + } else{ + cabButton.fStatus[fNum]=fPolarity; + } + + if(fNum<5){ // functions F0-F4 are single byte instructions of form 1-0-0-F0-F4-F3-F2-F1 + f=(1<<7) + +(cabButton.fStatus[0]<<4) + +(cabButton.fStatus[4]<<3) + +(cabButton.fStatus[3]<<2) + +(cabButton.fStatus[2]<<1) + +cabButton.fStatus[1]; + } else if(fNum<9){ // functions F5-F8 are single byte instructions of form 1-0-1-1-F8-F7-F6-F5 + f=(1<<7) + +(1<<5) + +(1<<4) + +(cabButton.fStatus[8]<<3) + +(cabButton.fStatus[7]<<2) + +(cabButton.fStatus[6]<<1) + +cabButton.fStatus[5]; + } else if(fNum<13){ // functions F9-F12 are single byte instructions of form 1-0-1-0-F12-F11-F10-F9 + f=(1<<7) + +(1<<5) + +(cabButton.fStatus[12]<<3) + +(cabButton.fStatus[11]<<2) + +(cabButton.fStatus[10]<<1) + +cabButton.fStatus[9]; + } else if(fNum<21){ // functions F13-F20 are two-byte instructions of form 0xDE followed by F20-F19-F18-F17-F16-F15-F14-F13 + f=222; // 0xDE + e=(cabButton.fStatus[20]<<7) + +(cabButton.fStatus[19]<<6) + +(cabButton.fStatus[18]<<5) + +(cabButton.fStatus[17]<<4) + +(cabButton.fStatus[16]<<3) + +(cabButton.fStatus[15]<<2) + +(cabButton.fStatus[14]<<1) + +cabButton.fStatus[13]; + } else if(fNum<29){ // functions F21-F28 are two-byte instructions of form 0xDF followed by F28-F27-F26-F25-F24-F23-F22-F21 + f=223; // 0xDF + e=(cabButton.fStatus[28]<<7) + +(cabButton.fStatus[27]<<6) + +(cabButton.fStatus[26]<<5) + +(cabButton.fStatus[25]<<4) + +(cabButton.fStatus[24]<<3) + +(cabButton.fStatus[23]<<2) + +(cabButton.fStatus[22]<<1) + +cabButton.fStatus[21]; + } + + if(fNum<13) + aPort.write(""); + else + aPort.write(""); + + } // activateFunction + +} // FunctionButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: NextFunctionsButton +////////////////////////////////////////////////////////////////////////// + +class NextFunctionsButton extends RectButton{ + CabButton cButton; + + NextFunctionsButton(Window window, CabButton cButton, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.ONESHOT); + this.cButton=cButton; + } // PowerButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + cButton.fbWindow.close(); + cButton.fbWindow=throttleA.cabButton.windowList.get((throttleA.cabButton.windowList.indexOf(throttleA.cabButton.fbWindow)+1)%throttleA.cabButton.windowList.size()); + cButton.fbWindow.open(); + } + +} // NextFunctionsButton Class \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dRoutes.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dRoutes.pde new file mode 100644 index 0000000..20e5a3e --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dRoutes.pde @@ -0,0 +1,139 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Class for Route Button +// +// RouteButton - creates a button to activate one or more Track Buttons +// that in turn set one or more TURNOUTS or CROSSOVERS to either an +// open or closed position representing a specific track route +// - tracks may also be added to a route button so that they are highlighted +// on the screen when the route button is first selected +// - track highlights will be color-coded to indicate whether each +// turnout or crossover that in in the route is already set properly, +// or needs to be toggled if that route is activiated +// +// - two types of route buttons are supported: +// +// * large stand-alone button with a text label indicated the name of the route +// * small button placed on a track where the route is obvious and does +// not require a name (such as at the end of a siding) +// +////////////////////////////////////////////////////////////////////////// + +class RouteButton extends DccComponent{ + int xPos, yPos; + int kWidth, kHeight; + String label=""; + boolean routeOn=false; + ArrayList aTrackButtons = new ArrayList(); + ArrayList bTrackButtons = new ArrayList(); + ArrayList rTracks = new ArrayList(); + + RouteButton(Track refTrack, int kWidth, int kHeight){ + this.xPos=int((refTrack.x[0]+refTrack.x[1])/2.0*refTrack.layout.sFactor+refTrack.layout.xCorner); + this.yPos=int((refTrack.y[0]+refTrack.y[1])/2.0*refTrack.layout.sFactor+refTrack.layout.yCorner); + this.kWidth=kWidth; + this.kHeight=kHeight; + dccComponents.add(this); + } + + RouteButton(int xPos, int yPos, int kWidth, int kHeight, String label){ + this.xPos=xPos; + this.yPos=yPos; + this.kWidth=kWidth; + this.kHeight=kHeight; + this.label=label; + dccComponents.add(this); + + } // RouteButton + +////////////////////////////////////////////////////////////////////////// + + void addTrackButton(TrackButton trackButton, int tPos){ + if(tPos==0){ // specifies that this track button should be set to A when route selected + aTrackButtons.add(trackButton); + trackButton.aRouteButtons.add(this); + } else if (tPos==1) { // specifies that this track button should be set to B when route selected + bTrackButtons.add(trackButton); + trackButton.bRouteButtons.add(this); + } + } + +////////////////////////////////////////////////////////////////////////// + + void addTrack(Track track){ + rTracks.add(track); + } + +////////////////////////////////////////////////////////////////////////// + + void display(){ + if(label.equals("")){ + ellipseMode(CENTER); + if(routeOn) + fill(color(0,255,0)); + else + fill(color(0,150,0)); + noStroke(); + ellipse(xPos,yPos,kWidth/2,kHeight/2); + } else{ + ellipseMode(CENTER); + if(routeOn) + fill(color(0,200,200)); + else + fill(color(0,100,100)); + noStroke(); + ellipse(xPos,yPos,kWidth,kHeight); + textFont(buttonFont,12); + textAlign(CENTER,CENTER); + fill(color(0)); + text(label,xPos,yPos); + } + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){ + cursorType=HAND; + selectedComponent=this; + for(Track track : rTracks){ + track.hStatus=1; + } + } + + else if(previousComponent==this){ + for(Track track : rTracks){ + track.hStatus=0; + } + } + + } // check + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + for(TrackButton trackButton : aTrackButtons){ + if(trackButton.rEnabled) + trackButton.pressed(0); + } + for(TrackButton trackButton : bTrackButtons){ + if(trackButton.rEnabled) + trackButton.pressed(1); + } + routeOn=true; + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void shiftPressed(){ + for(TrackButton trackButton : aTrackButtons){ + if(trackButton.rEnabled) + trackButton.pressed(1); + } + for(TrackButton trackButton : bTrackButtons){ + if(trackButton.rEnabled) + trackButton.pressed(0); + } + routeOn=false; + } // shiftPressed + +} // RouteButton Class \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dSensors.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dSensors.pde new file mode 100644 index 0000000..62edd81 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dSensors.pde @@ -0,0 +1,815 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Classes for Sensors and AutoPilot Control +// +// AutoPilot Button - automaticaly operates three cabs in a pattern +// by which each cab travels out to the sky bridge, +// reverses course, and comes back into the inner +// reversing loop after passing through the crossover +// - status is saved between session +// - clicking this button RESUMES a previous session +// or stops the current session +// - resumption implies all cabs, turnouts, and sensors are in +// the exact same position as before the session was halted +// - shift-clicking this button STARTS a new session +// - starting a new seesion assume all cabs are in their start position +// but sensors and turnouts will be automatically reset +// +// TrackSensor - defines a track sensor that triggers when the first car of a train passes, and +// then again when the last car of that same train passes. +// - creates a track sensor button on the track layout where ther sensor is located +// - a given track sensor is defined to be "on" once an initial trigger is received from passage +// of first the car of a train, and defined to be "off" once a second trigger is received from +// passage of last car of that same train +// - if the on/off status of a track sensor button seems out of sync with the actual train, +// user can manually toggle the sensor "on" or "off" by clicking the appropriate sensor button +// +////////////////////////////////////////////////////////////////////////// + +class AutoPilotButton extends RectButton{ + int[] cabs={8601,6021,1506,622,1202,54}; // list of all cabs to be included in autoPilot - order does not matter since it will be randomized + ArrayList cabList = new ArrayList(); + int phase=0; + int tCount=0; + int crossOver=0; + AutoProgram program=AutoProgram.NONE; + XML cabListXML, phaseXML, tCountXML, crossOverXML, programXML; + int safetyTimer; + + AutoPilotButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText); + } + + AutoPilotButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(0), fontSize, bText, ButtonType.NORMAL); + + phaseXML=autoPilotXML.getChild("Phase"); + if(phaseXML==null){ + phaseXML=autoPilotXML.addChild("Phase"); + phaseXML.setContent(str(phase)); + } else{ + phase=int(phaseXML.getContent()); + } + + tCountXML=autoPilotXML.getChild("TCount"); + if(tCountXML==null){ + tCountXML=autoPilotXML.addChild("TCount"); + tCountXML.setContent(str(tCount)); + } else{ + tCount=int(tCountXML.getContent()); + } + + crossOverXML=autoPilotXML.getChild("CrossOver"); + if(crossOverXML==null){ + crossOverXML=autoPilotXML.addChild("CrossOver"); + crossOverXML.setContent(str(crossOver)); + } else{ + crossOver=int(crossOverXML.getContent()); + } + + programXML=autoPilotXML.getChild("Program"); + if(programXML==null){ + programXML=autoPilotXML.addChild("Program"); + programXML.setContent(program.name); + } else{ + program=AutoProgram.index(programXML.getContent()); + } + + cabListXML=autoPilotXML.getChild("CabList"); + if(cabListXML==null){ + cabListXML=autoPilotXML.addChild("CabList"); + cabListXML.setContent(join(nf(cabs,0)," ")); + } + + for(int i: int(split(trim(cabListXML.getContent())," "))) + cabList.add(cabsHM.get("Cab"+i)); + + updateDiagBox(); + + } // AutoButton + +////////////////////////////////////////////////////////////////////////// + + void display(){ + super.display(); + + textAlign(CENTER,CENTER); + textFont(messageFont,12); + fill(color(255)); + text(program.name,xPos+xWindow(),yPos+yWindow()+bHeight/2+10); + + } + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + + if(isOn){ + turnOff(); + return; + } + + if(program==AutoProgram.NONE){ + msgBoxMain.setMessage("Can't resume Auto Pilot until program specified!",color(50,50,200)); + return; + } + + for(CabButton cb : cabList) // set throttles of all cabs specified in current program to prior values + cb.setThrottle(ThrottleSpeed.index(cb.speedXML.getContent())); + + if(program.equals(AutoProgram.AUTO_CLEAN)) + cleaningCab.turnOn(); + + msgBoxMain.setMessage("Auto Pilot Resuming",color(50,50,200)); + safetyTimer=millis(); + turnOn(); + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void init(){ + phase=0; + tCount=0; + crossOver=0; + + for(TrackSensor ts : sensorsHM.values()) + ts.reset(); + + cabList.clear(); + for(int i:cabs) + cabList.add(cabsHM.get("Cab"+i)); + + for(int i=0;i<3;i++) // randomize list + cabList.add(0,cabList.remove(int(random(i,cabList.size())))); + + updateCabList(); + + for(CabButton cb : cabList) // halt all cabs specified in full autopilot program + cb.setThrottle(ThrottleSpeed.STOP); + + rButtonReset.pressed(); + cabList.get(0).sidingRoute.pressed(); // set siding turnouts so they are aligned for first cab + rButtonSpiral.pressed(); + rButton12.pressed(); + + cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start first cab! + + msgBoxMain.setMessage("Auto Pilot Engaged",color(50,50,200)); + updateDiagBox(); + + } // init() + + +////////////////////////////////////////////////////////////////////////// + + void clean(){ + + if(isOn){ + msgBoxMain.setMessage("Auto Pilot already engaged!",color(50,50,200)); + return; + } + + cleaningCab.turnOn(); // turn on cleaning car + cabList.clear(); + cabList.add(cabsHM.get("Cab"+2004)); // assumes cab 2004 is pulling cleaning car + updateCabList(); + phase=100; + phaseXML.setContent(str(phase)); + tButton10.pressed(0); + + for(TrackSensor ts : sensorsHM.values()) + ts.reset(); + + cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start throttle for cab + msgBoxMain.setMessage("Auto Clean Engaged",color(50,50,200)); + setProgram(AutoProgram.AUTO_CLEAN); + safetyTimer=millis(); + turnOn(); + + } // clean + +////////////////////////////////////////////////////////////////////////// + + void parkCab(CabButton selectedCab){ + + if(selectedCab.parkingSensor==0){ + msgBoxMain.setMessage("Auto Park not available for Cab "+selectedCab.cab,color(50,50,200)); + return; + } + + if(isOn){ + msgBoxMain.setMessage("Auto Pilot already engaged!",color(50,50,200)); + return; + } + + cabList.clear(); + cabList.add(selectedCab); + updateCabList(); + phase=42; + phaseXML.setContent(str(phase)); + cabList.get(0).setThrottle(ThrottleSpeed.FULL); // start throttle for cab selected --- do not modify throttles for any other cabs + msgBoxMain.setMessage("Auto Park Engaged for Cab "+selectedCab.cab,color(50,50,200)); + setProgram(AutoProgram.SINGLE_CAB_PARK); + safetyTimer=millis(); + turnOn(); + + } // parkCab + +////////////////////////////////////////////////////////////////////////// + + void shiftPressed(){ + + if(!isOn){ + setProgram(AutoProgram.ALL_CABS_RUN); + safetyTimer=millis(); + turnOn(); + msgBoxMain.setMessage("Starting Auto Pilot...",color(50,50,200)); + buttonQueue.add(this); + } else + if(program==AutoProgram.ALL_CABS_RUN){ + msgBoxMain.setMessage("Switching to Auto Park",color(50,50,200)); + setProgram(AutoProgram.ALL_CABS_PARK); + } else{ + msgBoxMain.setMessage("Auto Park or other program already engaged!",color(50,50,200)); + } + } // shiftPressed + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + super.turnOff(); + + msgBoxMain.setMessage("Auto Pilot Disengaged",color(50,50,200)); + + for(CabButton cb : cabList) // halt (but without updating XML) all cabs specified in current program only + cb.stopThrottle(); + + if(program.equals(AutoProgram.AUTO_CLEAN)) + cleaningCab.turnOff(); + + if(program.equals(AutoProgram.SINGLE_CAB_RUN)){ + aPort.write(""); + setProgram(AutoProgram.NONE); + } + } + +////////////////////////////////////////////////////////////////////////// + + void updateCabList(){ + + cabListXML.setContent(""); + for(CabButton cb : cabList) + cabListXML.setContent(cabListXML.getContent()+cb.cab+" "); + + cabListXML.setContent(trim(cabListXML.getContent())); + } + +////////////////////////////////////////////////////////////////////////// + + void process(int s, boolean isActive){ + + int lastPhase; + + if(!isOn || program.equals(AutoProgram.SINGLE_CAB_RUN)) + return; + + if(!isActive) + s=-s; + + lastPhase=phase; + + switch(s){ + + case 1: + if(phase==3){ + rButtonBridge.pressed(); + phase=4; + } else + if(phase==4){ + phase=5; + } else + if(phase==5){ + phase=6; + } else + if(phase==10){ + crossOver++; + if(crossOver==2){ + cabList.get(0).stopThrottle(); +// cabList.get(0).activateFunction(CabFunction.HORN,true); +// cabList.get(0).activateFunction(CabFunction.HORN,false); + } else{ + cabList.get(0).activateFunction(CabFunction.S_HORN,true); + } + } else + if(phase==11){ + phase=12; + } else + if(phase==13){ + phase=14; + } else + if(phase==40){ + tButton20.pressed(0); + phase=41; + } + break; + + case -1: + if(phase==2){ + tCount++; + } else + if(phase==120){ + phase=42; + tButton20.pressed(1); + tButton10.pressed(0); + } + break; + + case 2: + if(phase==0){ + tButton40.routeDisabled(); + cabList.get(1).sidingRoute.pressed(); + tButton40.routeEnabled(); + cabList.get(1).setThrottle(ThrottleSpeed.FULL); + phase=1; +// } else +// if(phase==10 || phase==11){ +// cabList.get(2).setFunction(CabFunction.HORN,false); + } else + if(phase==30){ + tButton50.pressed(1); + phase=31; + } + break; + + case -2: + if(phase==2){ + tCount++; + if(tCount==1) + cabList.get(1).setThrottle(ThrottleSpeed.STOP); + } else + if(phase==9){ + tButton30.pressed(1); + tCount++; + } else + if(phase==10){ + if(crossOver>0){ + crossOver--; + } + if(crossOver==1){ + cabList.get(0).setThrottle(ThrottleSpeed.FULL); +// cabList.get(0).activateFunction(CabFunction.S_HORN,true); + } + } + break; + + case 3: + if(phase==7){ + tButton30.pressed(0); + phase=8; + } else + if(phase==10){ + crossOver++; + if(crossOver==2){ + cabList.get(2).stopThrottle(); +// cabList.get(2).activateFunction(CabFunction.HORN,true); +// cabList.get(2).activateFunction(CabFunction.HORN,false); + } else{ + cabList.get(2).activateFunction(CabFunction.S_HORN,true); + } + } + break; + + case -3: + if(phase==110){ + phase++; + tButton30.pressed(1); + } else + if(phase==111||phase==112){ + phase++; + } else + if(phase==113){ + phase++; + tButton30.pressed(0); + tButton40.pressed(1); + tButton1.pressed(0); + } + break; + + case 4: + if(phase==1){ + tButton40.routeDisabled(); + cabList.get(2).sidingRoute.pressed(); + tButton40.routeEnabled(); + cabList.get(2).setThrottle(ThrottleSpeed.FULL); + phase=2; + } else + if(phase==8){ + tButton40.pressed(0); + phase=9; +// } else +// if(phase==10){ +// cabList.get(0).setFunction(CabFunction.HORN,false); + } else + if(phase==12){ + tButton4.pressed(1); // set reversing loop + tButton7.pressed(0); + phase=20; // start "parking" phase, then resume pattern + } else + if((phase==21 || phase==31 || phase==42) && cabList.get(0).parkingSensor==4){ + cabList.get(0).setThrottle(ThrottleSpeed.SLOW); + phase++; + } else + if(phase==41){ + tButton50.pressed(0); + tButton4.pressed(1); // set reversing loop + tButton7.pressed(0); + phase=42; + } + break; + + case -4: + if(phase==10){ + if(crossOver>0){ + crossOver--; + } + if(crossOver==1){ + cabList.get(2).setThrottle(ThrottleSpeed.FULL); +// cabList.get(2).activateFunction(CabFunction.S_HORN,true); + } + cabList.get(1).setThrottle(ThrottleSpeed.FULL); // just in case cab-1 was stopped on bridge + tButton40.pressed(1); + tButton1.pressed(0); + tButton20.pressed(1); + crossOver=0; + phase=11; + } else + if((phase==22 || phase==32 || phase==43) && cabList.get(0).parkingSensor==4){ + phase++; + cabList.get(0).setThrottle(ThrottleSpeed.STOP); + cabList.get(0).sidingRoute.shiftPressed(); + delay(500); + cabList.get(0).sidingRoute.pressed(); + sensorsHM.get(cabList.get(0).sidingSensor).pressed(false); + cabList.get(0).setThrottle(ThrottleSpeed.REVERSE); + } + break; + + case 5: + if(phase==6){ + cabList.get(0).setThrottle(ThrottleSpeed.SLOW); + } else + if(phase==14){ + cabList.get(1).setThrottle(ThrottleSpeed.SLOW); + } else + if(phase==42 && cabList.get(0).parkingSensor==5){ + cabList.get(0).setThrottle(ThrottleSpeed.SLOW); + phase++; + } + break; + + case -5: + if(phase==6){ + cabList.get(0).setThrottle(ThrottleSpeed.STOP); + phase=7; + } else + if(phase==14){ + cabList.get(1).setThrottle(ThrottleSpeed.STOP); + cabList.add(cabList.remove(0)); // move cab-0 to end of list + updateCabList(); + phase=7; // start next cycle + } else + if(phase==43 && cabList.get(0).parkingSensor==5){ + phase++; + cabList.get(0).setThrottle(ThrottleSpeed.STOP); + cabList.get(0).sidingRoute.shiftPressed(); + delay(500); + cabList.get(0).sidingRoute.pressed(); + sensorsHM.get(cabList.get(0).sidingSensor).pressed(false); + delay(100); + cabList.get(0).setThrottle(ThrottleSpeed.REVERSE); + } else + if(phase==100){ + phase++; + tButton10.pressed(1); + tButton30.pressed(1); + tButton50.pressed(1); + tButton4.pressed(0); + tButton20.pressed(0); + } else + if(phase==101||phase==102){ + phase++; + } else + if(phase==103){ + phase++; + tButton20.pressed(1); + tButton50.pressed(0); + } else + if(phase==104||phase==105){ + phase++; + } else + if(phase==106){ + phase++; + tButton10.pressed(0); + } else + if(phase==107||phase==108){ + phase++; + } else + if(phase==109){ + phase++; + tButton20.pressed(0); + tButton30.pressed(0); + } + break; + + case 6: + if(phase==10){ + cabList.get(1).stopThrottle(); // wait on bridge until cab-0 clears sensor 4 + } + break; + + case -6: + if(phase==9){ + tCount++; + tButton8.pressed(); + } + break; + + case 7: + case 8: + case 9: + case 10: + case 12: + case 13: + case 14: + if(phase==23 || phase==33 || phase==44){ + phase++; + cabList.get(0).setThrottle(ThrottleSpeed.REVERSE_SLOW); + } + break; + + case -7: + case -8: + case -9: + case -10: + case -12: + case -13: + case -14: + if(phase==24 || phase==34 || phase==45){ + cabList.get(0).setThrottle(ThrottleSpeed.STOP); + sensorsHM.get(cabList.get(0).parkingSensor).pressed(false); + tButton40.pressed(1); + if(program==AutoProgram.SINGLE_CAB_PARK||program==AutoProgram.AUTO_CLEAN){ + phase=51; // phase must have previously been 45 + turnOff(); + } else + if(program==AutoProgram.ALL_CABS_PARK){ + cabList.add(0,cabList.remove(2)); // move cab-2 to beginning of list, making it cab-0 + updateCabList(); + phase+=6; // start parking routine at either phase=30, or if second cab just parked then phase=40, or if third cab finished parking phase=51 + if(phase==51){ + turnOff(); + } + } else{ + cabList.add(3,cabList.remove(int(random(3,cabList.size())))); // pick random cab to be next to leave siding + updateCabList(); + tButton40.routeDisabled(); + cabList.get(3).sidingRoute.pressed(); + tButton40.routeEnabled(); + cabList.get(3).setThrottle(ThrottleSpeed.FULL); + phase=25; + } + } + break; + + case 11: + if(phase==20){ + phase=21; + } else + if((phase==21 || phase==31 || phase==42) && cabList.get(0).parkingSensor==11){ + cabList.get(0).setThrottle(ThrottleSpeed.SLOW); + phase++; + } else + if(phase==25){ + phase=26; + } else + if(phase==26){ + phase=13; + } + break; + + case -11: + if((phase==22 || phase==32 || phase==43) && cabList.get(0).parkingSensor==11){ + phase++; + cabList.get(0).setThrottle(ThrottleSpeed.STOP); + cabList.get(0).sidingRoute.shiftPressed(); + delay(500); + cabList.get(0).sidingRoute.pressed(); + sensorsHM.get(cabList.get(0).sidingSensor).pressed(false); + delay(100); + cabList.get(0).setThrottle(ThrottleSpeed.REVERSE); + } else + if(phase==114||phase==115){ + phase++; + } else + if(phase==116){ + phase++; + tButton4.pressed(1); + tButton7.pressed(0); + tButton5.pressed(0); + tButton20.pressed(0); + tButton8.pressed(0); + } else + if(phase==117){ + phase++; + tButton40.pressed(0); + } else + if(phase==118||phase==119){ + phase++; + } + break; + + } // switch t + + if(phase==2 && tCount==2){ + cabList.get(1).setThrottle(ThrottleSpeed.FULL); // just in case cab-1 was previously stopped to wait for cab-0 to catch up + rButton10.pressed(); + rButton11.pressed(); + tCount=0; + phase=3; + } else + + if(phase==9 && tCount==2){ + cabList.get(0).setThrottle(ThrottleSpeed.FULL); + tButton20.pressed(0); + tButton4.pressed(0); + tCount=0; + crossOver=0; + phase=10; + } + + phaseXML.setContent(str(phase)); + tCountXML.setContent(str(tCount)); + crossOverXML.setContent(str(crossOver)); + + updateDiagBox(); + + if(phase!=lastPhase) // there was an update of the phase + safetyTimer=millis(); // reset timer + + + } // process + +////////////////////////////////////////////////////////////////////////// + + void setProgram(AutoProgram p){ + program=p; + programXML.setContent(program.name); + updateDiagBox(); + saveXMLFlag=true; + } + +////////////////////////////////////////////////////////////////////////// + void updateDiagBox(){ + + String s=""; + + for(XML xml: autoPilotXML.getChildren()){ + if(!xml.getName().equals("#text")) + s=s+(String.format("%10s",xml.getName())+" = "+xml.getContent()+"\n"); + } + + msgAutoState.setMessage(s); + + } // updateDiagBox + +////////////////////////////////////////////////////////////////////////// + + void safetyCheck(){ + + int countDown; + + if(!isOn || program.equals(AutoProgram.SINGLE_CAB_RUN)) + return; + + countDown=120-int((millis()-safetyTimer)/1000); + + msgAutoTimer.setMessage("Timer = "+countDown); + + if(countDown<=0){ + powerButton.turnOff(); + turnOff(); + } + + } // safetyCheck + +} // AutoPilot Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: TrackSensor +////////////////////////////////////////////////////////////////////////// + +class TrackSensor extends Track{ + boolean isActive=false; + boolean sensorDefault; + int xPos, yPos; + int mTime; + int kWidth, kHeight; + String sensorName; + int sensorNum; + XML sensorButtonXML; + MessageBox msgBoxSensor; + + TrackSensor(Track refTrack, int trackPoint, float tLength, int kWidth, int kHeight, int sensorNum, boolean sensorDefault){ + super(refTrack,trackPoint,tLength); + this.kWidth=kWidth; + this.kHeight=kHeight; + this.xPos=int(x[1]*layout.sFactor+layout.xCorner); + this.yPos=int(y[1]*layout.sFactor+layout.yCorner); + this.sensorNum=sensorNum; + sensorName="Sensor"+sensorNum; + componentName=sensorName; + this.sensorDefault=sensorDefault; + sensorButtonXML=sensorButtonsXML.getChild(sensorName); + if(sensorButtonXML==null){ + sensorButtonXML=sensorButtonsXML.addChild(sensorName); + sensorButtonXML.setContent(str(isActive)); + } else{ + isActive=boolean(sensorButtonXML.getContent()); + } + sensorsHM.put(sensorNum,this); + msgBoxSensor=new MessageBox(sensorWindow,0,sensorNum*22+22,-1,0,color(175),18,"S-"+nf(sensorNum,2)+":",color(50,50,250)); + } + + TrackSensor(Track refTrack, int trackPoint, float curveRadius, float curveAngleDeg, int kWidth, int kHeight, int sensorNum, boolean sensorDefault){ + super(refTrack,trackPoint,curveRadius,curveAngleDeg); + this.kWidth=kWidth; + this.kHeight=kHeight; + this.xPos=int(x[1]*layout.sFactor+layout.xCorner); + this.yPos=int(y[1]*layout.sFactor+layout.yCorner); + this.sensorNum=sensorNum; + this.sensorDefault=sensorDefault; + sensorName="Sensor"+sensorNum; + componentName=sensorName; + sensorButtonXML=sensorButtonsXML.getChild(sensorName); + if(sensorButtonXML==null){ + sensorButtonXML=sensorButtonsXML.addChild(sensorName); + sensorButtonXML.setContent(str(isActive)); + } else{ + isActive=boolean(sensorButtonXML.getContent()); + } + sensorsHM.put(sensorNum,this); + msgBoxSensor=new MessageBox(sensorWindow,0,sensorNum*22+22,-1,0,color(175),18,"S-"+nf(sensorNum,2)+":",color(50,50,250)); + } + +////////////////////////////////////////////////////////////////////////// + + void display(){ + ellipseMode(CENTER); + + strokeWeight(1); + stroke(color(255,255,0)); + noFill(); + + if(isActive) + fill(color(50,50,200)); + + ellipse(xPos,yPos,kWidth/2,kHeight/2); + } // display() + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + pressed(!isActive); + } + +////////////////////////////////////////////////////////////////////////// + + void pressed(boolean isActive){ + this.isActive=isActive; + autoPilot.process(sensorNum,isActive); + sensorButtonXML.setContent(str(isActive)); + saveXMLFlag=true; + if(isActive){ + msgBoxSensor.setMessage("S-"+nf(sensorNum,2)+": "+nf(hour(),2)+":"+nf(minute(),2)+":"+nf(second(),2)+" - "+nf((millis()-mTime)/1000.0,0,1)+" sec"); + mTime=millis(); + } + + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void reset(){ + pressed(sensorDefault); + + } // reset + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){ + cursorType=HAND; + selectedComponent=this; + } + + } // check + +} // TrackSensor Class \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTracks.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTracks.pde new file mode 100644 index 0000000..cc6600f --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTracks.pde @@ -0,0 +1,264 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Classes for Layouts and Tracks +// +// Layout - defines a scaled region on the screen into which tracks +// will be place using scaled coordinates +// +// Track - defines a curved or straight piece of track. +// - placement on layout can be in absolute scaled coordinates +// or linked to one end of a previously-defined track. +// - tracks can be linked even across separate layouts +// - define multiple overlapping tracks to create any type +// of turnout, crossover, or other complex track +////////////////////////////////////////////////////////////////////////// + +class Layout{ + int xCorner, yCorner; + float sFactor; + + Layout(int xCorner, int yCorner, int frameWidth, float layoutWidth, float layoutHeight){ + this.xCorner=xCorner; + this.yCorner=yCorner; + sFactor=float(frameWidth)/layoutWidth; // frameWidth in pixels, layoutWidth in mm, inches, cm, etc. + } // Layout + + Layout(Layout layout){ + this.xCorner=layout.xCorner; + this.yCorner=layout.yCorner; + this.sFactor=layout.sFactor; + } // Layout + + void copy(Layout layout){ + this.xCorner=layout.xCorner; + this.yCorner=layout.yCorner; + this.sFactor=layout.sFactor; + } // copy + + boolean equals(Layout layout){ + return((this.xCorner==layout.xCorner)&&(this.yCorner==layout.yCorner)&&(this.sFactor==layout.sFactor)); + } // equals + +} // Layout Class + +////////////////////////////////////////////////////////////////////////// + +class Track extends DccComponent{ + float[] x = new float[2]; + float[] y = new float[2]; + float[] a = new float[2]; + color tColor; + float xR, yR; + float r; + float aStart, aEnd; + int tStatus=1; // specfies current track status (0=off/not visible, 1=on/visible) + int hStatus=0; // specifies if current track is highlighted (1) or normal (0) + Layout layout; + + Track(Layout layout, float x, float y, float tLength, float angleDeg){ + this.x[0]=x; + this.y[0]=y; + this.a[1]=angleDeg/360.0*TWO_PI; + this.a[0]=this.a[1]+PI; + if(this.a[0]>=TWO_PI) + this.a[0]-=TWO_PI; + this.x[1]=this.x[0]+cos(this.a[1])*tLength; + this.y[1]=this.y[0]-sin(this.a[1])*tLength; + this.layout=layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - straight, absolute + +////////////////////////////////////////////////////////////////////////// + + Track(Track track, int trackPoint, float tLength, Layout layout){ + this.x[0]=track.x[trackPoint%2]; + this.y[0]=track.y[trackPoint%2]; + this.a[1]=track.a[trackPoint%2]; + this.a[0]=this.a[1]+PI; + if(this.a[0]>=TWO_PI) + this.a[0]-=TWO_PI; + this.x[1]=this.x[0]+cos(this.a[1])*tLength; + this.y[1]=this.y[0]-sin(this.a[1])*tLength; + this.layout=layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - straight, relative, Layout specified + +////////////////////////////////////////////////////////////////////////// + + Track(Track track, int trackPoint, float tLength){ + this.x[0]=track.x[trackPoint%2]; + this.y[0]=track.y[trackPoint%2]; + this.a[1]=track.a[trackPoint%2]; + this.a[0]=this.a[1]+PI; + if(this.a[0]>=TWO_PI) + this.a[0]-=TWO_PI; + this.x[1]=this.x[0]+cos(this.a[1])*tLength; + this.y[1]=this.y[0]-sin(this.a[1])*tLength; + this.layout=track.layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - straight, relative, no Layout specified + +////////////////////////////////////////////////////////////////////////// + + Track(Layout layout, float x, float y, float curveRadius, float curveAngleDeg, float angleDeg){ + float thetaR, thetaA; + int d; + + thetaR=curveAngleDeg/360.0*TWO_PI; + thetaA=angleDeg/360.0*TWO_PI; + d=(thetaR>0)?1:-1; + + this.x[0]=x; + this.y[0]=y; + + this.a[0]=thetaA+PI; + if(this.a[0]>=TWO_PI) + + this.a[0]-=TWO_PI; + this.a[1]=thetaA+thetaR; + if(this.a[1]>=TWO_PI) + this.a[1]-=TWO_PI; + if(this.a[1]<0) + this.a[1]+=TWO_PI; + + this.r=curveRadius; + + this.xR=this.x[0]-d*this.r*sin(thetaA); + this.yR=this.y[0]-d*this.r*cos(thetaA); + + this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR); + this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR); + + if(d==1){ + this.aEnd=PI/2-thetaA; + this.aStart=this.aEnd-thetaR; + }else{ + this.aStart=1.5*PI-thetaA; + this.aEnd=this.aStart-thetaR; + } + + this.layout=layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - curved, absolute + +////////////////////////////////////////////////////////////////////////// + + Track(Track track, int trackPoint, float curveRadius, float curveAngleDeg, Layout layout){ + float thetaR, thetaA; + int d; + + thetaR=curveAngleDeg/360.0*TWO_PI; + thetaA=track.a[trackPoint%2]; + d=(thetaR>0)?1:-1; + + this.x[0]=track.x[trackPoint%2]; + this.y[0]=track.y[trackPoint%2]; + + this.a[0]=thetaA+PI; + if(this.a[0]>=TWO_PI) + + this.a[0]-=TWO_PI; + this.a[1]=thetaA+thetaR; + if(this.a[1]>=TWO_PI) + this.a[1]-=TWO_PI; + if(this.a[1]<0) + this.a[1]+=TWO_PI; + + this.r=curveRadius; + + this.xR=this.x[0]-d*this.r*sin(thetaA); + this.yR=this.y[0]-d*this.r*cos(thetaA); + + this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR); + this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR); + + if(d==1){ + this.aEnd=PI/2-thetaA; + this.aStart=this.aEnd-thetaR; + }else{ + this.aStart=1.5*PI-thetaA; + this.aEnd=this.aStart-thetaR; + } + + this.layout=layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - curved, relative, Layout specified + +////////////////////////////////////////////////////////////////////////// + + Track(Track track, int trackPoint, float curveRadius, float curveAngleDeg){ + float thetaR, thetaA; + int d; + + thetaR=curveAngleDeg/360.0*TWO_PI; + thetaA=track.a[trackPoint%2]; + d=(thetaR>0)?1:-1; + + this.x[0]=track.x[trackPoint%2]; + this.y[0]=track.y[trackPoint%2]; + + this.a[0]=thetaA+PI; + if(this.a[0]>=TWO_PI) + + this.a[0]-=TWO_PI; + this.a[1]=thetaA+thetaR; + if(this.a[1]>=TWO_PI) + this.a[1]-=TWO_PI; + if(this.a[1]<0) + this.a[1]+=TWO_PI; + + this.r=curveRadius; + + this.xR=this.x[0]-d*this.r*sin(thetaA); + this.yR=this.y[0]-d*this.r*cos(thetaA); + + this.x[1]=this.xR+d*this.r*sin(thetaA+thetaR); + this.y[1]=this.yR+d*this.r*cos(thetaA+thetaR); + + if(d==1){ + this.aEnd=PI/2-thetaA; + this.aStart=this.aEnd-thetaR; + }else{ + this.aStart=1.5*PI-thetaA; + this.aEnd=this.aStart-thetaR; + } + + this.layout=track.layout; + this.tColor=color(255,255,0); + dccComponents.add(this); + } // Track - curved, relative, no Layout specified + +////////////////////////////////////////////////////////////////////////// + + void display(){ + + if(tStatus==1){ // track is visible + if(hStatus==1) // track is highlighted + stroke(color(0,255,0)); + else + stroke(color(255,255,0)); + } else{ // track is not visible + if(hStatus==1) // track is highlighted + stroke(color(255,0,0)); + else + stroke(color(80,80,0)); + } + + strokeWeight(3); + ellipseMode(RADIUS); + noFill(); + if(r==0){ + line(x[0]*layout.sFactor+layout.xCorner,y[0]*layout.sFactor+layout.yCorner,x[1]*layout.sFactor+layout.xCorner,y[1]*layout.sFactor+layout.yCorner); + } + else{ + arc(xR*layout.sFactor+layout.xCorner,yR*layout.sFactor+layout.yCorner,r*layout.sFactor,r*layout.sFactor,aStart,aEnd); + } + } // display() + +} // Track Class + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTurnouts.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTurnouts.pde new file mode 100644 index 0000000..085de8d --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dTurnouts.pde @@ -0,0 +1,153 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Class for Track Button +// +// TrackButton - creates a TURNOUT or CROSSOVER by grouping two sets of +// of pre-specified tracks +// - one set of tracks defines the state of the turnout +// or crossover in the "open" position +// - the other set of tracks defines the state of the turnout +// or crossover in the "closed" position +// - a clickable but otherwise invisible button (the Track Button) +// located near the center of the turnout or crossover +// toggles between the closed and open positions +// +// +// - when toggled, TrackButton will: +// +// * reset the colors of each set of tracks to +// indicate whether the turnour or crossover +// is "open" or "closed" +// +// * reset the color of any route buttons that use this +// track button +// +// * send a DCC ACCESSORY COMMAND to the DCC++ Base Station +// using the Accessory Address and Accessory Number +// specified for this Track Button +// +// In accordance with NMRA DCC Standards, accessory decoders +// are controlled using 12 bits messages. The first 11 form +// a main address (9 bits) and a sub address (2 bits). Depending +// on the specifics of a particular manufacturers decoder, these +// 11 bits can be interpreted as a single address (0-2047) or +// as a main address (0-511) with 4 sub addresses (0-3). Some decoders +// may respond to any address matching the first 9 bits; others may +// also consider the two sub address bits. In any case, Track Button +// can be used to send the correct combination of 11 bits to sucessfully +// communicate with the decoder. +// +// The 12th bit is generally considered to be the data bit that is used +// to toggle the accessory either on or off. In the case of a decoder +// driving a turnout or crossover, this data bit is used to toggle between +// the open and closed positions. +// +////////////////////////////////////////////////////////////////////////// + +class TrackButton extends DccComponent{ + int xPos, yPos; + int kWidth, kHeight; + int buttonStatus=0; + int id; + boolean rEnabled=true; + ArrayList aTracks = new ArrayList(); + ArrayList bTracks = new ArrayList(); + ArrayList aRouteButtons = new ArrayList(); + ArrayList bRouteButtons = new ArrayList(); + + TrackButton(int kWidth, int kHeight, int id){ + this.kWidth=kWidth; + this.kHeight=kHeight; + this.id=id; + this.componentName="T"+id; + trackButtonsHM.put(id,this); + dccComponents.add(this); + } // FunctionButton + +////////////////////////////////////////////////////////////////////////// + + void addTrack(Track track, int tPos){ + int n=aTracks.size()+bTracks.size(); + this.xPos=int((this.xPos*n+(track.x[0]+track.x[1])/2.0*track.layout.sFactor+track.layout.xCorner)/(n+1.0)); + this.yPos=int((this.yPos*n+(track.y[0]+track.y[1])/2.0*track.layout.sFactor+track.layout.yCorner)/(n+1.0)); + + if(tPos==0){ // specifies that this track should be considered part of aTracks + track.tStatus=1-buttonStatus; + aTracks.add(track); + } else if (tPos==1) { // specifies that this track should be considered part of bTracks + track.tStatus=buttonStatus; + bTracks.add(track); + } + + } + +////////////////////////////////////////////////////////////////////////// + + void display(){ + if(buttonStatus==0){ + for(Track track : bTracks) + track.display(); + for(Track track : aTracks) + track.display(); + } else { + for(Track track : aTracks) + track.display(); + for(Track track : bTracks) + track.display(); + } + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX-xPos)*(mouseX-xPos)/(kWidth*kWidth/4.0)+(mouseY-yPos)*(mouseY-yPos)/(kHeight*kHeight/4.0)<=1){ + cursorType=HAND; + selectedComponent=this; + } + } // check + +////////////////////////////////////////////////////////////////////////// + + void routeEnabled(){ + rEnabled=true; + } +////////////////////////////////////////////////////////////////////////// + + void routeDisabled(){ + rEnabled=false; + } + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + pressed(1-buttonStatus); + } + +////////////////////////////////////////////////////////////////////////// + + void pressed(int buttonStatus){ + aPort.write(""); + delay(50); + } + +////////////////////////////////////////////////////////////////////////// + + void update(int buttonStatus){ + + this.buttonStatus=buttonStatus; + + for(Track track : aTracks) + track.tStatus=1-buttonStatus; + for(Track track : bTracks) + track.tStatus=buttonStatus; + + if(buttonStatus==0){ + for(RouteButton routeButton : bRouteButtons) + routeButton.routeOn=false; + } else { + for(RouteButton routeButton : aRouteButtons) + routeButton.routeOn=false; + } + + } // update + +} // TrackButton Class \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/ARCENA-15.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/ARCENA-15.vlw new file mode 100644 index 0000000..e09a876 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/ARCENA-15.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/IrisUPC-36.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/IrisUPC-36.vlw new file mode 100644 index 0000000..341c10a Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/IrisUPC-36.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/JasmineUPC-36.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/JasmineUPC-36.vlw new file mode 100644 index 0000000..30bbc22 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/JasmineUPC-36.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/LucidaConsole-18.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/LucidaConsole-18.vlw new file mode 100644 index 0000000..8471dd3 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/LucidaConsole-18.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/Miriam-36.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/Miriam-36.vlw new file mode 100644 index 0000000..6816bdb Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/Miriam-36.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/MiriamFixed-26.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/MiriamFixed-26.vlw new file mode 100644 index 0000000..c6102b8 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/MiriamFixed-26.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/OCRAExtended-26.vlw b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/OCRAExtended-26.vlw new file mode 100644 index 0000000..a9be097 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/OCRAExtended-26.vlw differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1202.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1202.jpg new file mode 100644 index 0000000..17ee9cb Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1202.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1506.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1506.jpg new file mode 100644 index 0000000..8f6dbdd Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-1506.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-2004.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-2004.jpg new file mode 100644 index 0000000..889c99e Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-2004.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-54.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-54.jpg new file mode 100644 index 0000000..8ef7635 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-54.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-6021.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-6021.jpg new file mode 100644 index 0000000..0c93922 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-6021.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-622.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-622.jpg new file mode 100644 index 0000000..d2e672f Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-622.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-8601.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-8601.jpg new file mode 100644 index 0000000..d2729d6 Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/cab-8601.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/helpMenu.jpg b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/helpMenu.jpg new file mode 100644 index 0000000..f6f5f6f Binary files /dev/null and b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/data/helpMenu.jpg differ diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dccStatus.xml b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dccStatus.xml new file mode 100644 index 0000000..46e1237 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/dccStatus.xml @@ -0,0 +1,63 @@ + + + Emulator + + false + false + true + false + false + false + true + true + false + false + true + false + true + false + + + FULL + FULL + STOP + STOP + STOP + FULL + STOP + 42 + 0 + 0 + SINGLE CAB PARK + 622 + STOP + + + + + + + + + + + + + + + + + + + + + + + + + + + + /dev/tty.usbmodem1431 + 192.168.1.169 + diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/eventHandlers.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/eventHandlers.pde new file mode 100644 index 0000000..91c94d8 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/eventHandlers.pde @@ -0,0 +1,336 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Event Handlers +// +// Top-level processing of mouse, keyboard, and serial events. +// Most of the real functionality is contained in other methods, +// functions, and classes called by these handlers +// +////////////////////////////////////////////////////////////////////////// + +void mouseDragged(){ + if(selectedComponent!=null) + selectedComponent.drag(); +} + +////////////////////////////////////////////////////////////////////////// + +void mousePressed(){ + + if(activeInputBox!=null){ + for(InputBox inputBox : activeInputBox.linkedBoxes) + inputBox.setIntValue(activeInputBox.getIntValue()); + } + + activeInputBox=null; + if(selectedComponent!=null){ + if (keyPressed == true && key == CODED){ + if(keyCode == SHIFT){ + selectedComponent.shiftPressed(); + } else if(keyCode == CONTROL){ + msgBoxMain.setMessage("Component Name: "+selectedComponent.componentName,color(30,30,150)); + } + } + else if(mouseButton==LEFT){ + selectedComponent.pressed(); + } else { + selectedComponent.rightClick(); + } + } + +} + +////////////////////////////////////////////////////////////////////////// + +void mouseReleased(){ + if(selectedComponent!=null) + selectedComponent.released(); +} + +////////////////////////////////////////////////////////////////////////// + +void keyPressed(){ + keyCommand(key, keyCode); +} + +////////////////////////////////////////////////////////////////////////// + +void keyReleased(){ + keyCommandReleased(key, keyCode); +} + +////////////////////////////////////////////////////////////////////////// + +void serialEvent(Serial p){ + receivedString(p.readString()); +} + +////////////////////////////////////////////////////////////////////////// + +void clientEvent(Client c){ + String s; + s=c.readStringUntil('>'); + if(s!=null) + receivedString(s); +} + +////////////////////////////////////////////////////////////////////////// + + void receivedString(String s){ + if(s.charAt(0)!='<') + return; + + String c=s.substring(2,s.length()-1); + + switch(s.charAt(1)){ + + case 'i': + baseID=c; + msgBoxMain.setMessage("Found "+baseID,color(0,150,0)); + break; + + case '*': + msgBoxDiagIn.setMessage(c,color(30,30,150)); + break; + + case 'r': + String[] cs=splitTokens(c,"|"); + callBacks.get(int(cs[0])).execute(int(cs[1]),cs[2]); + break; + + case 'T': + int[] n=int(splitTokens(c)); + if(n[0]>cabButtons.size()) + break; + CabButton t=cabButtons.get(n[0]-1); + if(n[2]==1) + t.speed=n[1]; + else + t.speed=-n[1]; + break; + + case 'Q': + if(sensorsHM.get(int(c))!=null){ + sensorsHM.get(int(c)).pressed(); + } + break; + + case 'Y': + int[] h1=int(splitTokens(c)); + if(remoteButtonsHM.get(h1[0])!=null){ + if(h1[1]==1) + remoteButtonsHM.get(h1[0]).turnOn(); + else + remoteButtonsHM.get(h1[0]).turnOff(); + } + break; + + case 'H': + int[] h=int(splitTokens(c)); + + if(trackButtonsHM.get(h[0])!=null){ + trackButtonsHM.get(h[0]).update(h[1]); + } else if(remoteButtonsHM.get(h[0])!=null){ + if(h[1]==((remoteButtonsHM.get(h[0]).buttonType==ButtonType.T_COMMAND)?1:0)) + remoteButtonsHM.get(h[0]).turnOn(); + else + remoteButtonsHM.get(h[0]).turnOff(); + } + + break; + + case 'L': + int[] z=int(splitTokens(c)); + color tempColor; + tempColor=color(z[0],z[1],z[2]); + colorMode(HSB,1.0,1.0,1.0); + ledColorButton.hue=hue(tempColor); + ledColorButton.sat=saturation(tempColor); + ledColorButton.val=brightness(tempColor); + ledColorButton.update(0); + colorMode(RGB,255); + break; + + case 'U': + autoPilot.cabList.clear(); + autoPilot.setProgram(AutoProgram.SINGLE_CAB_RUN); + autoPilot.turnOn(); + break; + + case 'p': + if(c.equals("1")){ + powerButton.isOn=true; + msgBoxMain.setMessage("Track Power On",color(30,30,150)); + } else if(c.equals("0")){ + powerButton.isOn=false; + msgBoxMain.setMessage("Track Power Off",color(30,30,150)); + } else if(c.equals("2")){ + msgBoxMain.setMessage("MAIN Track Current Overload - Power Off",color(200,30,30)); + powerButton.isOn=false; + } else if(c.equals("3")){ + msgBoxMain.setMessage("PROG Track Current Overload - Power Off",color(200,30,30)); + powerButton.isOn=false; + } + break; + + case 'a': + currentMeter.addSample(int(c)); + break; + + } + } // receivedString + +////////////////////////////////////////////////////////////////////////// + + void keyCommand(char k, int kC){ + + if(activeInputBox!=null){ + activeInputBox.keyStroke(k, kC); + return; + } + + if(k==CODED){ + switch(kC){ + case UP: + if(throttleA.cabButton!=null){ + if(!keyHold) + throttleA.pressed(); + throttleA.keyControl(1); + } + break; + case DOWN: + if(throttleA.cabButton!=null){ + if(!keyHold) + throttleA.pressed(); + throttleA.keyControl(-1); + } + break; + case LEFT: + if(throttleA.cabButton!=null){ + throttleA.keyControl(0); + } + break; + case RIGHT: + if(throttleA.cabButton!=null){ + throttleA.cabButton.stopThrottle(); + } + break; + } + } // key is coded + + else{ + switch(k){ + case 'P': + powerButton.turnOn(); + break; + + case 'F': + aPort.write("<3>"); + break; + + case 'f': + aPort.write("<2>"); + break; + + case ' ': + powerButton.turnOff(); + break; + + case 'a': + accWindow.toggle(); + break; + + case 'c': + currentMeter.isOn=!currentMeter.isOn; + break; + + case 'e': + extrasWindow.toggle(); + break; + + case 'x': + autoWindow.toggle(); + break; + + case 'S': + sensorWindow.toggle(); + break; + + case 'l': + ledWindow.toggle(); + break; + + case 's': + portWindow.toggle(); + break; + + case 'h': + helpWindow.toggle(); + break; + + case 'q': + imageWindow.toggle(); + break; + + case 'd': + diagWindow.toggle(); + break; + + case 'i': + if(layoutBridge.equals(layout2)) + layoutBridge.copy(layout); + else + layoutBridge.copy(layout2); + break; + + case 'p': + progWindow.toggle(); + break; + + case 'o': + opWindow.toggle(); + break; + + case 'n': + if(throttleA.cabButton!=null){ + throttleA.cabButton.fbWindow.close(); + throttleA.cabButton.fbWindow=throttleA.cabButton.windowList.get((throttleA.cabButton.windowList.indexOf(throttleA.cabButton.fbWindow)+1)%throttleA.cabButton.windowList.size()); + throttleA.cabButton.fbWindow.open(); + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + cabButtons.get(int(k)-int('1')).pressed(); + break; + + } + } // key not coded + + keyHold=true; + } // keyCommand + +////////////////////////////////////////////////////////////////////////// + + void keyCommandReleased(char k, int kC){ + + keyHold=false; + + if(k==CODED){ + switch(kC){ + } + } // key is coded + + else{ + switch(k){ + } + } // key not coded + + } // keyCommandReleased + + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gButtons.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gButtons.pde new file mode 100644 index 0000000..311135f --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gButtons.pde @@ -0,0 +1,168 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Generic Ellipse and Rectangle Buttons +// +// EllipseButton - base class for creating simple buttons +// - operating buttons that extend EllipseButton should +// over-ride these methods with functionality specific +// to that button +// +// RectButton - variant of EllipseButton that define a rectanglular button +// +////////////////////////////////////////////////////////////////////////// + +class EllipseButton extends DccComponent{ + int bWidth, bHeight; + int baseHue; + color textColor; + int fontSize; + String bText; + ButtonType buttonType; + int remoteCode; + boolean isOn=false; + + EllipseButton(){ + this(width/2,height/2,80,50,100,color(0),16,"Button",ButtonType.NORMAL); + } + + EllipseButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType); + } + + EllipseButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){ + this.xPos=xPos; + this.yPos=yPos; + this.bWidth=bWidth; + this.bHeight=bHeight; + this.bText=bText; + this.fontSize=fontSize; + this.baseHue=baseHue; + this.textColor=textColor; + this.window=window; + this.buttonType=buttonType; + if(window==null) + dccComponents.add(this); + else + window.windowComponents.add(this); + } // EllipseButton + +////////////////////////////////////////////////////////////////////////// + + void display(){ + colorMode(HSB,255); + ellipseMode(CENTER); + noStroke(); + fill(color(baseHue,255,isOn?255:125)); + ellipse(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight); + fill(textColor); + textFont(buttonFont,fontSize); + textAlign(CENTER,CENTER); + text(bText,xPos+xWindow(),yPos+yWindow()); + if(buttonType==ButtonType.ONESHOT && isOn) + turnOff(); + colorMode(RGB,255); + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX-xPos-xWindow())*(mouseX-xPos-xWindow())/(bWidth*bWidth/4.0)+(mouseY-yPos-yWindow())*(mouseY-yPos-yWindow())/(bHeight*bHeight/4.0)<=1){ + cursorType=HAND; + selectedComponent=this; + } + } // check + +////////////////////////////////////////////////////////////////////////// + + void turnOn(){ + isOn=true; + } + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + isOn=false; + } + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + + if(buttonType==ButtonType.T_COMMAND){ + aPort.write("":"1>")); + return; + } + + if(buttonType==ButtonType.TI_COMMAND){ + aPort.write("":"0>")); + return; + } + + if(buttonType==ButtonType.Z_COMMAND){ + aPort.write("":"1>")); + return; + } + + if(isOn) + turnOff(); + else + turnOn(); + } + +////////////////////////////////////////////////////////////////////////// + + void released(){ + if(buttonType==ButtonType.HOLD) + turnOff(); + } + +} // EllipseButton Class + +////////////////////////////////////////////////////////////////////////// + +class RectButton extends EllipseButton{ + + RectButton(){ + super(width/2,height/2,80,50,100,color(0),16,"Button",ButtonType.NORMAL); + } + + RectButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){ + super(null, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType); + } + + RectButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType); + } + + RectButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, color textColor, int fontSize, String bText, ButtonType buttonType, int remoteCode){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, textColor, fontSize, bText, buttonType); + this.remoteCode=remoteCode; + remoteButtonsHM.put(remoteCode,this); + } // RectangleButton + +////////////////////////////////////////////////////////////////////////// + + void display(){ + colorMode(HSB,255); + rectMode(CENTER); + noStroke(); + fill(color(baseHue,255,isOn?255:125)); + rect(xPos+xWindow(),yPos+yWindow(),bWidth,bHeight); + fill(textColor); + textFont(buttonFont,fontSize); + textAlign(CENTER,CENTER); + text(bText,xPos+xWindow(),yPos+yWindow()); + if(buttonType==ButtonType.ONESHOT && isOn) + turnOff(); + colorMode(RGB,255); + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX>xPos+xWindow()-bWidth/2)&&(mouseXyPos+yWindow()-bHeight/2)&&(mouseY linkedBoxes = new ArrayList(); + + InputBox(CabButton cb){ + this(cb.editWindow,4,cb.bHeight/2,cb.fontSize,color(255,0,255),color(0,0,0),4,InputType.DEC); + this.cb=cb; + setIntValue(cb.cab); + } + + InputBox(int xPos, int yPos, int fontSize, color boxColor, color msgColor, int maxChars, InputType inputType){ + this(null, xPos, yPos, fontSize, boxColor, msgColor, maxChars, inputType); + } + + InputBox(Window window, int xPos, int yPos, int fontSize, color boxColor, color msgColor, int maxChars, InputType inputType){ + this.xPos=xPos; + this.yPos=yPos; + this.fontSize=fontSize; + this.msgColor=msgColor; + this.boxColor=boxColor; + this.window=window; + this.maxChars=maxChars; + textFont(messageFont,fontSize); + String s="0"; + for(int i=0;ixPos+xWindow())&&(mouseXyPos+yWindow()-kHeight/2)&&(mouseY0){ + inputText=inputText.substring(0,inputText.length()-1); + } else if(k==ENTER || k==RETURN){ + activeInputBox=null; + for( InputBox inputBox : linkedBoxes) + inputBox.setIntValue(getIntValue()); + if(cb!=null){ + cb.cab=getIntValue(); + cb.bText=str(cb.cab); + cb.cabFile=("cab-"+cb.cab+".jpg"); + cb.cabImage=loadImage(cb.cabFile); + cb.name="Cab"+cb.cab; + cabsHM.put(cb.name,cb); + cb.editWindow.close(); + } + } else if(k==TAB){ + if(nextBox!=null) + nextBox.pressed(); + else + activeInputBox=null; + for( InputBox inputBox : linkedBoxes) + inputBox.setIntValue(getIntValue()); + if(cb!=null){ + setIntValue(cb.cab); + cb.editWindow.close(); + } + } + } // kc!=CODED + } // keyStroke + +} // InputBox Class + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gWindows.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gWindows.pde new file mode 100644 index 0000000..bfd0475 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/gWindows.pde @@ -0,0 +1,291 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Generic Windows +// +// Window - creates a window box of a specified size, color, and +// initial position into which other components can be placed +// such as buttons, message boxes, and text boxes +// +// DragBar - creates a drag bar on window to allow it to be dragged +// across screen +// +// CloseButton - creates close button on window that closes window box +// - windows are normally opened by other buttons or key commands +// defined elsewhere +// +// ImageWindow - extends Window to create a window bx into which +// a single cab image tied to a specified throttle will be displayed +// +// JPGWindow - extends Window to create a generic window box for diplaying a single jpg image +// +////////////////////////////////////////////////////////////////////////// + +class Window extends DccComponent{ + int xPos, yPos; + int kWidth, kHeight; + color backgroundColor; + color outlineColor; + + ArrayList windowComponents = new ArrayList(); + + Window(int xPos, int yPos, int kWidth, int kHeight, color backgroundColor, color outlineColor){ + this.xPos=xPos; + this.yPos=yPos; + this.kWidth=kWidth; + this.kHeight=kHeight; + this.backgroundColor=backgroundColor; + this.outlineColor=outlineColor; + } // Window + +////////////////////////////////////////////////////////////////////////// + + void display(){ + + rectMode(CORNER); + fill(backgroundColor); + strokeWeight(3); + stroke(outlineColor); + rect(xPos,yPos,kWidth,kHeight); + } // display + +////////////////////////////////////////////////////////////////////////// + + void check(){ + if(selectedComponent==null && (mouseX>xPos)&&(mouseXyPos)&&(mouseYxPos+window.xPos)&&(mouseXyPos+window.yPos)&&(mouseYxPos+window.xPos)&&(mouseXyPos+window.yPos)&&(mouseY1024){ + msgBoxMain.setMessage("Error - CV must be in range 1-1024",color(255,30,30)); + } else if(bText=="WRITE"){ + aPort.write(""); + } else if(bText=="READ"){ + aPort.write(""); + } + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void execute(int n, String c){ + String[] cs = splitTokens(c); + + int cv=int(cs[0]); + int val=int(cs[1]); + + progCVInput.setIntValue(cv); + + if(val<0){ + msgBoxMain.setMessage(n==0?"Error - Read Failed":"Error - Write Failed",color(255,30,30)); + progHEXInput.resetValue(); + progBINInput.resetValue(); + progDECInput.resetValue(); + } else{ + msgBoxMain.setMessage(n==0?"Read Succeeded":"Write Succeeded",color(30,150,30)); + progHEXInput.setIntValue(val); + progBINInput.setIntValue(val); + progDECInput.setIntValue(val); + } + + } // execute + +} // progWriteReadButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: ProgAddReadButton +////////////////////////////////////////////////////////////////////////// + +class ProgAddReadButton extends EllipseButton implements CallBack{ + InputBox shortAddInput, longAddInput; + MessageBox activeAddBox; + int longAdd; + + ProgAddReadButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox shortAddInput, InputBox longAddInput, MessageBox activeAddBox){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, shortAddInput, longAddInput, activeAddBox); + } + + ProgAddReadButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox shortAddInput, InputBox longAddInput, MessageBox activeAddBox){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + this.shortAddInput=shortAddInput; + this.longAddInput=longAddInput; + this.activeAddBox=activeAddBox; + callBacks.add(this); + } // ProgAddReadButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + + aPort.write(""); + + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void execute(int n, String c){ + String[] cs = splitTokens(c); + + int cv=int(cs[0]); + int val=int(cs[1]); + + switch(cv){ + + case 1: + if(val<0){ + msgBoxMain.setMessage("Error - Reading Short Address Failed",color(255,30,30)); + shortAddInput.resetValue(); + } else{ + shortAddInput.setIntValue(val); + aPort.write(""); + } + break; + + case 17: + if(val<0){ + msgBoxMain.setMessage("Error - Reading First Byte of Long Address Failed",color(255,30,30)); + longAddInput.resetValue(); + } else{ + longAdd=(val&0x3F)*256; + aPort.write(""); + } + break; + + case 18: + if(val<0){ + msgBoxMain.setMessage("Error - Reading Second Byte of Long Address Failed",color(255,30,30)); + longAddInput.resetValue(); + } else{ + longAdd+=val; + longAddInput.setIntValue(longAdd); + aPort.write(""); + } + break; + + case 29: + if(val<0){ + msgBoxMain.setMessage("Error - Reading Second Byte of Long Address Failed",color(255,30,30)); + activeAddBox.setMessage("?",color(200,50,50)); + } else{ + if((val&0x20)==0) + activeAddBox.setMessage("SHORT",color(200,50,50)); + else + activeAddBox.setMessage("LONG",color(200,50,50)); + msgBoxMain.setMessage("Reading Short and Long Addresses Succeeded",color(30,150,30)); + } + break; + + } + + } // execute + +} // ProgAddReadButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: ProgShortAddWriteButton +////////////////////////////////////////////////////////////////////////// + +class ProgShortAddWriteButton extends EllipseButton implements CallBack{ + InputBox addInput; + + ProgShortAddWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, addInput); + } + + ProgShortAddWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + this.addInput=addInput; + callBacks.add(this); + } // ProgAddReadButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + + int val=addInput.getIntValue(); + + if(val<1 || val>127){ + msgBoxMain.setMessage("Error - Short Address must be in range 1-127",color(255,30,30)); + } else { + aPort.write(""); + } + + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void execute(int n, String c){ + String[] cs = splitTokens(c); + + int cv=int(cs[0]); + int val=int(cs[1]); + + if(val<0){ + msgBoxMain.setMessage("Error - Write Short Address Failed",color(255,30,30)); + addInput.resetValue(); + } else{ + msgBoxMain.setMessage("Write Short Address Succeeded",color(30,150,30)); + addInput.setIntValue(val); + } + + } // execute + +} // ProgShortAddWriteButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: ProgLongAddWriteButton +////////////////////////////////////////////////////////////////////////// + +class ProgLongAddWriteButton extends EllipseButton implements CallBack{ + InputBox addInput; + int longAddIn, longAddOut; + + ProgLongAddWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, addInput); + } + + ProgLongAddWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox addInput){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + this.addInput=addInput; + callBacks.add(this); + } // ProgAddReadButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + + longAddIn=addInput.getIntValue(); + + if(longAddIn<0 || longAddIn>10239){ + msgBoxMain.setMessage("Error - Long Address must be in range 0-10239",color(255,30,30)); + } else { + aPort.write(""); + } + + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void execute(int n, String c){ + String[] cs = splitTokens(c); + + int cv=int(cs[0]); + int val=int(cs[1]); + + switch(cv){ + + case 17: + if(val<0){ + msgBoxMain.setMessage("Error - Writing First Byte of Long Address Failed",color(255,30,30)); + addInput.resetValue(); + } else{ + longAddOut=(val&0x3F)*256; + aPort.write(""); + } + break; + + case 18: + if(val<0){ + msgBoxMain.setMessage("Error - Writing Second Byte of Long Address Failed",color(255,30,30)); + addInput.resetValue(); + } else{ + msgBoxMain.setMessage("Write Long Address Succeeded",color(30,150,30)); + longAddOut+=val; + addInput.setIntValue(longAddOut); + } + break; + } + + } // execute + +} // ProgLongAddWriteButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: ProgLongShortButton +////////////////////////////////////////////////////////////////////////// + +class ProgLongShortButton extends EllipseButton implements CallBack{ + MessageBox activeAddBox; + + ProgLongShortButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, MessageBox activeAddBox){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, activeAddBox); + } + + ProgLongShortButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, MessageBox activeAddBox){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + this.activeAddBox=activeAddBox; + callBacks.add(this); + } // ProgrWriteReadButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + + if(bText=="Long"){ + aPort.write(""); + } else if(bText=="Short"){ + aPort.write(""); + } + + } // pressed + +////////////////////////////////////////////////////////////////////////// + + void execute(int n, String c){ + String[] cs = splitTokens(c); + + int val=int(cs[2]); + + switch(val){ + + case -1: + msgBoxMain.setMessage(n==1?"Error - Activating Long Address Failed":"Error - Activating Short Address Failed",color(255,30,30)); + activeAddBox.setMessage("?",color(200,50,50)); + break; + + case 0: + msgBoxMain.setMessage("Activating Short Address Succeeded",color(30,150,30)); + activeAddBox.setMessage("SHORT",color(200,50,50)); + break; + + case 1: + msgBoxMain.setMessage("Activating Long Address Succeeded",color(30,150,30)); + activeAddBox.setMessage("LONG",color(200,50,50)); + break; + } + + } // execute + +} // ProgLongShortButton Class + +////////////////////////////////////////////////////////////////////////// +// DCC Component: OpWriteButton +////////////////////////////////////////////////////////////////////////// + +class OpWriteButton extends EllipseButton{ + InputBox opCVInput, opValueInput; + + OpWriteButton(int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox opCVInput, InputBox opValueInput){ + this(null, xPos, yPos, bWidth, bHeight, baseHue, fontSize, bText, opCVInput, opValueInput); + } + + OpWriteButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText, InputBox opCVInput, InputBox opValueInput){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + this.opCVInput=opCVInput; + this.opValueInput=opValueInput; + } // OpWriteButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + super.pressed(); + int cab=opCabInput.getIntValue(); + int cv=opCVInput.getIntValue(); + int val=opValueInput.getIntValue(); + + if(cab<1 || cab>10239){ + msgBoxMain.setMessage("Error - Cab must be in range 1-10239",color(255,30,30)); + return; + } + if(cv<1 || cv>1024){ + msgBoxMain.setMessage("Error - CV must be in range 1-1024",color(255,30,30)); + return; + } + + if(bText=="WRITE"){ + aPort.write(""); + return; + } + + if(val>7){ + msgBoxMain.setMessage("Error - Bit must be in range 0-7",color(255,30,30)); + return; + } + + + if(bText=="SET"){ + aPort.write(""); + } else if(bText=="CLEAR"){ + aPort.write(""); + } + + } // pressed + +} // OpWriteButton Class \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/DCCpp_Controller/serialComponents.pde b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/serialComponents.pde new file mode 100644 index 0000000..6a7abc9 --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/DCCpp_Controller/serialComponents.pde @@ -0,0 +1,247 @@ +////////////////////////////////////////////////////////////////////////// +// DCC++ CONTROLLER: Serial Components +// +// All classes and methods related to serial communication to and from +// the DCC++ Base Station +// +// PortScanButton - function depends on button label as follows: +// +// "SCAN" - create a list all serial ports on the computer +// ">" - scroll forward through the list +// "<" - scroll backwards through the list +// "CONNECT" - attempt to connect to a DCC++ Base Station +// +// - the default configuration of DCC++ Controller defines a +// Serial Window that includes all of these components +// +// ArduinoPort - defines a generic port connection to the DCC++ Base Station +// - extends Processing's normal Serial class by adding an +// Ethernet or WiFi Client connection at port 2560 as well as +// a "simulation" function so that DCC++ Controller can be run +// in "emulator" mode without actually establishing a connection +// to the DCC++ Base Station +// - ideal for developing, testing, and demonstrating DCC++ Controller +// without an Arduino +// - also adds functionality that echos to a pre-specified text box all +// text that is written to the DCC++ Base Station +// - the default configuration of DCC++ Controller defines a +// Diagnostic Window that includes this text box and is useful for +// observing the exact commands DCC++ Controller sends to the +// DCC++ Base Station +// +////////////////////////////////////////////////////////////////////////// +// DCC Component: PortScanButton +////////////////////////////////////////////////////////////////////////// + +class PortScanButton extends RectButton{ + boolean isComplete; + + PortScanButton(Window window, int xPos, int yPos, int bWidth, int bHeight, int baseHue, int fontSize, String bText){ + super(window, xPos, yPos, bWidth, bHeight, baseHue, color(255), fontSize, bText, ButtonType.ONESHOT); + } // AccessoryButton + +////////////////////////////////////////////////////////////////////////// + + void pressed(){ + isComplete=false; + super.pressed(); + } + +////////////////////////////////////////////////////////////////////////// + + void scan(){ + String[] emulator = {"Emulator"}; + String[] serverList=splitTokens(serverListXML.getContent()); + + + aPort.portList=concat(emulator,Serial.list()); + aPort.portList=concat(aPort.portList,serverList); + + aPort.displayedPort=0; + portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200)); + portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50)); + + } // scan + +////////////////////////////////////////////////////////////////////////// + + void turnOff(){ + String[] emulator = {"Emulator"}; + + if(isComplete==false){ + isComplete=true; + return; + } + + super.turnOff(); + + if(bText=="SCAN"){ + scan(); + return; + } // SCAN + + if(bText==">" && aPort.portList!=null && aPort.portList.length>0){ + aPort.displayedPort=(aPort.displayedPort+1)%aPort.portList.length; + portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200)); + portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50)); + return; + } // > + + if(bText=="<" && aPort.portList!=null && aPort.portList.length>0){ + if(--aPort.displayedPort<0) + aPort.displayedPort=aPort.portList.length-1; + portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200)); + portNumBox.setMessage("Port "+(aPort.displayedPort+1)+" of "+aPort.portList.length,color(50,50,50)); + return; + } // < + + if(bText=="CONNECT" && aPort.portList!=null && aPort.portList.length>0){ + arduinoPortXML.setContent(aPort.portList[aPort.displayedPort]); + portBox.setMessage(aPort.portList[aPort.displayedPort],aPort.portList[aPort.displayedPort].equals(arduinoPortXML.getContent())?color(50,150,50):color(50,50,200)); + saveXML(dccStatusXML,STATUS_FILE); + baseID=null; + aPort.open(arduinoPortXML.getContent()); + return; + } // < + + } // pressed + +} // PortScanButton Class + +////////////////////////////////////////////////////////////////////////// +// ArduinoPort +////////////////////////////////////////////////////////////////////////// + +class ArduinoPort{ + Serial port; + Client client; + String[] portList; + int displayedPort; + boolean emulate; + String portName; + int baud; + + ArduinoPort(){ + emulate=false; + port=null; + client=null; + } + +////////////////////////////////////////////////////////////////////////// + + void write(String text){ + msgBoxDiagOut.setMessage(text,color(30,30,150)); + + if(emulate) + simulate(text); + else if(port!=null) + port.write(text); + else if(client!=null) + client.write(text); + + } // write + +////////////////////////////////////////////////////////////////////////// + + void simulate(String text){ + String c = text.substring(2,text.length()-1); + + switch(text.charAt(1)){ + + case 'c': + if(powerButton.isOn) + receivedString(""); + else + receivedString(""); + break; + + case '0': + receivedString(""); + break; + + case '1': + receivedString(""); + break; + + case 't': + String[] s = splitTokens(c); + if(int(s[2])==-1) + s[2]="0"; + receivedString(""); + break; + + case 'T': + String[] s1 = splitTokens(c); + receivedString(""); + break; + + case 'z': + String[] s2 = splitTokens(c); + receivedString(""); + break; + + } //switch + + } // simulate + +////////////////////////////////////////////////////////////////////////// + + void open(String portName){ + int t; + this.portName=portName; + + emulate=false; + + if(port!=null) + port.stop(); + + if(client!=null) + client.stop(); + + int[] n=int(splitTokens(portName,".")); + + if(n.length==4 && n[0]>0 && n[0]<=255 && n[1]>=0 && n[1]<=255 && n[2]>=0 && n[2]<=255 && n[3]>=0 && n[3]<=255){ + client=new Client(Applet,portName,2560); + if(client.ip()==null){ + msgBoxMain.setMessage("Can't connect to Server: "+portName,color(200,50,0)); + client=null; + return; + } else if(client!=null){ + msgBoxMain.setMessage("Waiting for Base Station at Server: "+client.ip(),color(200,50,0)); + client.write(""); + return; + } + } + + if(portName.equals("Emulator")){ + emulate=true; + msgBoxMain.setMessage("Using Emulator to Simulate Arduino",color(50,50,200)); + return; + } + + try{ + port=new Serial(Applet,portName,BASE_BAUD); + port.bufferUntil('>'); + } catch(Exception e){ + msgBoxMain.setMessage("Serial Port Busy: "+portName,color(200,50,0)); + port=null; + return; + } + + if(port.port==null){ + msgBoxMain.setMessage("Can't find Serial Port: "+portName,color(200,50,0)); + port=null; + return; + } + + msgBoxMain.setMessage("Waiting for Base Station at Serial Port: "+portName,color(200,50,0)); + + t=millis(); + while(millis()-t<3000); + port.write(""); + + } // open() + +} // Class ArduinoPort + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/DCC-Centrale/Firmware/Controller/README.md b/DCC-Centrale/Firmware/Controller/README.md new file mode 100644 index 0000000..837d5af --- /dev/null +++ b/DCC-Centrale/Firmware/Controller/README.md @@ -0,0 +1,58 @@ +What’s DCC++ +------------ + +DCC++ is an open-source hardware and software system for the operation of DCC-equipped model railroads. + +The system consists of two parts, the DCC++ Base Station and the DCC++ Controller. + +The DCC++ Base Station consists of an Arduino micro controller fitted with an Arduino Motor Shield that can be connected directly to the tracks of a model railroad. + +The DCC++ Controller provides operators with a customizable GUI to control their model railroad. It is written in Java using the Processing graphics library and IDE and communicates with the DCC++ Base Station via a standard serial connection over a USB cable or wireless over BlueTooth. + +What’s in this Repository +------------------------- + +This repository, Controller, contains a complete DCC++ Graphical User Interface sketch, written in Java, and designed for use within the Processing IDE environment (www.processing.org). All sketch files are in the folder named DCCpp_Controller. + +To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Controller.pde within the DCCpp_Controller folder using your Processing IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Processing IDE relies on the structure and name of the folder to properly display and run the code. + +Though this code base is relatively mature and has been tested with the latest version of Processing (3.0.1), it is not as well-commented or documented as the DCC++ Base Station code for the Arduino. + +Before using this code, you may wish to visit my DCC++ YouTube channel (see link on main DCC++ GitHub screen) and watch the demo videos showing all the features and functions of this interface. + +Use and Customization +--------------------- + +DCC++ Controller can be used with or withouth a connection to a DCC++ Base Station, though obviously without a Base Station you won't be able to control a model railroad. However, you would still be able to test out the interface, modify the layout, create turnouts, add and delete cabs, throttles, etc. + +All main operating functions are found on the main screen. Hitting 'h' toggles a help window on and off that contains a list of all other windows that can be opened with similar single-key toggling. You can also toggle the help window via the question mark in the upper right corner of the screen. + +To connect the DCC++ Controller to a DCC++ Arduino Base Station, first connect the Base Station to your PC or Mac via its USB cable. Then open and run the DCC++ Controller within the Processing Environment. Hitting 's' will bring up a serial connection window. Hit the SCAN button and the interface will identify all available serial ports. Use the arrow keys to select which port contains your DCC++ Arduino Base Station and then hit the CONNECT button. After 5 or so seconds, a message should appear at the top of the screen indicating connectivity. If not, please re-check your serial connection and make sure you don't have the Arduino IDE Serial Monitor (or any other serial monitor) opened and connected to the Base Station. This will block the Controller from connecting to the Arduino since only one serial connection to the Arduino can be opened at a time. + +If you do not have an Arduino Base Station, or just want to test out the Controller, you can select "Emulator" from the serial connection window. This will allow the Controller to operate most functions as if it were connected to a Base Station. + +Note that most of the functions on the Controller rely on feedback from the Base Station in order to operate. This is why the imbedded "Emulator" functionality is needed -- to provide emulated feedback to the Controller. + +If you sucessfully connect the Controller to the Base Station, the first thing you may want to test is the "Power" button. This should turn on and off power to the tracks. If the Power button lights up when you press it, this means the Controller is properly communicating with the Base Station since the Power button won't light until it receives a confirm from the Base Station. + +I have pre-programmed 7 cabs and all of their functions into a single throttle. You should be able to select any cab button and control the throttle. However, unless your cab numbers happen to match one of the 7 I have included, you will not be able to operate any of your trains. Almost all of the code you will need to customize for your own layout can be found in the "controllerConfig" tab of the Processing IDE. Definitions of the throttle, the cabs, and the cab buttons can be found starting at line 283. The first cab you'll see defined is #2004 in the following line: + + cab2004 = new CabButton(tAx-125,tAy-150,50,30,150,15,2004,throttleA); + +It's okay to leave the name of the variable as cab2004 -- it could be called anything. The actual cab number is provided in the second-to-last parameter. Change this from 2004 to match the cab number for one of your locomotives. Then restart the program (you don't have to restart Processing itself, just the Controller program). Controller should have remembered your serial settings from before so you wont have to go through the serial scan and connect every time, unless you want to make a change. + +Hit the Power button and verifify that it lights up. Then hit the cab button that now should show the cab number you just modified. Give the throttle a try. If all is well, your train should now be moving. + +Starting at around line 365 in “configController” you'll find all the code that creates the track layout. You should be able to modify these to match your own. The code under the "dTracks" tab should provide some info on the parameters. + +Starting at around line 507 you'll find the code for the turnouts. The routines supporting these functions can be found in the "dRoutes" tab. Note that each turnout has a uniquely defined ID number. These numbers must match the ID number of turnouts you defined in the Arduino DCC++ Base Station sketch. If not, the turnout will not respond on the interface when you click it to switch direction (and obviously will not respond on your layout). You can define a turnout in the Base Station sketch even if it is not really connected to an accessory decoder, if you'd like to simply test the Controller functionality. + +This is a rather complex code base and it's definitely not as clean and tight as the Base Station sketch, but I hope you'll be able to get the gist of things by changing individual parameters and observing the net effect. + +Ideally, if others start to utilize this Controller, it would probably make sense to move the customization of the cabs and layout into an XML or JSON parameters file. A good project for the future... + +Enjoy! + + + + diff --git a/DCC-Centrale/Hardware/Doc/Locoduino.html b/DCC-Centrale/Hardware/Doc/Locoduino.html new file mode 100644 index 0000000..1a88c65 --- /dev/null +++ b/DCC-Centrale/Hardware/Doc/Locoduino.html @@ -0,0 +1,5151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LOCODUINO - Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + + + +

+ + + +
+
+ +
+ +
+ +

Réalisation de centrales DCC avec le logiciel libre DCC++

+ +

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

+

DCC++ : Quel matériel et quelle mise en œuvre ?

+ + + + + +

+ .
Par : bobyAndCo +

+

+ DIFFICULTÉ : +

+

Avec le premier article de cette série, Dominique nous a fait découvrir les entrailles de DCC++ et a démontré que ce programme était sans doute la meilleure solution DIY sur ARDUINO du moment pour le modélisme ferroviaire.

+

Dans un deuxième article, nous avons abordé une partie un peu plus concrète puisqu’il s’agit des interfaces H/DCC++. Entendez homme et DCC++ avec un controleur en HTML permettant de piloter ses locomotives. Bien d’autres interfaces sont envisageables, logicielles ou matérielles comme nous l’a présenté ponponmvo sur le forum avec une télécommande à infrarouge telle que celles utilisées pour les TV ou les chaines HiFi.

+

Ce troisième article se veut également très pratique puisqu’il aborde la question des matériels nécessaires pour mettre en œuvre DCC++ sur vos réseaux.

+

Cet article s’adresse principalement aux modélistes débutants ou peu expérimentés qui souhaitent des réponses simples et faciles à réaliser.

+
Article mis à jour en janvier 2022 : images agrandissables.

Le choix de l’ARDUINO :

+

Le développeur de DCC++, Gregg E. Berman, n’a prévu l’utilisation qu’avec un Arduino UNO ou un Arduino MEGA. Mais Dominique a montré dans son article que tout ATMega 328 ou 2560 peut supporter DCC++, c’est seulement la définition de Gregg dans le fichier DCCpp_Uno.h qui limite le choix. Par exemple en remplaçant :

+

#if defined  ARDUINO_AVR_UNO

+

par :

+

#if defined  ARDUINO_AVR_NANO

+

le programme compile à l’identique dans un Nano. C’est bien expliqué par Tanguy sur le Forum.

+

Pour respecter l’esprit de simplicité et de praticité évoqué en introduction, nous n’aborderons ici que l’utilisation d’un UNO ou d’un MEGA et il ne sera pas bien compliqué pour ceux qui le souhaitent d’adapter à d’autres Arduino.

+

Pour de petits réseaux en N ou en HO, le UNO saura se montrer à la hauteur. Au delà il est certainement préférable d’utiliser un MEGA d’autant que la différence de coût n’est pas très importante. Et si vous envisagez une liaison Ethernet avec votre Arduino, vous ne pourrez de toutes façons utiliser que le MEGA.

+

Notez que DCC++ détectera automatiquement le type de l’Arduino (UNO ou MEGA) sur lequel il est installé. Vous n’aurez aucun réglage à faire dans le programme.

+

LA CARTE MOTEUR :

+

Tout d’abord, à quoi sert cette carte ?

+

Le rôle d’une application DCC (DCC++, mais aussi toutes les autres), c’est de générer un signal électrique codé de manière très précise en binaire pour envoyer des informations à des locomotives ou des accessoires.

+
+
PNG - 6.9 kio
+
+

C’est une suite de 0 et de 1 qui sont traduits électriquement par des valeurs alternances positives et négatives longues (100µs) pour les 0 et courtes (50µs) pour les 1. C’est l’Arduino qui code ce signal en fonction des commandes de l’application DCC. Les terminaux sur le circuit (locomotives, accessoires, etc...) sont équipés de décodeurs qui vont traduire ces messages binaires en commandes : nous avons donc une opération de codage avec l’Arduino puis une opération de décodage au niveau des terminaux.

+

La grande innovation de la technologie DCC consiste à faire circuler cette information « de commande » dans le circuit de puissance ; tout passe dans les rails. Et comme nos Arduino qui génèrent l’information de commande sont bien incapables de produire les courants et les tensions nécessaires sur un réseau aussi modeste soit-il, il faut donc y ajouter une carte moteur.

+

La carte moteur va servir à amplifier le signal en sortie d’Arduino en conservant la forme du signal d’entrée et lui appliquer une tension de sortie en provenance d’une alimentation extérieure de +15 à - 15 V (pour le N) ou +18 à - 18 (en HO) avec une intensité qui se situe dans la majorité des cas entre 1 et 3 ampères.

+

Le signal représenté ci-dessus ne sera plus de quelques 20 mA sous 5V à la sortie de l’Arduino mais de plusieurs ampères sous 15 ou 18 volts. Ainsi sommes nous arrivés à l’alimentation électrique dont nous allons maintenant rapidement parler.

+

L’ALIMENTATION

+

Comment choisir la bonne alimentation en fonction de votre réseau ? Ce n’est pas l’objet dans cet article de fournir des méthodes et des outils sophistiqués pour le calcul de consommation. Nous vous apportons simplement ici quelques indications qui répondront aux attentes de la majorité des modélistes ferroviaires.

+

Pour le calcul de consommation, on prendra tout d’abord en compte le nombre de locomotives, la nature du réseau et l’âge des locomotives. Les locos récentes ont des moteurs peu gourmands, les vielles locos digitalisées consomment beaucoup plus. On prendra également en compte certaines fonctions spécifiques comme les fumigènes sur les locos vapeur dont la consommation est de l’ordre de 0,5A.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Mini

+

Maxi

+
HOLocomotive récente

0,20 A

+

0,40 A

+
Locomotive digitalisée

0,40 A

+

0,80 A

+
NLocomotive récente

0,15 A

+

0,30 A

+
Locomotive digitalisée

0,20 A

+

0,40 A

+
+

Pour le calcul de consommation du réseau, on tiendra aussi compte de l’éclairage dans les locos ou les wagons, des accessoires en fonctionnement, de la signalisation lumineuse…

+

Enfin, on devra aussi prendre en compte le fait que tous les équipements ne fonctionnent pas tous simultanément ; toutes les locos ne roulent pas en même temps et la consommation d’un aiguillage ne « dure » que quelques fractions de secondes (mais il est conseillé de les alimenter séparément de l’alimentation DCC).

+

Pour faire simple, on peut considérer qu’un réseau en HO avec 4 à 5 locomotives fonctionnant simultanément consomme environ 2 à 2,5A et le même en N, environ 1,5 à 2A.

+

Le tableau ci-dessous nous montre donc que le choix d’une alimentation devra être au minimum de 30VA en N et 54VA en HO.

+ + + + + + + + + + + + + + + + + + + +
AmpèresVoltsVA
N

2

+

15

+

30

+
HO

3

+

18

+

54

+
+

Et comme « qui peut le plus peut le moins », n’hésitez pas à choisir une puissance plus élevée, il n’y a que votre facture EDF qui s’en plaindra.

+

Le choix de la carte moteur.

+

Revenons à la carte moteur qui est un élément important dans notre configuration matérielle. Vous pourrez opter soit pour une carte sous forme de shield que vous enficherez sur votre Arduino, soit pour une carte indépendante. Le shield présente l’avantage de limiter le câblage puisqu’un certain nombre de liaisons sont réalisées par cette superposition. Les fonctions indispensables seront automatiquement assurées avec ce montage comme la coupure en cas de court circuit. La carte moteur indépendante procure sans doute une petite économie mais pas seulement ! Nous allons voir cela.

+

Précisons que Gregg E. Berman n’a envisagé dans DCC++ que des montages avec utilisation de shields.

+

Voyons tout d’abord les cartes indépendantes : Il y a de nombreuses cartes pour moteurs en vente sur la toile qui peuvent en principe faire l’affaire. Les cartes à base de L298N sont souvent retenues pour leur prix peu élevé (1 à 2 €) mais ne délivrent au maximum que 2A si elles sont équipées d’un radiateur. Notez qu’il existe des cartes pouvant délivrer 43A avec un Arduino, mais à moins de maîtriser parfaitement les moyens d’éviter les courts circuits, nous vous conseillons de rester à distance (ce circuit est plus adapté aux voitures radiocommandées). Pour des raisons que vous comprendrez facilement, nous avons limité notre choix au seul LMD18200, dont les qualités ont souvent été mises en avant ici sur Locoduino et en particulier celle de pouvoir supporter des intensités de l’ordre de 3 Ampères, chaque carte étant livrée avec un radiateur.

+

Son prix est de 10 € environ mais si vous souhaitez profiter de la possibilité offerte par DCC++ de programmer sur la voie de programmation, il vous en faudra deux. A moins de modifier à chaque fois vos câblages ce qui n’est, avouons le, pas très pratique. Il vous faudra par ailleurs ajouter une mesure de courant qui pourra être assuré très efficacement par un MAX471 au prix de 1 à 2 € multiplié par 2 si vous alimentez simultanément la voie principale et la voie de programmation. Cette mesure de courant est prise en compte par DCC++ pour protéger votre matériel contre les court-circuits et aussi pour récupérer les réponses par consommation de courant du décodeur interrogé ou programmé.

+

Voici les schémas de câblage avec le LMD18200.

+

 

+

+Avec un Arduino UNO et la seule voie principale :

+
+
PNG - 148.4 kio
+
+

 

+ + + + + + + + + + + + + + + + + + + +
+

UNO

+
+

Main

+
+

P W M

+

3

+
+

 

+
+

 

+
+

DIR

+

10

+
+

 

+
+

 

+
+

MAX471

+

A0

+
+

 

+

 

+

Avec un Arduino UNO, voie principale et voie de programmation :

+
+
PNG - 204.2 kio
+
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

UNO

+
+

Main

+
+

P W M

+

3

+
+

 

+
+

 

+
+

DIR

+

10

+
+

 

+
+

 

+
+

MAX471

+

A0

+
+

UNO

+
+

Prog

+
+

P W M

+

11

+
+

 

+
+

 

+
+

DIR

+

5

+
+

 

+
+

 

+
+

MAX471

+

A1

+
+

 

+

 

+

+Avec un Arduino MEGA et la seule voie principale :

+
+
PNG - 158.6 kio
+
+

 

+ + + + + + + + + + + + + + + + + + + +
+

MEGA

+
+

Main

+
+

P W M

+

3

+
+

 

+
+

 

+
+

DIR

+

12

+
+

 

+
+

 

+
+

MAX471

+

A0

+
+

 

+

 

+

Avec un Arduino MEGA, voie principale et voie de programmation :

+
+
PNG - 212.7 kio
+
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

MEGA

+
+

Main

+
+

P W M

+

3

+
+

 

+
+

 

+
+

DIR

+

12

+
+

 

+
+

 

+
+

MAX471

+

A0

+
+

MEGA

+
+

Prog

+
+

P W M

+

11

+
+

 

+
+

 

+
+

DIR

+

2

+
+

 

+
+

 

+
+

MAX471

+

A1

+
+

 

+

 

+

Dans le fichier config.h de DCC++,

+

#define MOTOR_SHIELD_TYPE devra être à 0 mais c’est l’option par défaut.

+
// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE:
+//
+//  0 = ARDUINO MOTOR SHIELD          (MAX 18V/2A PER CHANNEL)
+//  1 = POLOLU MC33926 MOTOR SHIELD   (MAX 28V/3A PER CHANNEL)
+
+#define MOTOR_SHIELD_TYPE   0
+

Vous pourrez en profiter pour remplacer ARDUINO MOTOR SHIELD par :

+

 

+

+//  0 = LMD18200 MOTOR SHIELD         (MAX 28V/3A PER CHANNEL)

+

 

+

DCC++ et les shields moteurs.

+

L’avantage d’un shield réside dans la simplification du câblage et l’encombrement du montage. Les deux shields moteurs que nous vous présentons disposent de deux sorties permettant d’alimenter la voie principale et la voie de programmation. Ils réalisent par ailleurs la mesure du courant permettant ainsi de se dispenser des MAX471.

+

ARDUINO MOTOR SHIELD

+

Le premier montage que nous allons voir concerne l’ARDUINO MOTOR SHIELD qui est l’une des deux solutions proposées « en natif » par DCC++.

+

Voir la présentation de ARDUINO MOTOR SHIELD sur www.arduino.cc

+

ARDUINO MOTOR SHIELD est basé sur le L298 dont nous parlions précédemment. Si les 18 Volts délivrés sont suffisants en HO et en N, son courant (théorique) maxi donnée pour 2 Ampères pourra se révéler un peu juste selon votre parc de locomotives surtout si vous utilisez la voie DCC pour l’alimentation de vos accessoires.

+
+
JPEG - 87.2 kio
+
Arduino Motor Shield monté sur une carte Ethernet elle même montée sur un MEGA +
+
Arduino Motor Shield monté sur une carte Ethernet elle même montée sur un MEGA
+
PNG - 189.4 kio
+
+

Vous constatez que le câblage est nettement simplifié. Sur le shield moteur, vous devez relier la pin 5 à la pin 13 et la pin 10 à la pin 12. Notez que toutes les pins encadrées en rouge ou remplies de rouge sont réservées à DCC++ et ne doivent pas êtres utilisées. Toutes les pins encadrées en vert sont disponibles ainsi que les masses (GND) et les sorties de courant +5V et + 3,3V.

+

Avec un MEGA, le câblage sur l’Arduino Motor Shield est sensiblement différent. Vous n’aurez qu’à relier la pin 2 à la pin 13. Là aussi, toutes les pins encadrées en rouge ou remplies de rouge sont réservées à DCC++ et ne doivent pas être utilisées. Toutes les pins encadrées en vert sont disponibles ainsi que les masses (GND) et les sorties de courant +5V et + 3,3V.

+

Les pins 4 et 10, (losanges jaunes) sont réservées dans le seul cas où vous utilisez un shield Ethernet.

+
+
PNG - 189.6 kio
+
+

Notez qu’avec un UNO comme avec un MEGA, vous devrez couper la liaison V-IN Connect tel qu’indiqué ci-dessous. Il faut utiliser un cutter délicatement pour ne pas déborder de la zone concernée et il faut s’assurer que le contact est bien coupé.

+
+
PNG - 734 kio
+

POLOLU MC33926 MOTOR SHIELD

+

Le POLOLU est le second shield figurant dans les options proposées par DCC++. Sa dénomination exacte est en fait : Pololu Dual MC33926 Motor Driver Shield for Arduino puisque chez Pololu il existe d’autres références pour MC33926 mais qui ne pourront pas se monter en shield sur un Arduino.

+
+
JPEG - 393.1 kio
+

POLOLU Dual MC33926 Motor Driver Shield for Arduino ici montée sur un Uno R3

+

 

+

Voici la page de présentation du constructeur. La présentation et la documentation sont très complètes.

+

Comme les autres shields, le Pololu présente l’avantage de simplifier les câblages et de réduire l’encombrement. Sa qualité de fabrication est soignée. Notez qu’il est livré bornes non assemblées. Vous devrez donc jouer du fer à souder avant de pouvoir l’utiliser.

+

Le Pololu est donné pour des performances de 3A sur chaque sortie (voie principale et voie de programmation). En pratique, cela doit être fortement nuancé. En premier lieu, le refroidissement du Pololu influence ses performances. Aussi n’est-ce pas un luxe de lui adjoindre des dissipateurs comme on peut le voir sur la photo ci-dessous et/ou mieux encore, un ventilateur.

+

Par ailleurs, le Pololu se met rapidement en sécurité à l’occasion de pics de courant comme il s’en produit lorsque l’on met le circuit sous tension avec plusieurs locomotives sur les rails. Vous n’aurez pas d’autre solution que d’en enlever quelques unes et de relancer la mise sous tension. Un peu crispant certaines fois il faut bien l’avouer.

+

Enfin, notez que le Pololu peut servir à l’alimentation de l’Arduino, ce qui peut présenter un avantage dans certaines circonstances. Mais comme préconisé par le fabricant, l’Arduino ne devra pas être alimenté par sa prise jack et le jumper devra lui être positionné comme indiqué sur le schéma ci-dessous.

+

Pololu précise bien que la tension d’alimentation de la carte devra respecter les préconisations pour l’Arduino, à savoir de préférence entre 7 et 12 Volts et en aucun cas dépasser 20 Volts.

+
+
PNG - 85.1 kio
+
+

Tout comme nous l’avons évoqué plus haut, vous aurez certainement à modifier le fichier config.h de DCC++ BaseStation puisque l’option Pololu n’est pas l’option par défaut.

+

#define MOTOR_SHIELD_TYPE devra être à 1.

+
// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE:
+//
+//  0 = ARDUINO MOTOR SHIELD          (MAX 18V/2A PER CHANNEL)
+//  1 = POLOLU MC33926 MOTOR SHIELD   (MAX 28V/3A PER CHANNEL)
+
+#define MOTOR_SHIELD_TYPE   1
+
JPEG - 211.9 kio
+

+POLOLU MC33926 monté sur un shield Ethernet lui même monté sur un MEGA.

+

Vous remarquez les deux dissipateurs de chaleur.

+

 

+

Câblage :

+

Voici le schéma pour l’utilisation de l’Arduino UNO + POLOLU sur la voie principale uniquement.

+

Dans ce cas, il y a juste à relier la pin 7 avec la 10.

+
+
PNG - 215.4 kio
+
+

Pour l’utilisation du POLOLU et d’un UNO avec la voie de programmation, le câblage est plus conséquent.

+
+
PNG - 229.2 kio
+
+

Il faut relier :

+
  • La pin 5 avec la pin 8
  • La pin 7 avec la pin 10
  • La pin 9 avec la pin D2
  • La pin 11 avec la pin M2PWM
+

 

+

Avec un MEGA et l’utilisation de la voie principale uniquement, il suffira juste de relier la pin 7 à la pin 12

+
+
PNG - 215.9 kio
+
+

Avec un MEGA et l’utilisation de la voie principale et de la voie de programmation, Il faut relier :

+
  • La pin 2 avec la pin 8
  • La pin 7 avec la pin 12
  • La pin 9 avec la pin D2
  • La pin 11 avec la pin M2PWM
+
PNG - 237.9 kio
+
+

Enfin, sur le POLOLU également, il va falloir couper délicatement certains jumps avec un cutter ("Cut Trace" sur la figure). Mais les jumpers se situent sous la carte en non dessus comme pourrait le laisser penser l’image ci-dessous.

+
+
PNG - 160 kio
+

En conclusion :

+

Vous disposez maintenant de quelques configurations matérielles éprouvées ainsi que les moyens de les mettre en œuvre pour utiliser DCC++ sur vos réseaux.

+

L’Arduino Motor Shield constitue une solution économique et assez simple à mettre en place. La détection des surtensions et courts circuits ainsi que de température excessive est intégrée. Une sortie pour la voie principale et une pour la voie de programmation. Ses performances en limitent néanmoins l’usage aux petits et moyens réseaux. Attention à la qualité de fabrication. Toutes cartes à base de L298 ne se valent pas.

+

Le POLOLU Dual MC33926 Motor Driver Shield for Arduino offre une solution complète et intégrée ; détection des surtensions et courts circuits ainsi que température excessive. Une sortie pour la voie principale et une pour la voie de programmation. Ses performances sont cependant un peu en deçà d’un LMD18200 et son prix est élevé, de l’ordre de 30 €. La qualité du produit et de la fabrication nous semble irréprochable.

+

Le LMD18200 est un peu le chouchou de Locoduino. Il est vrai que ses performances sont très bonnes et son prix abordable (10 €). Il nécessite un peu plus de câblage et l’ajout d’un MAX471 extérieur (car la mesure de courant intégrée ne donne pas satisfaction). Dans le cas où vous envisagez de n’utiliser que la voie principale, c’est incontestablement le bon choix.

+

Voilà, nous avons vanté dans les articles précédents la simplicité de communication avec DCC++. Une fois votre montage réalisé, vous pouvez le tester simplement avec le moniteur de l’IDE de l’Arduino. Saisissez les 3 caractères <1> pour mettre votre circuit sous tension. Les LED’s sur votre shield ou que nous avons ajoutées dans le montages du LMD18200 doivent s’allumer.

+

Comme d’habitude, n’hésitez pas à nous poser toutes vos questions et nous faire part de vos propres expériences.

+

Pour mettre en oeuvre complètement votre centrale DCC++, il faut charger le logiciel et l’utiliser : Nous vous renvoyons donc aux articles :

+
+ + +

127 +Messages

+ + + +
    + + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 25 janvier 2017 14:20, par plnew +
    +
    +

    Bonjour et un grand merci pour vos articles sur le DCC

    +

    j’ai une question concernant le montage avec le circuit LMD18200 et l’Arduino, sur le forum j’avais lu qu’il fallait ajouter une résistance de 10k entre PWM et BRAKE, je n’ai pas retrouvé dans le forum mais j’avais sauvegardé l’image (nom de l’image : dominique-Platine.002.jpg).
    +quand est-il concernant cette résistance utile ou pas ?

    +

    une seconde question concernant dcc++ : peut-on utiliser les pins disponibles de l’arduino pour contrôler la loco (vitesse,sens,arret) dans ce cas pouvez-vous m’aiguiller sur la programmation a ajouter dans le programme DCCpp_Uno

    +

    d’avance merci
    +Pascal

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 25 janvier 2017 18:44, par bobyAndCo +
    +
    +

    Bonjour Pascal,

    +

     

    +

    +Merci pour vos remerciements :-))))

    +

     

    +

    +1° - Résistance de 10 KΩ ou pas ? Effectivement, Dominique à publié un schéma où figurait une telle résistance. Il avait trouvé par là même la réponse à un problème qu’il rencontrait à l’activation du signal DCC. Mais si tout fonctionne bien sans, vous pouvez tout à fait vous en dispenser. Personnellement j’utilise des montages LMD18200 et Pololu avec DCC++ sur mon réseau depuis plus de 6 mois sans 10 KΩ et ça fonctionne !

    +

     

    +

    +2° - J’avoue que je ne comprends pas du tout la question.

    +
    +

    peut-on utiliser les pins disponibles de l’arduino pour contrôler la loco (vitesse,sens,arret) ?

    +
    +

    Pourquoi voulez-vous utiliser des pins disponibles sur l’Aduino pour contrôler la loco (vitesse,sens,arret) alors même que c’est DCC++ qui le fait ??? Faites un des montages proposés répondant à votre matériel et à vos besoins, téléversez DCC++ (BaseStation) sur votre Arduino et le tour est joué. Entrez dans la zone de saisie du moniteur de l’IDE "<1>" et vous mettez votre réseau sous tension. Puis entrez "< t 1 3 120 1 >" et vous allez voir votre locomotive ayant pour adresse "3" avancer à la vitesse de 120 en avant.

    +

     

    +

    +Alors bien sûr, ce n’est pas très pratique avec le moniteur de l’IDE et c’est pourquoi il faut une interface qui envoie les commandes à l’Arduino par le port série ou par ethernet. Reportez vous par exemple à l’article précédent qui propose un controller en HTML. Voyez aussi les posts de Ponponmov qui lui à mis au point un pilotage basé sur une télécommande TV. Mais vous pouvez tout autant utiliser un logiciel comme JMRI

    +

     

    +

    +Bien cordialement.

    +

     

    +

    +Christophe.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 25 janvier 2017 19:46, par plnew +
      +
      +

      Bonsoir Christophe et merci pour la réponse rapide

      +

      j’ai suivi les articles et je suis arrivé à l’étape où les commandes du type fonctionnent parfaitement.

      +

      pour le projet de "Ponponmov" basé sur une télécommande, j’ai lu et compris qu’il fallait utiliser un deuxième arduino avec son propre .ino

      +

      question : peut-on ajouter cette fonctionnalité dans le dcc++ en recopiant le code du .ino dans dcc++ afin d’avoir un seul arduino ? (je débute avec arduino et la programmation)
      +.

      +

      vous m’indiquer :

      +
      +

      c’est pourquoi il faut une interface qui envoie les commandes à l’Arduino par le port série ou par ethernet

      +
      +

      quand je connecte un bouton à l’arduino (contenant dcc++ ), puis-je envoyer l’information sur le port série lu par la loop() de dcc++ et quelle routine ajouter à celle-ci pour prendre en compte le bouton et y affecter une action

      +

      d’avance merci

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC (3) + 25 janvier 2017 21:46, par bobyAndCo +
    +
    +

    Bonsoir Pascal,

    +

     

    +

    +Pour votre première question, je dirai que c’est sans doute Ponponmov qui est le mieux placé pour vous répondre. N’hésitez pas à le contacter par MP. Personnellement, je crois que ce n’est pas souhaitable car des applications comme DCC++ Base Station nécessitent d’utiliser à l’optimal le peu de puissance des Arduino. Par ailleurs, le code de DCC++ en langage C est complexe à appréhender pour quelqu’un qui n’est pas vraiment aguerri. Je pense que le jeu n’en vaut pas la chandelle. Alors, on dira peut être que j’ai bien réussi avec mon controller en HTML à n’avoir qu’un Arduino. C’est vrai mais il faut savoir que le serveur web que j’ai implanté dans DCC++ renvoie la totalité des calculs au controller HTML donc à l’ordinateur ou la tablette. L’Arduino de DCC++ n’est sollicité qu’une seule fois au moment du chargement du controller d’ailleurs assez long. Mais dans des versions plus élaborées de mon controller, je les ai totalement "sorties" de DCC++ BaseStation sur de plateformes dédiées (Arduino, PCDuino, Raspberry, serveur web PHP...)

    +

     

    +

    +Ma réponse à votre seconde question est dans le même ordre d’idée. Oui vous pouvez ajouter du code dans DCC++ Base Station qui va détecter le changement d’état d’une entrée quand vous appuyez sur un bouton. Si c’est pour envoyer une information codée en DCC à un décodeur (d’accessoire par exemple) peut être. Mais sincèrement, au prix d’un Arduino, je vous conseille vraiment de dissocier les deux. Une carte qui va détecter l’action du bouton, et envoyer un message structuré à DCC++ Base Station qui lui même va le coder en DCC pour l’envoyer à l’accessoire que vous voulez actionner via son décodeur. Ou alors votre premier Arduino envoi directement une commande (sans DCC donc) à votre accessoire, servo, LED ou autre.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 29 janvier 2017 16:06, par Daniel (ponponmvo) +
      +
      +

      Bonjour à tous,

      +

      J’abonde dans le sens de bobyAndCo. Mon projet de contrôleur à télécommande IR fait appel à une deuxième carte pour, entre autres, éviter les conflits de timer. En effet, la librairie IR utilise le TIMER2 qui est déjà utilisé d’autre part. De plus, si vous souhaitez ajouter des fonctions (affichage LCD comme dans le projet, etc) vous atteindrez rapidement les limites de la carte Arduino. J’ajoute enfin que la recherche d’erreurs et la mise au point sont bien plus aisées lorsque les deux fonctions (centrale DCC++ et contrôleur) sont sur deux cartes séparées. Pensez que vous pouvez contrôler toutes les fonctions de la centrale en envoyant une simple commande via le moniteur série !

      +

      Je prépare un article qui synthétisera la réalisation d’un contrôleur basé sur une télécommande IR du commerce comme décrit dans mes messages précédents. Ce sera une version revue et améliorée du croquis que j’avais présenté.

      +

      Daniel (Ponponmvo)

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 25 janvier 2017 22:57, par msport +
    +
    +

    Bonsoir,
    +je joins mes remerciements aux précédents !
    +Cet article est le bienvenu, car la créativité des auteurs m’a un peu dérouté quand j’ai découvert le site ...
    +Pour ma part, je pense qu’il faudrait recommander de débuter avec un Uno et son shield moteur, le tout alimenté par un simple bloc secteur 12 V 0,8A depuis le shield.
    +Ceci permet de commencer sans aucune soudure et pas de cablage ... Ce montage est à conserver car idéal pour avoir une voie de programmation sous la main.
    +Ensuite, la version à un LMD12800 avec son radiateur + MAX471 et une alimentation 3A permet de passer aux choses sérieuses sur la voie principale. (et sans s’embêter à câbler une voie de programmation qui ne servira qu’épisodiquement)
    +Et j’attends une probable série sur les manettes pour faire la promotion de celle à base de Nano + clavier 4x4 + codeur + liaison radio HC12.
    +Cordialement
    +PS : si on ne met qu’une LED pour voir la mise sous tension des rails, on la soumet à une tension inverse pour laquelle elle n’est pas prévue, d’où l’intérêt d’en mettre deux tête bêche.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Bonsoir,

      +

       

      +

      +Vous avez raison,

      +
      +

      PS : si on ne met qu’une LED pour voir la mise sous tension des rails, on la soumet à une tension inverse pour laquelle elle n’est pas prévue, d’où l’intérêt d’en mettre deux tête bêche.

      +
      +

      Le DCC est en effet un courant alternatif (même s’il n’est pas sinusoïdal mais carré). Avec une tension inverse de 12 à 18 V, c’est la durée de vie de la LED qui est en jeu. Deux LED’s en tête bêche est effectivement une solution ou une LED et une diode si l’on veut éviter de transformer son LMD18200 en sapin de Noël ;-) J’ai modifié les schéma et je précise bien que anode et cathode de la LED et de la diode doivent être inversées.

      +

       

      +

      +Merci pour cette précision.
      +BobyAndCo

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 28 janvier 2017 16:34, par Dominique DELEAZ +
        +
        +

        Bonjour et encore bravo pour la clarté des explications.

        +

        J’utilise le shields motor et c’est un peu juste en effet avec plusieurs loco, donc je vais passer sur le LMD18200 et je ne trouve pas ou acheter un MAX 471.

        +

        Merci de votre aide.

        +

        Dominique

        + + + + + + +

        Répondre

        +
        +
        + + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 janvier 2017 16:49, par bobyAndCo +
    +
    +

    Bonjour,

    +

     

    +

    +Pour le MAX471, sur le baie il faut taper "MAX471 DC3V-25V Voltage Current Test Sensor Module for Arduino". Il vaut mieux prendre les rouges comme ceux du schéma. Peu de chance d’en trouver qui ne viendraient pas de Chine -> Délai !

    +

     

    +

    +Bien cordialement.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 janvier 2017 19:15, par msport +
    +
    +

    Entre autres, des bleus, ceux que j’ai utilisé :

    +

    http://www.ebay.fr/itm/172359487699

    +

    http://www.ebay.fr/itm/151911379735

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 22 février 2017 11:14, par Jose Antonio Nuñez +
    +
    +

    Bonjour a tous

    +

    excelent tres bien explique pour les debutants, a quand la versión pour les nuls. Je comprends les grandes lignes mais je fini par me perdre.
    +j ai 60 ans cette annee et je n ai plus le temps de me mettre a la programation.
    +existe t il quelque chose "cles en main".
    +je capable de souder, faire un PCB, et un peu plus.
    +les seules connaissances que j ai de l arduino ont ete acquises lors de la fabrication de mon imprimante 3D.
    +y aurait il un internaute tres patient qui pourrait me debrifer en Mail prive.je voudrais me faire un reseau en N, au niveau manuel je n ai aucun problema, mais au niveau de la comprehension informatique je ne trouve pas de qualificatif superieur a NUL dommage.
    +je suis en espagne et cela explique qu il n y ai pas d accent dans ce message,veuillez m en excuser mais les claviers sont sans accent ici.
    +merci

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 22 février 2017 12:29, par Dominique +
      +
      +

      Bonjour José.

      +

      Il y a tellement de cours pour débuter en Arduino que vous devriez facilement apprendre, surtout à 60 ans avec 30 à 40 ans devant vous.

      +

      Commencez par consulter ceci :
      +http://forum.locoduino.org/index.ph...

      +

      L’Arduino s’apprend en pratiquant, il faut faire le plus possible des exemples donnés qui vous permettront d’assimiler les bases.
      +Apres vous verrez que le reste vient naturellement.

      +

      Bon courage

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 22 février 2017 12:30, par BobyAndCo +
      +
      +

      Bienvenue à nos amis Espagnols et bonjour Jose,

      +

       

      +

      +Félicitation tout d’abord pour votre français parfaitement compréhensible. Pour répondre à votre attente, j’ai sans doute ce que vous recherchez. J’ai a plusieurs reprises parlé ici sur locoduino d’un controller web pour piloter DCC++ qui se veut très simple d’utilisation. Je viens tout juste de terminer une version on-line, c’est à dire que l’on y accède directement par internet et que l’on peut piloter ses propres locomotives, il n’y a rien à télécharger, rien à installer sur son ordinateur.

      +

       

      +

      +La version Beta est opérationnelle et je suis en train de finir la documentation (ce qui sera fait dans 2 ou 3 jours au maximum). J’ouvrirai alors un nouveau fil sur le forum afin que tous ceux qui sont intéressés par ce projet puissent échanger et le faire évoluer. Je ne manquerai pas de vous tenir informé.

      +

       

      +

      +Bien amicalement.

      +

       

      +

      +Christophe

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 5 mars 2017 14:44, par BobyAndCo +
    +
    +

    Bonjour,

    +

    Comme je l’avais annoncé dans le post précédent, la version on-line de mon controller est maintenant disponible avec sa documentation. J’ai ouvert un sujet dédié visible ici.

    +

    Bien amicalement.

    +

    Christophe.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Bonjour,
    +Ce dossier est très intéressant et d’une grande qualité. Je suis sur le point de réaliser la centrale avec le LMD18200. Je dispose d’une alimentation de PC portable de 19V pour 4,47A donc largement dimensionnée mais je me demande si 19V n’est pas trop élevé. Merci pour ce tutoriel.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Bonsoir,

      +

      Merci pour vos appréciations. Vous ne précisez pas si vous êtes en HO ou en N. En HO, il n’y a pas de problème pour 19 volts. En N, cela peut faire un peu beaucoup.

      +

      En N, selon moi, le plus simple serait d’abaisser la tension avec un régulateur à 15 - 16 volts. Attention tout de même à bien refroidir, il peut y avoir pas moins de 5 à 9 W à dissiper selon l’intensité (2A ?).

      +

      Bien amicalement.

      +

      Christophe.

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        + +
        +

        Bonjour Christophe et merci pour votre réactivité. J’avais aussi noté mon oubli mais il s’agit bien de HO. Mon réseau est de dimensions raisonnables et je pense ne pas avoir de problème d’intensité mais effectivement je serais vigilant quant aux échauffements. Encore merci pour ce travail et les précieux conseils qui vont avec.
        +Bien cordialement.
        +Pascal

        + + + + + + +

        Répondre

        +
        +
        + + +
          + + +
        • +
          + +
          +

          Bonjour,

          +

          L’avantage du LMD18200 est qu’il autorise des intensités supérieures. Je viens d’en faire récemment le constat avec le Pololu pourtant donné pour 3A qui sur la voie de programmation "disjoncte" avec une seule loco. Ne surtout pas oublier le MAX471 sinon ça va faire de la fumée dans la loco en cas de court jus !

          +

          Bien amicalement.

          +

          Christophe

          + + + + + + +

          Répondre

          +
          +
          + + + +
        • + + +
        + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    + +
    +

    Bonjour, je démarre en digital et j’ai enfin réalisé ma centrale en HO sur mega 2560 avec LMD18200 et max471 (bleu, faute d’avoir trouvé le modèle rouge). J’ai crée le fichier locos.jso avec quelques locos et comme première id=0, adress=5 (programmée avec Sprog et reconnue) et register=0 aussi. J’ouvre control.htm sans problème, je sélectionne la locomotive sur la voie dans la liste déroulante mais dés que je veux opérer un ordre (marche avant ou feux par ex.) j’obtiens le message "Vous devez sélectionner une locomotive !" inlassablement. Pouvez-vous m’indiquer l’erreur possible car comme je peux lancer control.htm, je pense que la carte sd est bien lue. Merci pour l’intérêt que vous voudrez bien porter à ma question. Je sens que je suis près du bout mais voilà, ça bloque.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Pascal,

      +

      Après avoir "mis le nez dans le code", je vois que le register doit être supérieur à 0 (zéro) !!! L’ID peut lui être à 0 mais pas le register (c’est une convention DCC++).

      +

      J’espère que ça réglera le problème.

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        + +
        +

        C’est génial, effectivement il faut décaler le n° de registre et commencer par 1. Quel plaisir de voir la centrale fonctionner ! Maintenant je vais pouvoir passer à la personnalisation et en réel. Encore merci pour ta réactivité qui est rassurante qu’on on débute.
        +Amicalement.

        + + + + + + +

        Répondre

        +
        +
        + + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 27 mai 2017 17:53, par BobyAndCo +
    +
    +

    Bonjour Pascal,

    +

    Il faut sélectionner une loco dans le menu déroulant. Sur la vidéo de Youtube https://www.youtube.com/watch?v=ft3... , on voit bien comment on passe d’une loco à une autre. Si toute fois vous n’avez aucune loco qui s’affiche dans le menu déroulant c’est plus embêtant. Faites-moi un retour pour m’indiquer si les locos s’affichent dans le menu déroulant.

    +

    Bien amicalement.

    +

    Christophe

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Bonjour et merci pour la réponse. Les locos s’affichent bien dans le menu déroulant, je sélectionne la première loco par clic simple sur le nom (je ne reçois pas de message en retour) mais si je sélectionne le curseur de vitesse alors là je reçois le message signalé dans mon message précédent.
    +PS : Je ne peux pas tester d’autres locos car je n’ai installé qu’un décodeur pour le moment.
    +Merci.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    En faisant de nouveaux essais je viens de remarquer que le message "Vous devez sélectionner une locomotive" n’apparaît que pour la BB63708 seule machine effectivement munie d’un décodeur. Si je sélectionne une autre loco dans la liste, je peux manipuler les curseurs mais cette loco est bien sur "virtuelle" parce non présente mais je ne reçois pas le message d’erreur. Je vais revoir mon fichier locos.jso car c’est bizarre. Dernière chose, sous le nom de la loco sélectionnée, je ne vois pas s’afficher l’adresse et le registre comme dans le test proposé dans le dossier #2.
    +Amicalement. Pascal

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Encore un point le premier registre est le 0 ou le 1. J’ai pris 0 comme l’id. Merci, j’attends votre analyse.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Ok merci BoByAndCo je viens de lire ta réponse au dessus de mes messages pour le n° de registre...

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Je profite que l’on ait à nouveau abordé la question des registres de DCC++ pour rappeler un point qui peut être la cause de soucis. DCC++ est par défaut paramètré pour 12 registres (12 locos en fait). Si l’on a besoin de plus, il faut modifier la ligne 23 de config.h : #define MAX_MAIN_REGISTERS 12.

      +

      N’oubliez pas que DCC++ peut "tourner" sur UNO et c’est sans doute pour cela que c’est paramètré à 12. Sur un Mega, il ne doit pas y avoir de problème à mon avis pour aller à 20 ou 30 et probablement 50 !!!

      +

      Christophe

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    + +
    +

    Vous ne vous étendez pas, à juste titre, sur les alimentations. Étant nouveau sur le sujet, mais désirant me lancer, je souhaiterais savoir si un adaptateur secteur pour PC portable 18V 65W peut convenir à l’alimentation d’un réseau et son pilotage par DCC++ ? (elles sont en générale protégées contre les surtensions, les courts-circuits, les surintensités et les surchauffes)

    +

    JC

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

       

      +

      +Bonjour Jean-Claude,

      +

       

      +

      +Rien de particulier n’est dit en effet car, comme vous l’avez très justement appréhendé, il n’y a pas ici de problème particulier.

      +

       

      +

      +Oui les alimentations de PC (portables mais aussi celles que l’on trouve dans les tours) ferront parfaitement l’affaire dans des configurations "normales" - consommation du réseau en DCC <= 3A soit environ 4 à 5 locos en simultané et dans la la limite autorisée par votre carte moteurs.

      +

       

      +

      +18V convient bien en HO. En N, essayez de ne pas dépasser 15 à 16 V. Bien sûr, n’oubliez pas les protection (en + de celle de l’alim) comme le MAX471 (testé et approuvé par Locoduino ;-)

      +

       

      +

      +Bienvenue puisque vous dites que vous vous lancez. N’hésitez surtout pas à poser toutes vos questions et la communauté s’efforcera de vous aider.

      +

       

      +

      +Et ne loupez pas le prochain article sur le pilotage de DCC++ qui paraîtra sous quelques jours où vous trouverez un controller "clef en main" pour vos PC, tablettes et smartphones en WiFi.

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    + +
    +

    Bonjour,
    + Question de béotien : Vous traitez les cas (Voie) et (Voie Prog). Mais, le montage LMD18200, Max471 et UNO est-il fonctionnel uniquement en mode Prog avec sorties de l’UNO A1, 5 et 11 ?

    +

    Je vois bien le message <iDCC BASE STATION FOR ARDUINO UNO / ARDUINO MOTOR SHIELD : V-1.2.1 etc... sur le moniteur. Je mets avec succès ma voie Prog seule sous tension avec <1> mais ensuite si j’essaye de lire un CV de ma machine (déja programmée CV=3 avec un SPROG 3) avec la commande : <R 3 25 25, par exemple, je ne récolte que des -1] comme réponse (read could not be verified).

    +

    J’ai pas mal cherché sur le web : j’ai ainsi essayé de baisser le niveau du discri en diminuant ACK_SAMPLE_THRESHOLD dans PacketRegister.h. Nada ! Sinon en mode Voie, c’est OK ! Ça roule !

    +

    Félicitations !

    +

    Michel

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Bonjour Michel,
      +Le lien cité par Christophe dans le forum est le bon, et des interfaces spécifiques ont été ajoutées dans la bibliothèque DCCpp pour lire des CV et découvrir l’adresse (courte et longue) sur la voie principale. Ces ajouts permettent d’avoir un ensemble de fonctionnalités complet associé à la voie de programmation uniquement. Chez moi j’ai fait une Centrale exprès pour ça.
      +Mais aujourd’hui la question n’est pas complètement résolue en ce qui concerne certains décodeurs qui ne répondent pas ( soit -1). Ça dépend des décodeurs, de certains CV, de la tension d’alimentation, et du circuit de mesure. Je vous conseille de ne pas toucher au logiciel. Nous reviendrons sur cette question dans le forum. Même des centrales du commerce ont ce problème.

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 22 février 2018 15:22, par bobyAndCo +
    +
    +

    Bonjour,

    +

     

    +

    +Je constate dans le log reporté ci-dessus que vous utilisez un UNO donc vous utilisez le port série. J’ai signalé dans l’article que la lecture de CVs ne fonctionne pas avec le port série. La configuration pour cela est MEGA + ETHERNET.

    +

     

    +

    +Mais quoiqu’il en soit, il y a des problèmes de lecture avec DCC++ pour certaines marques de décodeurs. Nous en avions beaucoup parlé il un a quelques mois sur le forum. ESU, Marklin (entre autres) ne posent pas de problème. Lenz de mémoire en pose.

    +

     

    +

    +Voir ici le fil : http://forum.locoduino.org/index.ph...

    +

     

    +

    +Ceci est intrinsèquement lié à DCC++ et nous n’avons jamais trouvé pourquoi. Désolé.

    +

     

    +

    +Bien cordialement

    +

     

    +

    +Christophe

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 9 avril 2018 13:37, par Chataignier +
    +
    +

    Bonjour et merci pour vos excellents articles. Pour la réalisation de mon réseau en N je m’interroge (et vous aussi par la même occasion) sur la possibilitée de découper l’alimentation en deux booster séparé et donc de monter deux carte motor shield l’une sur l’autre ( pour des raison de finition et de compacticité) pour bénéficier de deux sorties voies traction/principale (en 2A chacune) et une zone voie de programmation, (et aussi une sortie innutilisée). Merci d’avance

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 9 avril 2018 15:22, par bobyAndCo +
      +
      +

      Bonjour,

      +Dans l’absolu je dirais que rien ne doit s’y opposer. Les deux shieldes moteur recevant les mêmes informations de l’Arduino et les répercutant sur deux réseaux différenciés (isolés). Si la locomotive à qui s’adresse les commandes n’est pas sur le réseau 1 elle les recevra sur le réseau 2 (ou pas du tout si elle n’est ni sur l’un, ni sur l’autre).

      +Je vois cependant une limite et pas des moindres. DCC++ à un système de mesure de l’intensité du courant qui est bien utile en cas de court-circuit (déraillement par exemple). Ce dispositif ne sera pas opérationnel.

      +Par ailleurs, on peut craindre que les informations de la vie de programmation "souffrent" de ce type de montage, mais à voir.

      +Mais avant toute analyse, la question est de savoir quel est le besoin ? Il faudrait que vous me communiquiez le véritable avantage à procéder avec deux cartes au lieu d’une. Moi je n’en vois qu’un, le besoin de puissance.

      +Dans ce cas, le POLOLU MC33926 est un excellent shield qui délivre 3A (5A en pics). C’est déjà bien en N ! Personnellement, je l’utilise en HO et j’en suis très content. J’ai ajouté des refroidisseurs et modifié un paramètre dans DCC++ qui faisait que l’on n’exploitait pas tout le potentiel de la carte. Seul bémol, le prix de 30$.
      +https://www.pololu.com/product/2503

      +Mieux encore, le shield, toujours chez Pololu Dual VNH5019 qui délivre 12A par canal ! 49,95$ tout de même.
      +https://www.pololu.com/product/2507

      +Il existe aussi des cartes moteur (pas shield -> moins compact) de 10A dont nous avons parlé récemment sur Locoduino (et dont je pourrais retrouver les références si vous le souhaitez). Je ne parle pas d’une carte de 43A dont j’ai déjà eu l’occasion d’émettre des réserves quant aux conséquences dans le cas où les courts-circuits ne sont pas parfaitement gérés.

      +N’hésitez pas à préciser votre demande dans le cas où ces réponses ne seraient pas satisfaisantes.

      +Bien à vous.

      +Christophe

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 9 avril 2018 15:52, par Chataignier +
        +
        +

        Merci Christophe pour votre réponse, le réseau en question est en chantier depuis de nombreuses années, a l’origine il est en Selectrix mais seul la partie feeder et alimentation depuis deux booster est réalisée. Se pose maintenant la question pour moi au vu du cout d’une installation Selectrix et de son hypothétique abandon, soit de garder la traction en Selectrix et les accessoires en arduino/DCC++ ou de tout basculer en DCC++.
        +L’intérêt pour moi de séparer l’alimentation en deux est de garder le câblage et les isolations tel-quel, de plus en N, 2A de courant de C/C me semble un bon compromis pour la protection physique du réseau.
        +Ce que vous me dite de la surveillance du courant C/C par DCC++ me fait penser que je pourrait tout alimenté en 6A et laisser la partie logiciel gerer la sécurité.
        +merci encore

        + + + + + + +

        Répondre

        +
        +
        + + + +
      • + + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 septembre 2019 10:19, par Marius +
        +
        +

        Bonjour Christophe, pardon my French :) Pourriez-vous fournir des détails sur l’utilisation de VNH5019 pour dcc ++ ? Ce Shield nécessite 2 entrées directionnelles (MxINA/B) alors que dcc ++ génère un seul signal binaire (DIR). Avez-vous réussi à le faire fonctionner par programmation sans ajouter d’onduleur matériel ?

        + + + + + + +

        Répondre

        +
        +
        + + +
          + + +
        • +
          +
          + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 septembre 2019 11:09, par bobyAndCo +
          +
          +

          Bonjour Marius,

          +

           

          +

          Je n’ai pas fait fonctionner le shield VNH5019. Mais je pense qu’il faut dans un premier temps le monter exactement comme un MC33926 et voir ce qui se passe.

          +

           

          +

          Il est possible que cela fonctionne sans changement de la programmation de DCC++.

          +

           

          +

          Sinon, il faudrait essayer d’envoyer un 5V sur MxINB et voir ce qui se passe. Ou encore un 0V. Ce n’est surement pas autre chose.

          +

           

          +

          Par ailleurs, il existe de nombreux tuto concernant cette carte, il faut s’y reporter.

          +

           

          +

          Sur cet autre point Avez-vous réussi à le faire fonctionner par programmation sans ajouter d’onduleur matériel ? je ne vois pas ce que vous voulez dire.

          +

           

          +

          Merci de revenir vers moi avec ces précisions.

          +

           

          +

          Bien cordialement.

          +

           

          +

          Christophe

          + + + + + + +

          Répondre

          +
          +
          + + +
            + + +
          • +
            +
            + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 septembre 2019 16:11, par msport +
            +
            +

            I suggest to our english speakers friends to post in english and provide a french translation with it.
            +Je suggère à nos amis anglophones de poster en anglais et de fournir une traduction française avec.

            +

            It seems to me that the Arduino shield motor with L298 does not have inverted DIR inputs and that the Base Station sketch provides them. It should be simple to adapt the numbers of the outputs.

            +

            Otherwise we can look at the DAVE BODNAR schema with a simple inverter transistor :

            +

            Il me semble que shield motor Arduino à L298 ne dispose pas d’entrées DIR inversées et que le sketch Base Station les fournit. Il suffirait donc d’adapter les numéros des sorties.

            +

            Sinon on peut s’inspirer du montage de DAVE BODNAR avec un simple transistor inverseur :

            + +

            Voir en ligne : Booster

            + + + + +

            Répondre

            +
            +
            + + +
              + + +
            • +
              +
              + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 septembre 2019 21:14, par Marius +
              +
              +

              Hi msport,

              +

              can you point me to the place where the Base Station code generates inverted direction signal. The use of the D. Bodnar‘s inverter circuit is the default solution but I am looking for a software based utilising arduino resources.

              +

              Ipouvez-vous m’indiquer l’endroit où le code de la station de base génère un signal de direction inversé. L’utilisation du circuit inverseur de D. Bodnar est la solution par défaut, mais je recherche un logiciel utilisant des ressources Arduino.

              + + + + + + +

              Répondre

              +
              +
              + + + + + +
            • + + +
            + + +
          • + + +
          + + +
        • + + +
        + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 9 avril 2018 17:22, par bobyAndCo +
    +
    +

     
    +Il est vrai que capitaliser aujourd’hui sur Selectrix est risqué. Maintenant, s’il s’agit de rentabiliser un investissement déjà réalisé, c’est autre chose. Ca dépendent beaucoup de la question de savoir si vos décodeurs (nombre ?) sont en Selectrix seul ou Selectrix/DCC.

    +Mais j’ai une réponse plus catégorique concernant les accessoires. C’est pour moi un non sens d’utiliser le bus DCC (autant qu’on puisse le qualifier ainsi) pour autre choses que la traction. Il faut à chaque fois des décodeurs d’accessoires qui, si vous ne les fabriquez pas, coutent assez cher. Par ailleurs, faut-il rappeler que DCC n’a pas été conçu pour cela et ne le fait pas bien.

    +Vous avez partout sur le site de nombreux exemples d’accessoires à base d’Arduino Nano, Mini ou encore UNO. Le bus CAN qui est largement mis en avant sur Locoduino est simple à mettre en œuvre, performant, particulièrement fiable et économique. Il dispose par ailleurs de fonctionnalités que les autres n’ont pas comme la diffusion généralisée des messages, les filtres et les masques. Et interfacer un bus CAN avec des réseaux Ethernet et WiFi est assez simple quand comme moi vous utilisez des technologies dérivées du web : HTML, JavaScript, CSS, Bootstrap, Node.Js...

    +Ceux qui disposent d’investissements qu’ils souhaitent rentabiliser peuvent par exemple regarder avec intérêt l’article de Jean-Pierre sur l’interconnexion d’un bus S88 et d’un réseau CAN : Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

    +Dans le groupe Locoduino, nous travaillons actuellement sur des concepts de commande d’accessoires et de rétrosignalisation sur la base du bus CAN avec des choses vraiment très innovantes et totalement DIY. Garantie d’économie, d’indépendance par rapport à tel ou tel constructeur et de pérennité. Vous êtes bien placé pour le savoir avec Selectrix ! Vous devriez voir cela d’ici 8 à 10 mois au plus. Peut-être certaines choses seront-elles présentées avant ?

    +J’ai cru comprendre que vous n’étiez pas pressé. Familiarisez vous au CAN au travers des nombreux articles du site comme ceux de Dominique et Jean-Luc : Mise en oeuvre du Bus CAN entre modules Arduino. Commencez certains montages et croyez-moi vous ne serez pas déçu surtout si l’idée de faire par vous même ne vous rebute pas.

    +Conclusion, votre installation existante ou DCC++ avec un shield de 2A sont alors suffisants.

    +Bien cordialement.

    +Christophe

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 9 avril 2018 21:23, par Chataignier +
      +
      +

      cela fait un petit moment déjà que je suis vos travaux sur locoduino et j’ai pu lire vos avancées en matière de communication "inter-arduino".
      +Même si je comprend et je suis convaincu du bien fondé de votre raisonnement, je pense qu’il s’applique a des chevronés comme vous capable et désireux de concevoir un système complet en DIY.
      +Je pense m’orienter vers une gestion plus "grand public"et plus dans mes capacités, je vais faire des essais demain de pilotage en DCC car je me suis rendu compte cet après-midi que les derniers décodeur D&H que j’ai acheté ne sont déjà plus compatible en Selectrix 1, et qu’il va falloir envisager la conversion DCC de gré ou de force.
      +merci beaucoup pour vos réponses et vos liens

      +

      Cédric

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 10 janvier 2019 08:25, par Steven +
    +
    +

    Bonjour,
    +Suite à cet excellent article et après avoir manipulé déjà depuis quelques temps les arduino pour d’autres applications je me suis lancé dans la réalisation de cette centrale en utilisant une uno, un LMD et un MAX471.
    +Une fois le code importé dans la carte, installé et configuré JMRI je me retrouve devant un soucis.
    +Lorsque je pose une loco sur mon rail,avec l’adresse renseignée et que je veux la piloter rien ne se passe.Idem lorsque je fait une recherche de loco avec decodeur pro.
    + Il y a pourtant du courant dans le rail (vérifié au Voltmetre). J’ai utilisé une alimentation de qualité assez basse qui ne donne un courant très faible (400mA) cela peut il venir de là ?

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 10 janvier 2019 17:16, par Philippe MESLIN +
      +
      +

      Bonjour Steven,

      +

      Je pense que le mieux et de posé votre question dans le forum.
      +Pour la consommation, vous pouvez vous reporter au paragraphe L’ALIMENTATIONen haut de cet article.
      +Avez-vous mis une LED sur la sortie voie ?
      +Avez-vous fait un test avec le programme SerialDcc.ino ?

      +

      Cordialement,

      +

      Philippe

      + +

      Voir en ligne : SerialDcc

      + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 11 janvier 2019 07:56, par steven +
        +
        +

        Bonjour, j’ai mis une alimentation plus adaptée mais toujours rien.
        +Je n’ai pas mis la led sur les voies mais contrôle la présence de courant au multimètre (j’ai bien le courant quand je le met en route)
        +Je ferais le test avec serialdcc.ino pour vérifier

        + + + + + + +

        Répondre

        +
        +
        + + +
          + + +
        • +
          +
          + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 11 janvier 2019 08:34, par bobyAndCo +
          +
          +

          Qu’est ce que vous appelez "mettre en route" ??? Quel soft est chargé dans l’Arduino UNO ? Est-ce la version originale de DCC++ ? Si j’ai bien compris, vous utilisez JMRI pour piloter ? La liaison entre JMRI et l’Arduino est-elle réalisée par le port série ou ethernet ?

          +

          Merci de préciser pour que l’on puisse apporter des pistes de recherche.

          + + + + + + +

          Répondre

          +
          +
          + + + +
        • + + +
        + + +
      • + + +
      + + +
    • + + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 10 janvier 2019 17:50, par bobyAndCo +
      +
      +

      +Bonjour,

      +

       

      +

      +Pensez à préciser à quelle échelle est votre réseau quand il s’agit par exemple de consommation car cela n’est pas la même chose en HO ou en N.

      +

       

      +

      +Vus ne précisez pas non plus quelle est la tension !

      +

       

      +

      +Quoi qu’il en soit, avec seulement 400mA, on peut raisonnablement penser que ce soit le problème ? (Sauf en Z ;-)))

      +

       

      +

      +Suivez également les conseils de Philippe.

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 14 mars 2019 13:05, par Papymoujo +
    +
    +

    Bonjour
    +Sur les schémas, je remarque un petit montage avec une led (verte), une diode et une résistance de 1,5ko,
    +dois je répéter ce petit montage à chaque alimentation de tous les cantons ?
    +Merci

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 mars 2019 13:18, par bobyAndCo +
      +
      +

       

      +

      +Bonjour,

      +

       

      +

      +La LED n’est là que pour témoigner que le courant DCC sort bien du LMD18200 et alimente donc les rails. Dans la pratique, c’est assez utile d’avoir cette information quand quelque chose ne fonctionne pas. On sait au moins que ce n’est pas l’alimentation du réseau qui est en cause.

      +

       

      +

      +Il n’y en a donc qu’une seule LED.

      +

       

      +

      +Ensuite, pour chacun des cantons, je te conseille les détecteurs par consommation de courant dont il est question ici : http://forum.locoduino.org/index.ph...

      +

       

      +

      +Personnellement je les utilise et j’en suis totalement satisfait. Ils ont leur propre LED qui permet (si une loco est sur les rails ) de constater que l’alimentation est OK

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 14 mars 2019 16:49, par Papymoujo +
    +
    +

    J’ai été voir sur "Détecteur de présence DCC par consommation de courant" j’ai essayé de lire tout, résultat pour moi c’est du charabia.
    +Je suppose que ce sont des circuits imprimés sur les deux faces !
    +N’y a t’il pas moyen d’avoir le schémas définitif pour réaliser les typons et la liste des composants à jour ?
    +Merci d’avance.

    + + + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 14 juin 2019 18:15, par BricoleurenN +
    +
    +

    Bonjour,
    +Très intéressant vos articles, avec de nombreux détails.

    +

    Malgré tout il me reste des interrogations surtout une concernant le câblage du LMD18200 avec arduino Mega. On connecte les pins 12 et 3 sur DIR et PWM. La pin 12 correspond à la réponse de l’interruption OC1B mais à quoi correspond la pin 3. Et quelle sortie se met à 0 avec panicStop ?
    +Pour cet arrêt d’urgence, ne suffirait-il pas d’interrompre les interruptions pour qu’il n’y ai plus d’alim sur les voies ?
    +Merci
    +Bricoleur en N (je débute....)

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 juin 2019 19:31, par bobyAndCo +
      +
      +

      C’est amusant, c’est la première fois que j’entends parler de panicStop !!! Dans la norme NMRA un parle de e-stop, je pense que c’est à cela que tu fais référence, l’arrêt d’urgence ?

      +

       

      +

      +Non, la bonne solution n’est pas de couper l’alimentation électrique de la voie. Déjà pour une raison imparable concernant les locomotives équipées de PowerPack, aussi appelé "réserves d’énergie" chez REE par exemple. Elles sont conçues pour que les locomotives continuent à fonctionner même quand l’alimentation est coupée !!! M**de alors.

      +

       

      +

      +Ce qu’il y a de vraiment bien (entre autres choses) avec DCC++, c’est qu’il respecte totalement la norme NMRA. Et la norme dit que le e-stop se fait en envoyant une commande précise au décodeur des machines.

      +

       

      +

      +Par ailleurs, on parle ici de digital, c’est un peu élaboré. On n’arrête pas le système en coupant le jus. Je ne pense pas que tu fasses comme cela quand tu veux éteindre ton ordinateur.

      +

       

      +

      +Bien cordialement

      +

       

      +

      +Christophe

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 14 juin 2019 22:50, par BricoleurenN +
        +
        +

        Non ce n’est pas une invention de ma part mais une fonction présente dans la biblio DCCpp. Effectivement l’interruption matériel est une idée un peu rude pour notre matériel. J’opterais donc pour le stop avec la trame DCC correct.

        +

        Merci d’avoir éclairé ce point.

        +

        Quid du câblage du LMD18200 pin 12 et surtout pin 3 ?

        +

        Cordialement
        +Bricoleur en N

        + + + + + + +

        Répondre

        +
        +
        + + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 14 juin 2019 22:58, par bobyAndCo +
    +
    +

    Ah exact pour panicStop, mais il s’agit de DCCpp la "réécriture" qu’a fait Thierry de DCC++. Cet article parle de DCC++ et non DCCpp mais pour autant je ne savais pas que Thierry avait appelé ainsi une fonction de son soft. Il aime beaucoup s’amuser.

    +

     

    +

    +Il vaudrait mieux alors poster sur le fil qui traite de DCCpp : http://www.locoduino.org/spip.php?a...

    +

     

    +

    +Bien cordialement

    +

     

    +

    +Christophe

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    On peut tester d’autres fonctions que le <0> dans DCC++ tout aussi radicales comme :
    +< w CAB CV VALUE> : on a toutes les chances de ne pas passer inaperçu dans son club !

    +

    Le e-stop est lancé par DCC++ avec < M000 >
    +cf la mine d’or qu’est le forum :
    +http://forum.locoduino.org/index.ph...

    +

    Pour la gestion de la pin 3 sur Uno, il suffit de chercher "PWM" d’abord dans DCCpp_Uno.ino puis de suivre dans les include.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 7 juillet 2019 00:50, par GOURDON Christian +
    +
    +

    Bonjour
    +Je découvre locoduino et Je voudrais mettre en oeuvre base station master sur un arduino uno + arduino motor shield.
    +J’ai ouvert dans arduino ide DCCpp_Uno.Quand je compile il me sort des messages "warning iso c++ forbids converting a string constant to chart", pourquoi ? Quand je televerse le sketch dans la carte uno il me dit qu’il ne trouve pas de bibliothèque non valide :aucun fichier d’entête (.h) dans le repertoire DCCpp, pourquoi ?
    +Merci pour votre réponse.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 7 juillet 2019 11:09, par bobyAndCo +
    +
    +

     

    +

    +Bonjour,

    +

     

    +

    +Ce que je comprends est que vous avez essayé d’utiliser BaseStation-master.zip comme une bibliothèque en utilisant dans l’IDE "Ajouter la bibliothèque.zip".

    +

     

    +

    +Cela peut effectivement prêter à confusion mais DCCpp n’est pas une bibliothèque. Il faut juste dézipper le fichier, glisser l’ensemble du dossier dans le répertoire de votre choix mais de préférence dans "documents/arduino". Ensuite dans ce dossier, lancer le sketch en double-cliquant sur le fichier "DCCpp_Uno.ino".

    +

     

    +

    +Bien cordialement.

    + + + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 juillet 2019 19:26, par Steve Cox +
    +
    +

    Est-il possible de connecter plusieurs LMD18200 en tant que boosters contrôlant une section de piste distincte ?

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 juillet 2019 19:42, par bobyAndCo +
    +
    +

     

    +

    +Oui sur le principe rien ne s’y oppose. Il faudra totalement isoler les zones qui sont pilotées par tel ou tel LMD18200. Par contre, toutes les informations en sortie de DCC++ devront bien sûr être envoyées sur tous les LMD. Il faudra s’assurer que le courant nécessaire sur les broches PWM et DIR ne dépasse pas les capacités des broches de l’Arduino et au besoin passer par un montage à base de transistors.

    +

     

    +

    +Il faudra également s’assurer que les tensions dans les rails sont sensiblement identiques.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 août 2019 16:28, par crobard +
    +
    +

    Bonjour,
    +Désolé pour le caractère hors sujet de ma question.
    +Est-il possible d’utiliser le module LMD18200T + alimentation avec une centrale DCC classique (Je dispose d’un ECOS 2 ESU), dans l’affirmative ceci permettrai de constituer des boosters particulièrement économiques. Quel serait alors le câblage au niveau de la liaison données ?.
    +J’aurai sans doute d’autres questions à l’avenir concernant le pilotage d’aiguillages servo, accessoires et signalisation avec Arduino + centrale, voire un partage d’expérience à apporter ultérieurement.D’avance, un grand merci.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 29 août 2019 08:24, par Dominique +
    +
    +

    Non, le module lmd18200 a besoin de signaux de commande que vous ne trouverez pas dans les centrales du commerce

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Mais j’imagine que la question est en fait :
    +existe-t-il des montages (à base d’Arduino bien sur) capable de booster une centrale anémique du commerce ?
    +Et comme Locoduino a réponse à tout, la réponse a déjà été donnée :
    +http://forum.locoduino.org/index.ph...

    +

    en référence à un article américain :
    +http://www.trainelectronics.com/DCC...
    +(lire les précautions d’emploi)

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 26 septembre 2019 14:17, par GOURDON Christian +
    +
    +

    Bonjour
    +J’ai mis en place une centrale avec un arduino uno et la carte motorshield et je me heurte au problème suivant:les trames reçues sur le decodeur via le moniteur ide ne sont constituées que de points d’interrogation inversés. Cela ressemble à un problème de vitesse de transmission mais je vérifie partout et tout semble normal.Autre question naïve :est ce normal de voir constamment des trames passer alors que je ne fais rien ?
    +Avez vous une idée du pourquoi de mes problèmes.
    +Merci de votre intérêt pour mes questions.
    +Cristigou.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 26 septembre 2019 18:54, par msport +
      +
      +

      Qui pilote la centrale ? Via RX/TX ? A quelle vitesse ? (schéma bienvenu)

      +

      Effectivement des trames idle sont émises quand il n’y a rien d’autre.

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 26 septembre 2019 20:17, par GOURDON Christian +
        +
        +

        Bonsoir
        +Pour être complet dans mes explications ma centrale est un arduino uno avec une arduino motorshield comme décrit dans cet article.Elle est pilotée via USB par le logiciel rocrail implanté sur un raspberry pi3. La vitesse dans rocrail comme dans la centrale est sur 115200 bauds.

        + + + + + + +

        Répondre

        +
        +
        + + +
          + + +
        • +
          +
          + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 26 septembre 2019 21:57, par msport +
          +
          +

          Je ne connais pas le Pi, mais j’imagine qu’il y a une configuration du port matériel - qui peut échapper à Rocrail.

          +

          A regarder pour test, en passant la vitesse Arduino et serial à 9600b et autres. Le DCC fera n’importe quoi, mais ça dira quelle est la vitesse du Pi quand ça apparaitra en clair dans l’IDE.

          +

          Si le Uno dialogue correctement avec le PC via l’IDE à 115200 bauds, c’est que c’est du coté Pi qu’il y a un problème : reste à tester ce qui en sort avec un adaptateur USB / série et un genre HyperTerminal, YAT, ou autre

          + + + + + + +

          Répondre

          +
          +
          + + +
            + + +
          • +
            +
            + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 27 septembre 2019 22:35, par GOURDON Christian c +
            +
            +

            En utilisant minicom dans le raspberry j’arrive à mettre sous tension la carte arduino motorshield avec <0> et <1>.Après réflexion le raspberry ne peut pas être mis en cause:j’arrive â mettre sous tension la carte arduino motorshield avec rocrail. Quand j’agis sur le decodeur je vois des modifications dans les trames mais cela reste incohérent. Je me demande si la carte arduino motorshield est bien ok.Y a t’il un test à faire ?

            + + + + + + +

            Répondre

            +
            +
            + + + +
          • + + + +
          • +
            +
            + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 27 septembre 2019 22:44, par GOURDON Christian c +
            +
            +

            J’ai testé avec minicom,je peux mettre sous tension la carte arduino motorshield avec <0> et <1> et aussi directement avec le logiciel rocrail. Le raspberry pi n’est donc pas en cause. Quand je lance une commande d’aiguillage via le tco rocrail je vois des modifications dans les trames mais elles restent incohérentes. Je pense donc à un problème de la carte arduino motorshield. Y a t’il un test à faire ?

            + + + + + + +

            Répondre

            +
            +
            + + +
              + + +
            • +
              +
              + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 28 septembre 2019 09:39, par msport +
              +
              +

              Le test de base du shield moteur est simplement d’envoyer <0> et <1> via le serial monitor et de voir l’effet sur les quatre LED du shield (présence ou non du DCC).
              +Ensuite de démarrer une loco adresse 03 vitesse 20, avant, avec :
              +< t 1 03 20 1 >
              +Et voyez ce qui sort du Pi.

              + + + + + + +

              Répondre

              +
              +
              + + + +
            • + + +
            + + +
          • + + +
          + + +
        • + + +
        + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 26 septembre 2019 14:20, par bobyAndCo +
    +
    +

    Bonjour,

    +

    On dirait bien en effet que la vitesse sur le port série ne soit pas parametrée de manière identique dans le programme (Seriel.begin(115200)) et en bas de la fenetre du moniteur.

    + + + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 2 décembre 2019 11:32, par Zaki +
    +
    +

    can I use "SparkFun Monster Moto Shield" instead of Arduino Motor Shield or LMD18200 module ?

    + +

    Voir en ligne : SparkFun Monster Moto Shield

    + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 2 décembre 2019 17:24, par msport +
    +
    +

    This is beyond my skills ... if even possible.
    +If you need more power, you may be interrested in the Dave Bodnar booster.
    +Anyway you do not need power on the programming track and use the standard Base Station with the Arduino motor shield even with a 12V power supply.

    + +

    Voir en ligne : http://www.trainelectronics.com/DCC...

    + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    + +
    +

    Bonjour,
    +En cherchant de l’information sur internet, je suis tombé sur cette série d’articles, vraiment bien fait.
    +Moi je voudrais utiliser encore un autre Motor Shield utilisant le chip L298N.
    +Le problème, les pins current sensing du chip ne sont pas utilisés et donc pas accessible sur le board.
    +Deux solutions :

    +
    • modification hardware sur le board lui-même, on coupe les current sensing pins et on fabrique le circuit manquant.
    • utilisation du MAX471.
      +Mais alors, où je met le MAX471, dans l’entrée VIN ? Alors on mésure le total du courant.
      +Où je prend 2 MAX471 et je les met dans les sorties de la plaquette ? Cela marchera ? vue que les sorties portent les signaux DCC, pas vraiment du vrai DC...
    +

    Cordialement,

    + +

    Voir en ligne : Arduino DC Motor Control Tutorial – L298N | PWM | H-Bridge

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 11 décembre 2019 10:33, par msport +
    +
    +

    Bonjour,
    +en général on réserve le nom de shield à une carte qui s’enfiche sur un Arduino. (sauf les chinois)

    +

    Un shield moteur comme celui là (version L298HN avec radiateur) vous évite tous les problèmes de connexion, le current sensing est sur A0 et A1. Juste à modifier si on veut l’utiliser à plus de 12V. Et vu le tarif ...

    + +

    Voir en ligne : shield moteur version L298HN

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 11 décembre 2019 12:29, par Dominique +
    +
    +

    L’article en question concerne le pilotage de 2 moteurs en PWM dans un robot, ce qui n’est pas le sujet du modélisme ferroviaire ici.
    +Pour générer du DCC avec le L298 il faut convertir les 2 sorties PWM et DIR de l’Arduino en IN1 et IN2 du L298. Certains motor shield le font très bien ainsi que la mesure de courant (notamment celui d’ADAFRUIT (https://docs.rs-online.com/cafb/090...). Mais d’autres shield n’ont pas cette mesure de courant et alors il faut utiliser le schéma classique du Max471 donné dans cet article et qui reste valable.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      + +
      +

      Ma question est précisément sur l’utilisation du MAX471.
      +J’ai plusieurs shields comme le modèle dans le lien que j’ai donnée.
      +Malheureusement dans ce modèle du shield, les sorties de mesure de courant ne sont pas connectées.

      +

      La partie pour faire le signal DCC est déjà faite et fonctionne très bien.
      +Mais pour le moment c’est dangereux car la partie de la protection court circuit n’est pas implémenté vu que les bornes mesure de courant ne sont pas disponible sur cette carte.
      +Je peux mettre un MAX471 dans la connection de l’alimentation, mais alors le MAX471 mesure le total des deux sorties DCC (prog en main track).
      +Ou je peux mettre deux MAX471, un dans la sortie A et un dans la sortie B. Mais alors ils ne mesurent pas du DC, mais du AC (signal DCC)

      +

      Cordialement,

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    + +
    +

    le MAX471 mesure le total des deux sorties DCC (prog et main track).
    +Hé bien, n’utilisez pas les deux ensemble, solution préconisée dans l’article suivant :

    + +

    Voir en ligne : Une station DCC complète, polyvalente et économique avec JMRI.

    + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    + +
    +

    Bonjour.

    +Je ne vais pas revenir sur l’excellence de ces articles qui en moins d’une heure permettent de passer de zéro à une exploitation en réel, bien que des remerciements encore une fois renouvelés ne soient pas de trop !

    +Mon propos concerne une difficulté à programmer des décodeurs Uhlenbrock du type 73400 (sans doute vieux) alors que j’arrive dans les mêmes conditions à programmer les décodeurs Laisdcc achetés récemment et Trix (DH14 MTC14).

    +Le symptôme : Sur la voie de programmation, lecture de toutes les CV parfaites et petites saccades sur les locomotives ; donc OK.
    +Mais impossible de modifier une seule CV des décodeurs Uhlenbrock : "Erreur, pas de réponse".
    +Sur la voie normale, tous les décodeurs fonctionnent seuls ou en groupe au détail près : les Uhlenbrock sont restés a l’adresse 3 !

    +A l’oscilloscope, j’ai noté que les "1" font 2x62µs et les "0" font 2x106µs. Soit pour les deux un peu plus que la norme ne l’exige. Le signal est très propre (parfaitement carré) et l’amplitude crête à crête est exactement à 22V (je l’ai finalement légèrement baissée pour la ramener à 22V pour ne pas trop dépasser les valeurs dont j’avais l’habitude en N).
    +J’ai vérifié que le signal de retour de la mesure de courant était lisible à l’entrée de l’Arduino (tout est OK et surtout, il n’y a pas de différence visible entre les décodeurs qui fonctionnent et ceux qui refusent la programmation).
    +Pour information j’utilise la solution Arduino/DCC++/Ethernet/JMRI/LMD18200/MAX471/2voies.

    +J’ai aussi tenté tous les remèdes de grand-mère les résistances en série, les condensateurs, les différents niveaux de tension de la voie, etc.
    +Je soupçonne que le problème soit ailleurs (pour ne pas dire " que la vérité est ailleurs") !

    +1) Est-il possible d’ajuster les créneaux du signal DCC (si oui, où et comment) ? Est-ce important ou y a-t-t il une tolérance (je n’ai rien trouvé sur cette tolérance applicable au signal DCC dans les normes) ?

    +2) Que faire pour solutionner ce problème de programmation avec les Ulhenbrock (sachant que la lecture est quand même OK) ; j’ai bien lu cette discussion mais il n’y a pas de solution explicite (ou qui me soit compréhensible) :
    +http://forum.locoduino.org/index.php?topic=661.0

    +Si une âme charitable bien que confinée pouvait m’aider ?
    +Avec mes vifs remerciements.
    +Quidam.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Bonjour l’équipe,

    +

    Premièrement, je tiens à vous remercier quant à la mine d’informations et la clarté des explications.

    +

    Pour ma part, je débute en trains RC (avion, hélcio, voitres... c’est ok...).
    +Bref, J’ai eu la chance d’avoir récupéré une Bachmann 4-4-0 de 2004 en échelle G. Elle est bien évidemment en DC et non DCC.

    +

    Aujourd’hui, mon projet est de la passer en DCC avec un decoder dans la loco et de monter un controler DCC/DCC++ pour faire rouler la bête sur une boucle de 6 mètres avec 4 servos

    +

    Je pensais prendre pour l’alimentation : une centrale DCC d’occasion et pour la loco un ESU LokSound V5 XL (decoder avec son , lumiere, fumée et servos)

    +

    Cela fait 10 jours que mon cœur balance et il a fallut que je tombe sur votre site hier.... Bim une solution controler avec Bluetooh - le tout très accessible (€€€).

    +

    Ma question se porte sur la partie contrôler et mon usage très limité

    +

    Est-ce que cela peut convenir à mon besoin :
    +1 Arduino UNO alimenté par le Motor shield Rev3 (soudure vin-connect désolidarisé)+ 1 module Bluetooth HC-06 connecté
    +1 Arduino Motor Shield Rev 3 + adaptateur de 12V

    +

    ou
    +1 Arduino Nano (+ HC-06) et alimenté via un régulateur de tension par le LMD18200
    +... voire une MEGA...

    +

    Merci de votre support.

    +

    Alexis

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Bonjour, si vous débutez, vous pourriez commencer par un projet raisonnable, testé et bien décrit comme il en figure un sur le forum. (où il vaut mieux poster, rubrique projets, les échanges de fichiers et photos y sont possibles)
    +Parallèlement vous pourrez vous familiariser avec le DCC, qui est d’un abord plus compliqué et peut-être moins adapté au G.
    +Le LMD18200 est un pont en H pour piloter un moteur. Un régulateur de tension fournit une tension continue stable et filtrée.

    + +

    Voir en ligne : LGB sur le forum

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 15 décembre 2020 20:28, par valentin92 +
    +
    +

    Bonjour,
    +Je suis débutant en DCC, et j’ai réalisé la centrale avec un arduino mega (made in china), un motor shield (deek-robot). Mon décodeur est un ESU lokpilot V.4.
    +Lorsque j’entre <1> sur le moniteur les 4 diodes du motor shield s’allument, et s’éteignent lorsque j’entre <0>.
    +Par contre, après avoir entré <1>, quand j’entre une commande de traction la loco ne bouge pas et le moniteur renvoie toujours -1. Quelle en est la raison ?
    +(il manquait un passage dans mon mail)
    +Merci d’avance pour votre réponse

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 15 décembre 2020 21:35, par msport +
    +
    +

    Bonsoir,

    +

    connaissez vous l’adresse du décodeur que vous utilisez ? (par défaut 03)
    +Pour cela, via le moniteur série pour la voie de programmation : <R 1 123 123>
    +Pour un test envoyez <t 1 03 20 1> pour la voie principale (loco 03, en avant, vitesse 20).

    + +

    Voir en ligne : Liste des commandes DCC++ (au milieu)

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC (3) + 16 décembre 2020 09:05, par valentin92 +
    +
    +

    Bonjour et merci pour votre réponse rapide,
    +Le décodeur est neuf et son adresse est bien 03 d’après la doc.
    +Lorsque j’envoie <R 1 123 123>, il me retourne <r123|123| -1> quelle que soit la voie.
    +Lorsque j’envoie <t 1 03 20 1>, il me retourne <T1 20 1> quelle que soit la voie, mais rien ne bouge.
    +J’ai lu quelque part dans les articles Locoduino qu’il n’y avait pas de problèmes de reconnaissance des décodeurs ESU, ce qui ne semble pas être ici le cas. Alors une question vient tout naturellement : le décodeur ne serait-il pas HS ? Quel moyen permettrait de vérifier son état ?
    +Merci encore de votre aide
    +Bien cordialement

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 16 décembre 2020 10:07, par msport +
      +
      +

      Bonjour,
      +c’est la centrale qui retourne <T1 20 1> .
      +A partir de là, je vérifierais la loco, tourne-t-elle en courant continu ? Les décodeurs sont compatibles.
      +On peut faire un montage sur table avec un petit moteur de lecteur de CD (5V, ok)
      +Éventuellement un reset avec le CV 8 (voir la doc)

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 16 décembre 2020 17:49, par valentin92 +
        +
        +

        J’ai fait un montage sur table avec un petit moteur : mêmes résultats.
        +Par contre je ne vois pas comment faire un reset puisque je ne peux pas lire ni modifier les CV.
        +Cordialement

        + + + + + + +

        Répondre

        +
        +
        + + +
          + + +
        • +
          +
          + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 16 décembre 2020 21:37, par msport +
          +
          +

          Vous ne nous dites pas si la loco tourne avec du continu (avec une simple pile 9V).

          +

          Il se peut que votre décodeur soit verrouillé. Si c’est le cas, il faut écrire 0 dans la CV 124.

          +

          Au fait, que dit votre centrale sur le serial quand elle démarre ?

          + +

          Voir en ligne : Piste modification du CV 124 qui permettrait de verrouiller le décodeur

          + + + + +

          Répondre

          +
          +
          + + +
            + + +
          • +
            +
            + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 17 décembre 2020 18:12, par valentin92 +
            +
            +

            Bonsoir,
            +La loco ne fonctionne pas en continu (via le décodeur).
            +Le décodeur ne répondant pas (renvois de -1 sur les commandes), je ne peux ni lire ni modifier les CV ; je ne vois donc pas comment faire un reset ni un éventuel déverrouillage.
            +Au démarrage, la centrale renvoie <i DCC++ Base Station for Arduino Mega / Arduino Motor Shield : V-1.2.1+ / date et heure><NO: SERIAL>, ce qui parait normal.
            +Merci pour votre aide

            + + + + + + +

            Répondre

            +
            +
            + + +
              + + +
            • +
              +
              + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 17 décembre 2020 18:37, par msport +
              +
              +

              Il faut envoyer en aveugle <W 124 0 123 123> sur la voie de programmation.

              +

              Et en prime <W 8 8 123 123> = reset

              + + + + + + +

              Répondre

              +
              +
              + + +
                + + +
              • +
                +
                + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 17 décembre 2020 19:48, par valentin92 +
                +
                +

                Les 2 commandes renvoient -1, et rien ne change pour les autres commandes ; à part l’essayer sur un réseau dont le fonctionnement est sûr, je ne vois pas quoi faire d’autre.
                +Merci encore pour votre aide, et bonne soirée

                + + + + + + +

                Répondre

                +
                +
                + + +
                  + + +
                • +
                  +
                  + + Réalisation de centrales DCC avec le logiciel libre DCC (3) + + 17 décembre 2020 20:01, par msport +
                  +
                  +

                  Et réciproquement, testez votre centrale avec une loco qui fonctionne ailleurs ... car pour l’instant c’est votre décodeur qui présente des anomalies. (surtout pour un neuf). Vous ne nous avez pas dit si la loco était pré-équipée et sinon si vous avez bien testé l’isolation entre rails et moteur avant installation.

                  + + + + + + +

                  Répondre

                  +
                  +
                  + + + +
                • + + +
                + + +
              • + + +
              + + +
            • + + +
            + + +
          • + + +
          + + +
        • + + +
        + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 16 décembre 2020 09:41, par bobyAndCo +
    +
    +

    Surtout si les leds sur la carte sont allumées et que DCC++ a répondu à la suite de l’envoi de la commande <1>, cela doit fonctionner.

    +

     

    +

    Il ne faut pas avoir trop peur quant à savoir si le décodeur est HS. C’est le plus souvent le résultat d’une sur tension importante et ça fume (et ça sent le cramé) !

    +

     

    +

    Je ne sais pas s’il ne vaut pas mieux écrire <t 1 3 20 1> plutôt que <t 1 03 20 1> ?

    +

     

    +

    Pour la lecture, sur la voie principale, cela devrait fonctionner. Il faut aussi essayer les commandes de fonction pour les feux ou le son par exemple. Il faut aussi essayer <s> pour avoir le statut du programme.

    +

     

    +

    Pour la programmation et pour ce qui est des décodeurs ESU, je n’avais jamais eu de problème jusqu’à récemment. Mais sans avoir eu le temps de pousser plus en avant mes recherches, j’ai rencontré des difficultés avec les versions « micro » en particulier une version récente V5 (mais aussi V4 micro…) Tout n’est donc sans doute pas parfait dans le monde ESU !

    +

     

    +

    Eh bien pour vérifier, il faut sans doute mettre la locomotive sur un réseau disposant d’une station dont le fonctionnement est assuré, un ami, un revendeur ou un club. Vous êtes dans quelle région ?

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 16 décembre 2020 18:06, par valentin92 +
      +
      +

      Merci pour votre réponse.
      +J’ai essayé les 2 formes d’écriture pour l’adresse du décodeur (3 et 03) : pas de changement.
      +Lorsque j’envoie <s> il me retourne <X><X> : est-ce normal ?
      +J’ai essayé <f 03 144> pour allumer les feux, rien ne s’allume et pas de réponse (ce dernier point étant normal).
      +Un montage sur table donne les mêmes résultats sur les 2 voies.
      +Je suis dans le 77, près de St Fargeau Ponthierry.
      +Merci pour votre aide
      +Cordialement

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 18 décembre 2020 21:44, par Xavier +
    +
    +

    Bonjour

    +

    Est-ce que DCC++ EX peut etre installe sur l’arduino Mega avec les 2 x LM18200, en lieu et place de DCC++ "standard" ?

    +

    Merci

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 19 décembre 2020 17:53, par Jerome +
      +
      +

      Bonjour

      +

      Il me semble que la dernière version de la bibliothèque DCCpp développé sur ce site par Thierry comprend une partie de DCC++EX.
      +Cela concerne entre autre les améliorations pour la compatibilité avec les décodeurs récents.

      +

      Personnellement, j’ai adopté DCCpp, et je ne le regrette pas.
      +Mais Thierry et la lecture des articles de Locoduino pourront vous renseigner plus que je ne sais le faire.

      + + + + + + +

      Répondre

      +
      +
      + + + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 19 décembre 2020 21:07, par bobyAndCo +
    +
    +

     

    +

    +Pas besoin de DCC++EX pour faire du WiFi avec JMRI. J’avais présenté déjà à Orléans une DCC++ base station sur Mega avec un shield équipé d’un ESP8266. C’est la station du milieu que l’on voit sur la deuxième photo de ce post : https://forum.locoduino.org/index.p...

    +

     

    +

    +Si vous êtes intéressé, j’ai présenté en détail ce montage ici

    +

     

    +

    +C’est exactement le montage repris par DCC++EX

    +

     

    +

    +Mais les montages à base d’ESP8266 sont aujourd’hui dépassés. Je vous conseille l’ESP32... et donc quelque part la solution développée dans La Box.

    +

     

    +

    +Cependant, le premier montage avec un Mega fonctionne de la même manière avec un ESP32 à la place d’un ESP8266.

    +

     

    +

    +CQFD

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 21 décembre 2020 13:19, par Xavier +
    +
    +

    Je veux dire SANS avoir a utiliser JMRI....

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 21 décembre 2020 13:40, par bobyAndCo +
    +
    +

     

    +

    JMRI n’est qu’une des façons de piloter une Base Station, qu’elle soit DCC++, DCCpp ou DCC++EX.

    +

     

    +

    +Personnellement, je me suis fabriqué mon propre contrôleur sur Raspberry qui permet de piloter DCC++ avec un PC, une tablette et aussi avec un smartphone que je présente ici : https://www.locoduino.org/spip.php?...

    +

     

    +

    +Je montre aussi dans une vidéo comment je pilote DCC++ sur un Mega en passant par un ESP32 et WITHROTTLE sur mon smartphone (donc pas de PC) : https://youtu.be/YgPwCOJ9aEg

    +

     

    +

    +Sachez aussi que JMRI fonctionne très bien sur Raspberry.

    +

     

    +

    +Enfin, la Box de Locoduino offre encore pus de possibilités, avec ou sans PC.

    +

     

    +

    +Avec ou sans PC, vous n’avez que l’embarras du choix.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 22 décembre 2020 17:38, par Xavier +
      +
      +

      Oui, j’ai fait l’essai avec JMRI sur un Raspberry pour piloter une base station avec DCC++...pas de probleme
      +Mais apparemment avec DCC++ EX, on peut ajouter le wifi shield, et juste piloter directement depuis un smartphone (avec Engine Driver) sans passer par un PC ou un Raspberry.....

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 22 décembre 2020 21:43, par msport +
    +
    +

    Deux remarques :

    +

    1. Le projet LaBox permet justement de piloter un réseau en wifi avec EngineDriver (sans PC ou Raspberry).
    +2. BobyAndCo vous indique comment piloter DCC++ sur un Mega avec WiThrottle (sur smartphone, donc certainement aussi avec EngineDriver).

    +

    What else ?

    +

    Mais si vous évaluez DCC++ EX, postez vos essais sur le forum.

    + +

    Voir en ligne : Projet centrale "LaBox" wifi DCC++ Can

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 27 décembre 2020 09:36, par Pol +
    +
    +

    Bonjour,
    +Je suis un peu perdu dans tout ce que j’ai lu. Motor schield, Can Bus schield ? AC/DC ?
    +J’ai un Arduino mega et que dois-je acheté pour du 3 rails AC pour réaliser un schéma type "Avec un Arduino MEGA, voie principale et voie de programmation" ?
    +Schéma en image ?
    +Merci

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 27 décembre 2020 12:10, par bobyAndCo +
    +
    +

     

    +

    Tout d’abord, dans cet article, nous ne parlons pas de bus CAN. Voila qui va déja simplifier votre problème.

    +

     

    +

    Nous présentons ici des systèmes en DCC. Pour le 3 rails, vous pourez fonctionner en DCC à la condition que vos décodeurs acceptent ce standard. C’est le cas pour les locomotives depuis une dizaine d’années mais ne sera pas vrai pour des décodeurs plus anciens.

    +

     

    +

    Les décodeurs des locomotives Trix (2 rails) sont aujourd’hui identiques aux locomotives 3 rails de Marklin.

    +

     

    +

    Si donc vos décodeurs acceptent le standard DCC, tout ce qui est dit dans l’article au dessus est valable pour le 3 rails, vous placerez l’une des sorties DCC sur chacun des deux rails latéraux et la seconde sortie sur le rail central.

    +

     

    +

    Vous pouvez aussi réaliser la configuration suivante qui a été réaliser depuis la rédaction de cet article : https://www.locoduino.org/spip.php?...

    +

     

    +

    J’ai moi-même quelques locomotives en 3 rails que je pilote en DCC et ça fonctionne très bien.

    +

     

    +

    Si vos décodeurs n’acceptent pas le protocole DCC, alors ces stations DCC présentées ici ne pourront fonctionner. Ce n’est pas plus compliqué.

    +

     

    +

    Bien cordialement

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 23 janvier 2022 14:50, par Xavier +
    +
    +

    Bonjour
    +J’ai realise cette centrale avec 1 x Mega et 2 x LMD18200
    +Mais j’utilise DCC++EX et non pas DCC++ "standard"
    +Y a-t-il une difference entre les 2 sorties (Voie principale et Voie de programmation) au niveau du software ? Car apparemment, rien ne les differentie au niveau du cablage et hardware.....
    +Faut il modifier quelque chose dans le code pour que la voie de programmation soit une "vraie" voie de programmation ?
    +Merci

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 23 janvier 2022 16:58, par msport +
    +
    +

    Bonjour,
    +le code de DCC++EX n’a pas été élaboré sur ce site.
    +Vous devriez demander du support aux concepteurs.
    +Cordialement

    + +

    Voir en ligne : DCC++EX

    + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 23 janvier 2022 17:01, par bobyAndCo +
    +
    +

    Bonjour

    +

    Oui, DCC-Ex vous devez entrer un réglage spécial. Dans le fichier config.h il faut mettre ligne 28 et suivantes ceci :

    +
    //#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
    +
    +#define MY_MOTOR_SHIELD F("MY_MOTOR_SHIELD"),\
    +  new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
    +  new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
    +
    +#define MOTOR_SHIELD_TYPE MY_MOTOR_SHIELD
    +

    Pour les câblages, l’entrée PWM du LMD18200 voie main est sur 3 et le DIR sur 12. Pour la voie de programmation, le PWM en 11 et le DIR en 13

    +

    Amusez-vous bien !!!

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 24 janvier 2022 15:34, par Golebiarz Alfred +
    +
    +

    Bonjour. Retraité agé. Ce fameux décodeur décrit dans Elektor et Loco revue et par Sylvain Kuzcek (pour réseau alternatif(Marklin) ou 2 rails, s’accorde-t-il avec une centrale décrit sur cette page.
    +Cordialement.

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 24 janvier 2022 21:05, par msport +
      +
      +

      Bonsoir,
      +pouvez vous préciser ? Numéros, années ? Google qui d’habitude trouve des milliers de pages, reste sec sur ces mots.
      +Cordialement

      + + + + + + +

      Répondre

      +
      +
      + + +
        + + +
      • +
        +
        + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 24 janvier 2022 21:30, par msport +
        +
        +

        Ah, il ne s’agissait pas que d’un décodeur mais d’un système complet : Edits qui a eu son apogée dans les années 2000. Il utilisait le protocole Motorola.
        +L’arduino est apparu en 2005 et DCC++ en 2015 (DCC++ est un mode de génération du DCC, DCC qui a vu ses premiers décodeurs dès 1990).
        +Le protocole Motorola de Marklin et le protocole DCC sont différents.

        + + + + + + +

        Répondre

        +
        +
        + + + +
      • + + +
      + + +
    • + + +
    + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 25 janvier 2022 13:39, par Golebiarz Alfred +
    +
    +

    Bonjour tous. Je suis retraité et j’envoie ce mail le 25 janvier 2022. J’ai quelques machines que je voudrais éventuellement équiper en digital. Comme le fer à souder les composants ne m’est pas inconnu, c’est la raison pour laquelle j’ai posé la question de la centrale.
    +Je vous remercie de la diligence avec laquelle vous avez répondu à mo mail. Je tâtonne en ce moment pour décider quoi choisir.

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 23 mai 2022 17:04, par Auzereau alain +
    +
    +

    Bonjour
    +merci pour vos articles
    +je viens de terminer une dcc++ avec 2 lmd 18200 et max 471
    +qui fonctionne a merveille .
    +je souhaite metre driver moteur L9110S 10a.
    +POUVEZ VOUS m aider pour l’integrer dans le code (pour pouvoir depasser les 3A )
    +mon niveau est encore debutant pour ces adaptations.
    +Merçi de me consacrer de votre temps.
    +Aain

    + + + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 22 mars 2023 19:17, par De Riemacker +
    +
    +

    Bonjour
    +Bravo pour le site
    +Je voudrais savoir sur quel sortir de l’arduino on sait lire le signal carré à oscilloscope pour vérifier le signal
    +Merci

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    + +
    +

    Bonjour,
    +c’est la broche indiquée DIR dans les tableaux en début d’article. (dépend de votre carte)
    +Cordialement

    + + + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 janvier 2024 17:08, par Jozef +
    +
    +

    Bonjour, je déjà posé cette question sur le forum
    +Comment adapter les montages et les codes pour utiliser
    +Arduino R4 Wifi et une communication via Wifi avec Smartphone
    +Merci
    +Jozef

    + + + + + +

    Répondre

    +
    +
    + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 28 janvier 2024 18:56, par msport +
    +
    +

    Bonjour,
    +pouvez vous préciser ce que vous attendez d’un R4 Wifi par rapport à un ESP32 qui répond à votre besoin.
    +Cordialement

    + +

    Voir en ligne : LaBox, Une Centrale DCC polyvalente et abordable

    + + + +

    Répondre

    +
    +
    + + + + + +
  • + + +
  • +
    +
    + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + 13 octobre 2025 13:54, par KORDOS +
    +
    +

    Bonjour,
    +je suis en train de réaliser votre projet avec mon fils de 10 ans et j’ai une question.
    +J’utilise un motor shield Polulu et sur vos schémas vous indiquez qu’une tension d’alimentation continue pour trains HO de 16 à 18V est nécessaire.
    +Toutefois vous indiquez à un autre endroit : "Pololu précise bien que la tension d’alimentation de la carte devra respecter les préconisations pour l’Arduino, à savoir de préférence entre 7 et 12 Volts et en aucun cas dépasser 20 Volts".
    +J’ai donc un doute sur la tension à apporter eu shield motor pour piloter des trains HO : 12V ou 18 V ?
    +Merci pour vos articles et pour votre aide (on croise les doigts pour piloter notre locomotive analogique transformée en numérique via un décodeur lok pilot 5).
    +Etienne

    + + + + + +

    Répondre

    +
    +
    + + +
      + + +
    • +
      +
      + + Réalisation de centrales DCC avec le logiciel libre DCC++ (3) + + 16 octobre 2025 06:35, par bobyAndCo +
      +
      +

      Bonjour,

      +

      Je parle de deux tensions différentes. Une première, 7 à 12 volts est celle qui alimente l’électronique de la carte. Dans le cas de la Pololu, ce courant est en général "piqué" sur la carte Arduino par le principe de shield.

      +

      Le courant de 16 - 18 volts est celui qui sert à alimenter la voie, donc les locomotives.

      +

      Bien cordialement

      +

      Christophe

      + + + + + + +

      Répondre

      +
      +
      + + + +
    • + + +
    + + +
  • + + +

Réagissez à « Réalisation de centrales DCC avec le logiciel libre DCC++ (3) »

+ + + + + + + + + +
+ + + + +
+Qui êtes-vous ? + + +
+
+ + + +
+
+ + +
+
+ +
+
+ Votre message
+ + + +
+ + + +
+ + +
+ + +

Pour créer des paragraphes, laissez simplement des lignes vides.

+ +
+ +
+ +
+
+ Lien hypertexte +

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + + + + +

+ + +

+ +

+
+
+ + + + +
+
+ +
+
+ + +

Rubrique « Projets »

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Les derniers articles

+ + + + + + + + + + + + + + + + + + + + + + + +

Les articles les plus lus

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

À propos de LOCODUINO

+

LOCODUINO a pour objectif de montrer tout ce que l’on peut faire en DIY avec un Arduino en modélisme ferroviaire. LOCODUINO c’est d’abord un site communautaire où vous trouverez des articles consacrés à l’emploi de l’Arduino en modélisme ferroviaire mais aussi des articles pédagogiques sur les connaissances de base nécessaires : de l’électronique simple et de la programmation.

+ +

En savoir plusEn savoir plusPlus Mentions légalesMentions légalesMentions

+
+ + + diff --git a/DCC-Centrale/README.md b/DCC-Centrale/README.md new file mode 100644 index 0000000..ab167da --- /dev/null +++ b/DCC-Centrale/README.md @@ -0,0 +1,145 @@ +# DCC-Central + +DCC-Central consist in a complete system to drive DCC scale models. + +## Principles + +System is based on ESP32-S3 WROOM devKit, with some peripherals : + +- ILI9341 4.0" display module + To display what is happening +- Touch screen + To allow direct managing +- MCT2515 can interface + Used for accessory and feedback +- LM 18200 Hbridge (Drive track power / accessories ) + - DCC signals or DC - can be switched to program, + - second to send square signal - powers accessories +- ACS712 current sensor + Read DCC power send to track +- RAILCOM detector - input pin +- KY-040 rotary encoder + set speed / enventually help to navigate in menus +- relay + Swith 2 Rails / 3 rails + + +## Functionnalities + +DCC-Central is connected with CDM-Rail (protocol to choose), but can work alone. It will be RailCom compatible + +On display we must have : +- Select Autonomous / CDM-Rail driven switch +- Select DC / DCC type +- Select 2Rails / 3 Rails + +If autonomous and DCC +- Select Programming mode +- Select Loco ID +- Display Functions buttons +- Add a tab to display informations returned + + +### Running + +1. User choose a DCC-id to drive a loco with help of display +2. he selects type of signal DCC/DC (display) +3. track type ( 2rails / 3 rails) +4. Switch 'ON' track power + +System response + +- Switch relay ON (2 rails), OFF (3 rails) +- generate PWM (DC) / DCC signal (DCC) +- if DCC signal, set DCC loco id and drive DCC signal +- if DC signal, set DC (PWM) +- Power ON LM 18200 + +## Wiring + + + +## Wiring + +Below is a general guide on how to wire the main elements of the DCC-Central system, with suggested ESP32 GPIO numbers: + +- **ESP32-S3 WROOM devKit**: Serves as the main controller. All peripherals connect to its GPIO pins. + +- **ILI9341 3.2" Display Module**: Connect the SPI pins to the ESP32 as follows (suggested): + - MOSI: GPIO 23 + - MISO: GPIO 19 + - SCK: GPIO 18 + - CS: GPIO 5 + - DC: GPIO 21 + - RESET: GPIO 22 + Power the display with 3.3V or 5V as required by the module. + +- **Touch Screen**: If integrated with the display, connect the touch controller (often XPT2046) SPI pins as follows (suggested): + - T_MOSI: GPIO 13 + - T_MISO: GPIO 12 + - T_SCK: GPIO 14 + - T_CS: GPIO 15 + - T_IRQ: GPIO 27 + You may share SPI bus with the display if supported. Connect IRQ pin to a free GPIO. + +- **MCP2515 CAN Interface**: Connect SPI pins as follows (suggested): + - MOSI: GPIO 23 (shared with display) + - MISO: GPIO 19 (shared with display) + - SCK: GPIO 18 (shared with display) + - CS: GPIO 4 + - INT: GPIO 16 + Connect CANH and CANL to the CAN bus. Power with 3.3V or 5V as required. + +- **LM18200 H-Bridge**: Connect control pins as follows (suggested): + - IN1: GPIO 32 + - IN2: GPIO 33 + - PWM: GPIO 25 + Connect motor outputs to the track or accessories. Power the H-Bridge with the appropriate voltage for your track/accessories. + +- **ACS712 Current Sensor**: Connect VCC and GND to power, OUT to ESP32 GPIO 36 (analog input, VP). Place the sensor in series with the track power line. + +- **RailCom Detector**: Connect the detector output to ESP32 GPIO 26 (digital input). Follow the detector's datasheet for power and signal wiring. + +- **KY-040 Rotary Encoder**: Connect as follows (suggested): + - CLK: GPIO 34 + - DT: GPIO 35 + - SW: GPIO 17 (if used) + Use pull-up resistors if required. + +- **Relay**: Connect relay control pin to ESP32 GPIO 2. Use a transistor driver circuit if the relay requires more current than the ESP32 can provide. Connect relay contacts to switch between 2-rail and 3-rail track wiring. + +### ESP32 Pin Usage Summary + +| Peripheral | Signal | ESP32 GPIO | +|---------------------------|----------------|------------| +| ILI9341 Display | MOSI | 23 | +| | MISO | 19 | +| | SCK | 18 | +| | CS | 5 | +| | DC | 21 | +| | RESET | 22 | +| Touch Screen (XPT2046) | T_MOSI | 13 | +| | T_MISO | 12 | +| | T_SCK | 14 | +| | T_CS | 15 | +| | T_IRQ | 27 | +| MCP2515 CAN Interface | CS | 4 | +| | INT | 16 | +| LM18200 H-Bridge | IN1 | 32 | +| | IN2 | 33 | +| | PWM | 25 | +| ACS712 Current Sensor | OUT | 36 (VP) | +| RailCom Detector | OUT | 26 | +| KY-040 Rotary Encoder | CLK | 34 | +| | DT | 35 | +| | SW | 17 | +| Relay | CTRL | 2 | + + +**Tips:** +- Double-check voltage compatibility for each module. +- Use common ground for all modules. +- Keep power and signal wires as short as possible to reduce noise. +- Refer to each module's datasheet for exact pinouts and requirements. + +**NB** a PCB card will be designed with KiCad Software for easier assembly. \ No newline at end of file
+

LOCODUINO

+
+