From aa0a9437b739d7e190ef5f1901a387b4b4e16660 Mon Sep 17 00:00:00 2001 From: dguisado Date: Sat, 19 Mar 2016 18:31:21 +0100 Subject: [PATCH] Added files via upload --- BaseStation-1.2.1/DCC++ Arduino Sketch.pdf | Bin 0 -> 64439 bytes BaseStation-1.2.1/DCCpp_Uno/Accessories.cpp | 239 +++++++ BaseStation-1.2.1/DCCpp_Uno/Accessories.h | 39 ++ BaseStation-1.2.1/DCCpp_Uno/Comm.h | 14 + BaseStation-1.2.1/DCCpp_Uno/Config.h | 56 ++ .../DCCpp_Uno/CurrentMonitor.cpp | 70 ++ BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.h | 47 ++ ...ia en conflicto de maqueta 2016-03-04).ino | 649 ++++++++++++++++++ BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.h | 120 ++++ BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.ino | 625 +++++++++++++++++ BaseStation-1.2.1/DCCpp_Uno/EEStore.cpp | 83 +++ BaseStation-1.2.1/DCCpp_Uno/EEStore.h | 35 + BaseStation-1.2.1/DCCpp_Uno/Outputs.cpp | 256 +++++++ BaseStation-1.2.1/DCCpp_Uno/Outputs.h | 39 ++ .../DCCpp_Uno/PacketRegister.cpp | 476 +++++++++++++ BaseStation-1.2.1/DCCpp_Uno/PacketRegister.h | 64 ++ BaseStation-1.2.1/DCCpp_Uno/Sensor.cpp | 248 +++++++ BaseStation-1.2.1/DCCpp_Uno/Sensor.h | 41 ++ BaseStation-1.2.1/DCCpp_Uno/SerialCommand.cpp | 535 +++++++++++++++ BaseStation-1.2.1/DCCpp_Uno/SerialCommand.h | 31 + BaseStation-1.2.1/PinAssignment.xlsx | Bin 0 -> 10933 bytes BaseStation-1.2.1/README.md | 21 + comandos.txt | 13 + protocololoconet.txt | 58 ++ 24 files changed, 3759 insertions(+) create mode 100644 BaseStation-1.2.1/DCC++ Arduino Sketch.pdf create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Accessories.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Accessories.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Comm.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Config.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno (Copia en conflicto de maqueta 2016-03-04).ino create mode 100644 BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.ino create mode 100644 BaseStation-1.2.1/DCCpp_Uno/EEStore.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/EEStore.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Outputs.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Outputs.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/PacketRegister.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/PacketRegister.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Sensor.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/Sensor.h create mode 100644 BaseStation-1.2.1/DCCpp_Uno/SerialCommand.cpp create mode 100644 BaseStation-1.2.1/DCCpp_Uno/SerialCommand.h create mode 100644 BaseStation-1.2.1/PinAssignment.xlsx create mode 100644 BaseStation-1.2.1/README.md create mode 100644 comandos.txt create mode 100644 protocololoconet.txt diff --git a/BaseStation-1.2.1/DCC++ Arduino Sketch.pdf b/BaseStation-1.2.1/DCC++ Arduino Sketch.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ae83b4cb6ec9614fcefbad759cb3cc1d3696e9a8 GIT binary patch literal 64439 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 QfPp|ZR2mv_MG4gZ0j~|GI{*Lx literal 0 HcmV?d00001 diff --git a/BaseStation-1.2.1/DCCpp_Uno/Accessories.cpp b/BaseStation-1.2.1/DCCpp_Uno/Accessories.cpp new file mode 100644 index 0000000..69a3673 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Accessories.cpp @@ -0,0 +1,239 @@ +/********************************************************************** + +Accessories.cpp +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Accessories.h b/BaseStation-1.2.1/DCCpp_Uno/Accessories.h new file mode 100644 index 0000000..7ee7782 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Accessories.h @@ -0,0 +1,39 @@ +/********************************************************************** + +Accessories.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Comm.h b/BaseStation-1.2.1/DCCpp_Uno/Comm.h new file mode 100644 index 0000000..917b1ee --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Comm.h @@ -0,0 +1,14 @@ +/********************************************************************** + +Comm.h +COPYRIGHT (c) 2013-2015 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#if COMM_TYPE == 1 + #include ETHERNET_LIBRARY + extern EthernetServer INTERFACE; +#endif + diff --git a/BaseStation-1.2.1/DCCpp_Uno/Config.h b/BaseStation-1.2.1/DCCpp_Uno/Config.h new file mode 100644 index 0000000..8822f63 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Config.h @@ -0,0 +1,56 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2015 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 TYPE +// +// 0 = Built-in Serial Port +// 1 = Arduino Ethernet/SD Card Shield + +#define COMM_TYPE 0 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE NAME OF ETHERNET LIBRARY TO INCLUDE (DIFFERENT SHIELDS MAY USE THEIR OWN LIBRARIES) + +//#define ETHERNET_LIBRARY // https://github.com/Seeed-Studio/Ethernet_Shield_W5200 +#define ETHERNET_LIBRARY // https://github.com/arduino-org/Arduino + +///////////////////////////////////////////////////////////////////////////////////// +// +// 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/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.cpp b/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.cpp new file mode 100644 index 0000000..ffc7be7 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.cpp @@ -0,0 +1,70 @@ +/********************************************************************** + +CurrentMonitor.cpp +COPYRIGHT (c) 2013-2015 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; + globalPowerON=false; + } // 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) + setGlobalPower(EMERGENCY); + } +} // CurrentMonitor::check + +void CurrentMonitor::setGlobalPower(uint8_t pPower) +{ + if (pPower==ON) + { + digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); + digitalWrite(PWON_LED_PIN, HIGH); + digitalWrite(PWOFF_LED_PIN, LOW); + this->globalPowerON=true; + INTERFACE.println(""); + } + else if (pPower==OFF) + { + digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); + digitalWrite(PWON_LED_PIN, LOW); + digitalWrite(PWOFF_LED_PIN, LOW); + this->globalPowerON=false; + INTERFACE.println(""); + } + else if (pPower==EMERGENCY) + { + digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); + digitalWrite(PWON_LED_PIN, LOW); + digitalWrite(PWOFF_LED_PIN, HIGH); + this->globalPowerON=false; + INTERFACE.println(""); + } +} + +long int CurrentMonitor::sampleTime=0; +bool CurrentMonitor::globalPowerON=false; + diff --git a/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.h b/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.h new file mode 100644 index 0000000..a41e5af --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/CurrentMonitor.h @@ -0,0 +1,47 @@ +/********************************************************************** + +CurrentMonitor.h +COPYRIGHT (c) 2013-2015 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef CurrentMonitor_h +#define CurrentMonitor_h + +#include "Arduino.h" + +#define PWON_BUTTON_PIN 30 // power on push button +#define PWOFF_BUTTON_PIN 31 // power off push button +#define EMERGENCY_STOP_PIN 32 // external emergency stop +#define PWON_LED_PIN 33 // green led for POWER ON +#define PWOFF_LED_PIN 34 // red led for POWER OFF + +#define OFF 0 +#define ON 1 +#define EMERGENCY 2 + +#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; + static bool globalPowerON; + int pin; + float current; + char *msg; + CurrentMonitor(int, char *); + static boolean checkTime(); + void check(); + void setGlobalPower(uint8_t); +}; + +#endif + diff --git a/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno (Copia en conflicto de maqueta 2016-03-04).ino b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno (Copia en conflicto de maqueta 2016-03-04).ino new file mode 100644 index 0000000..61da759 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno (Copia en conflicto de maqueta 2016-03-04).ino @@ -0,0 +1,649 @@ +/********************************************************************** + +DCC++ BASE STATION +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++ 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 +#include "DCCpp_Uno.h" +#include "PacketRegister.h" +#include "CurrentMonitor.h" +#include "SerialCommand.h" +#include "Accessories.h" +#include "EEStore.h" +#include "Config.h" +#include "Comm.h" + +//DGS Loconet Slot table for locomotives +rwSlotDataMsg LnetSlots[MAX_MAIN_REGISTERS]; +bool globalPowerON=false; +lnMsg *LnPacket; + +// 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 + +void setGlobalPower(bool pON) +{ + if (pON) + { + digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); + globalPowerON=true; + INTERFACE.println(""); + } + else + { + digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); + digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); + globalPowerON=false; + INTERFACE.println(""); + } +} + +void processIncomingLoconetCommand() +{ + int n; int freeslot=MAX_MAIN_REGISTERS; + unsigned char opcode = (int)LnPacket->sz.command; + char s[20]; + + switch (opcode) + { + case OPC_GPON: //Global ON command + Serial.println("OPC_GPON"); + setGlobalPower(true); + break; + case OPC_GPOFF: // Global OFF command + Serial.println("OPC_GPOFF"); + setGlobalPower(false); + break; + case OPC_LOCO_ADR: // Request of Loco + //Check if it is in slot already and also gets the first possible free slot + //Slot 0 is not examined as it is used as BT2 slot (TODO not implemented) + Serial.println("OPC_LOCO_ADR"); + for (n=1;nla.adr_lo) break; + } + Serial.print("2 -");Serial.print(n);Serial.print(" ");Serial.println(freeslot); + //Loco not found and no free slots + if (n==MAX_MAIN_REGISTERS && freeslot==MAX_MAIN_REGISTERS) + { + Serial.println("Long Ack"); + LocoNet.sendLongAck(0); + break; + } + //Loco not found, add to the first free slot speed 0, direction front, F0 ON + if (n==MAX_MAIN_REGISTERS) + { + n=freeslot; + LnetSlots[n].command=0xE7; + LnetSlots[n].mesg_size=0x0E; + LnetSlots[n].slot=n; + LnetSlots[n].stat=LOCO_IDLE | DEC_MODE_128; + LnetSlots[n].adr=LnPacket->la.adr_lo; + LnetSlots[n].spd=0; + LnetSlots[n].dirf=DIRF_F0; + LnetSlots[n].trk &= GTRK_POWER & GTRK_MLOK1; // POWER ON & Loconet 1.1 by default + LnetSlots[n].ss2=0; + LnetSlots[n].adr2=0; + LnetSlots[n].snd=0; + LnetSlots[n].id1=0; + LnetSlots[n].id2=0; + } + LocoNet.send ((lnMsg*)&LnetSlots[n]); + break; + case OPC_MOVE_SLOTS: + Serial.println("OPC_MOVE_SLOTS"); + //Check slot range (0 DISPATCH NOT SUPPORTED, DIFFERENT NOT SUPPORTED) + if (LnPacket->sm.dest>=MAX_MAIN_REGISTERS || LnPacket->sm.src>=MAX_MAIN_REGISTERS || LnPacket->sm.dest!=LnPacket->sm.src || LnPacket->sm.dest<1 || LnPacket->sm.src<1) + { + LocoNet.sendLongAck(0); + return; + } + + LnetSlots[LnPacket->sm.dest].stat|=LOCO_IN_USE; + LocoNet.send ((lnMsg*)&LnetSlots[LnPacket->sm.dest]); + // + sprintf(s,"%d %d %d %d",LnPacket->sm.dest,LnetSlots[LnPacket->sm.dest].adr,LnetSlots[LnPacket->sm.dest].spd,(LnetSlots[LnPacket->ldf.slot].dirf&DIRF_DIR)>>5); + mainRegs.setThrottle(s); + break; + case OPC_SLOT_STAT1: + Serial.println("OPC_SLOT_STAT1"); + LnetSlots[LnPacket->ss.slot].stat = LnPacket->ss.stat; + // + //char s[20]; + //sprintf(s,"%d %d %d %d",LnPacket->ss.slot,LnetSlots[LnPacket->ss.slot].adr,LnetSlots[LnPacket->ss.slot].spd,LnetSlots[LnPacket->ss.slot].dirf); + //mainRegs.setThrottle(s); + break; + case OPC_LOCO_SPD: + Serial.println("OPC_LOCO_SPD"); + LnetSlots[LnPacket->lsp.slot].spd = LnPacket->lsp.spd; + // + sprintf(s,"%d %d %d %d",LnPacket->lsp.slot,LnetSlots[LnPacket->lsp.slot].adr,LnetSlots[LnPacket->lsp.slot].spd,LnetSlots[LnPacket->lsp.slot].dirf); + mainRegs.setThrottle(s); + break; + case OPC_LOCO_DIRF: + uint8_t mByte1; + + Serial.println("OPC_LOCO_DIRF"); + LnetSlots[LnPacket->ldf.slot].dirf = LnPacket->ldf.dirf; + + // + sprintf(s,"%d %d %d %d",LnPacket->ldf.slot,LnetSlots[LnPacket->ldf.slot].adr,LnetSlots[LnPacket->ldf.slot].spd,(LnetSlots[LnPacket->ldf.slot].dirf&DIRF_DIR)>>5); + Serial.println(s); + mainRegs.setThrottle(s); + + // + //To set functions F0-F4 on (=1) or off (=0): + //BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 + mByte1=LnPacket->ldf.dirf&(~DIRF_DIR); + mByte1+=128; + //mByte1=(mByte1<<3>>3) + 128; + sprintf(s,"%d %d",LnetSlots[LnPacket->ldf.slot].adr, mByte1); + Serial.println(s); + mainRegs.setFunction(s); + + break; + case OPC_LOCO_SND: + Serial.println("OPC_LOCO_SND"); + LnetSlots[LnPacket->ls.slot].snd = LnPacket->ls.snd; + // + //char s[20]; + //sprintf(s,"%d %d %d %d",LnPacket->ls.slot,LnetSlots[LnPacket->ls.slot].adr,LnetSlots[LnPacket->ls.slot].spd,LnetSlots[LnPacket->ls.slot].dirf); + //mainRegs.setThrottle(s); + break; + default: + // ignore the message... + Serial.print("RX: "); + uint8_t msgLen = getLnMsgSize(LnPacket); + for (uint8_t x = 0; x < msgLen; x++) + { + uint8_t val = LnPacket->data[x]; + // Print a leading 0 if less than 16 to make 2 HEX digits + if(val < 16) + Serial.print('0'); + + Serial.print(val, HEX); + Serial.print(' '); + } + Serial.println(" <"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MAIN ARDUINO LOOP +/////////////////////////////////////////////////////////////////////////////// + +void loop(){ + + // Check for any received LocoNet packets + LnPacket = LocoNet.receive(); + if (LnPacket) + processIncomingLoconetCommand(); + + 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(); + } + +} // loop + +/////////////////////////////////////////////////////////////////////////////// +// INITIAL SETUP +/////////////////////////////////////////////////////////////////////////////// + +void setup(){ + + LocoNet.init(47); + + 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 + + Serial.print(""); + + #if COMM_TYPE == 1 + Ethernet.begin(mac); // Start networking using DHCP to get an IP Address + 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 + + //DGS initialize slots to FREE + int n; + for (n=0;nactivePacket->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 + + +/////////////////////////////////////////////////////////////////////////////// + + + diff --git a/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.h b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.h new file mode 100644 index 0000000..cc0ffbd --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.h @@ -0,0 +1,120 @@ +/********************************************************************** + +DCCpp_Uno.h +COPYRIGHT (c) 2013-2015 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Config.h" + +#ifndef DCCpp_Uno_h +#define DCCpp_Uno_h + +///////////////////////////////////////////////////////////////////////////////////// +// 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 + +/*--------------------------------------------------------------- + * PINS D3, D8, D9, D11, D12, D13, A0, A1 used by Motor Shield + * PINS D47, D48 used by Loconet Shield + * PINS D50, D51, D52, D53, D10, D4 used by Ethernet Shield + ----------------------------------------------------------------*/ +#if 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 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_TYPE == 0 + + #define INTERFACE Serial + +#elif COMM_TYPE == 1 + + #define INTERFACE eServer + #define SDCARD_CS 4 + +#else + + #error CANNOT COMPILE - PLEASE SELECT A PROPER COMMUNICATIONS INTERFACE TYPE + +#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/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.ino b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.ino new file mode 100644 index 0000000..7a9366a --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/DCCpp_Uno.ino @@ -0,0 +1,625 @@ +/********************************************************************** + +DCC++ BASE STATION +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++ 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 +#include "DCCpp_Uno.h" +#include "PacketRegister.h" +#include "CurrentMonitor.h" +#include "SerialCommand.h" +#include "Accessories.h" +#include "EEStore.h" +#include "Config.h" +#include "Comm.h" + +//DGS Loconet Slot table for locomotives +rwSlotDataMsg LnetSlots[MAX_MAIN_REGISTERS]; +lnMsg *LnPacket; + +// 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 + + + +void processIncomingLoconetCommand() +{ + int n; int freeslot=MAX_MAIN_REGISTERS; + unsigned char opcode = (int)LnPacket->sz.command; + char s[20]; + + switch (opcode) + { + case OPC_GPON: //Global ON command + + break; + case OPC_GPOFF: // Global OFF command + + break; + case OPC_LOCO_ADR: // Request of Loco + //Check if it is in slot already and also gets the first possible free slot + //Slot 0 is not examined as it is used as BT2 slot (TODO not implemented) + + for (n=1;nla.adr_lo) break; + } + + //Loco not found and no free slots + if (n==MAX_MAIN_REGISTERS && freeslot==MAX_MAIN_REGISTERS) + { + LocoNet.sendLongAck(0); + break; + } + //Loco not found, add to the first free slot speed 0, direction front, F0 ON + if (n==MAX_MAIN_REGISTERS) + { + n=freeslot; + LnetSlots[n].command=0xE7; + LnetSlots[n].mesg_size=0x0E; + LnetSlots[n].slot=n; + LnetSlots[n].stat=LOCO_IDLE | DEC_MODE_128; + LnetSlots[n].adr=LnPacket->la.adr_lo; + LnetSlots[n].spd=0; + LnetSlots[n].dirf=DIRF_F0; + LnetSlots[n].trk &= GTRK_POWER & GTRK_MLOK1; // POWER ON & Loconet 1.1 by default + LnetSlots[n].ss2=0; + LnetSlots[n].adr2=0; + LnetSlots[n].snd=0; + LnetSlots[n].id1=0; + LnetSlots[n].id2=0; + } + LocoNet.send ((lnMsg*)&LnetSlots[n]); + break; + case OPC_MOVE_SLOTS: + //Check slot range (0 DISPATCH NOT SUPPORTED, DIFFERENT NOT SUPPORTED) + if (LnPacket->sm.dest>=MAX_MAIN_REGISTERS || LnPacket->sm.src>=MAX_MAIN_REGISTERS || LnPacket->sm.dest!=LnPacket->sm.src || LnPacket->sm.dest<1 || LnPacket->sm.src<1) + { + LocoNet.sendLongAck(0); + return; + } + + LnetSlots[LnPacket->sm.dest].stat|=LOCO_IN_USE; + LocoNet.send ((lnMsg*)&LnetSlots[LnPacket->sm.dest]); + // + sprintf(s,"%d %d %d %d",LnPacket->sm.dest,LnetSlots[LnPacket->sm.dest].adr,LnetSlots[LnPacket->sm.dest].spd,LnetSlots[LnPacket->sm.dest].dirf); + mainRegs.setThrottle(s); + break; + case OPC_SLOT_STAT1: + LnetSlots[LnPacket->ss.slot].stat = LnPacket->ss.stat; + // + //char s[20]; + //sprintf(s,"%d %d %d %d",LnPacket->ss.slot,LnetSlots[LnPacket->ss.slot].adr,LnetSlots[LnPacket->ss.slot].spd,LnetSlots[LnPacket->ss.slot].dirf); + //mainRegs.setThrottle(s); + break; + case OPC_LOCO_SPD: + LnetSlots[LnPacket->lsp.slot].spd = LnPacket->lsp.spd; + // + sprintf(s,"%d %d %d %d",LnPacket->lsp.slot,LnetSlots[LnPacket->lsp.slot].adr,LnetSlots[LnPacket->lsp.slot].spd,LnetSlots[LnPacket->lsp.slot].dirf); + mainRegs.setThrottle(s); + break; + case OPC_LOCO_DIRF: + LnetSlots[LnPacket->ldf.slot].dirf = LnPacket->ldf.dirf; + // + sprintf(s,"%d %d %d %d",LnPacket->ldf.slot,LnetSlots[LnPacket->ldf.slot].adr,LnetSlots[LnPacket->ldf.slot].spd,LnetSlots[LnPacket->ldf.slot].dirf); + mainRegs.setThrottle(s); + break; + case OPC_LOCO_SND: + LnetSlots[LnPacket->ls.slot].snd = LnPacket->ls.snd; + // + //char s[20]; + //sprintf(s,"%d %d %d %d",LnPacket->ls.slot,LnetSlots[LnPacket->ls.slot].adr,LnetSlots[LnPacket->ls.slot].spd,LnetSlots[LnPacket->ls.slot].dirf); + //mainRegs.setThrottle(s); + break; + default: + // ignore the message... + Serial.print("RX: "); + uint8_t msgLen = getLnMsgSize(LnPacket); + for (uint8_t x = 0; x < msgLen; x++) + { + uint8_t val = LnPacket->data[x]; + // Print a leading 0 if less than 16 to make 2 HEX digits + if(val < 16) + Serial.print('0'); + + Serial.print(val, HEX); + Serial.print(' '); + } + Serial.println(" <"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MAIN ARDUINO LOOP +/////////////////////////////////////////////////////////////////////////////// + +void loop(){ + + // Check for any received LocoNet packets + LnPacket = LocoNet.receive(); + if (LnPacket) + processIncomingLoconetCommand(); + + 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(); + } + + if (digitalRead(PWON_LED_PIN)==LOW) + mainMonitor.setGlobalPower(ON); + if (digitalRead(PWOFF_LED_PIN)==LOW) + mainMonitor.setGlobalPower(OFF); + if (digitalRead(EMERGENCY_STOP_PIN)==LOW) + mainMonitor.setGlobalPower(EMERGENCY); + +} // loop + +/////////////////////////////////////////////////////////////////////////////// +// INITIAL SETUP +/////////////////////////////////////////////////////////////////////////////// + +void setup(){ + + LocoNet.init(7); + + 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 + + Serial.print(""); + + #if COMM_TYPE == 1 + Ethernet.begin(mac); // Start networking using DHCP to get an IP Address + 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 + + //DGS initialize slots to FREE + int n; + for (n=0;nactivePacket->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 + + +/////////////////////////////////////////////////////////////////////////////// + + + diff --git a/BaseStation-1.2.1/DCCpp_Uno/EEStore.cpp b/BaseStation-1.2.1/DCCpp_Uno/EEStore.cpp new file mode 100644 index 0000000..6a08130 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/EEStore.cpp @@ -0,0 +1,83 @@ +/********************************************************************** + +EEStore.cpp +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/EEStore.h b/BaseStation-1.2.1/DCCpp_Uno/EEStore.h new file mode 100644 index 0000000..722b3c1 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/EEStore.h @@ -0,0 +1,35 @@ +/********************************************************************** + +EEStore.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Outputs.cpp b/BaseStation-1.2.1/DCCpp_Uno/Outputs.cpp new file mode 100644 index 0000000..aac4b4d --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Outputs.cpp @@ -0,0 +1,256 @@ +/********************************************************************** + +Outputs.cpp +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Outputs.h b/BaseStation-1.2.1/DCCpp_Uno/Outputs.h new file mode 100644 index 0000000..c15d62b --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Outputs.h @@ -0,0 +1,39 @@ +/********************************************************************** + +Outputs.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.cpp b/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.cpp new file mode 100644 index 0000000..8b68af6 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.cpp @@ -0,0 +1,476 @@ +/********************************************************************** + +PacketRegister.cpp +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.h b/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.h new file mode 100644 index 0000000..469e5ae --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/PacketRegister.h @@ -0,0 +1,64 @@ +/********************************************************************** + +PacketRegister.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Sensor.cpp b/BaseStation-1.2.1/DCCpp_Uno/Sensor.cpp new file mode 100644 index 0000000..ca47133 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Sensor.cpp @@ -0,0 +1,248 @@ +/********************************************************************** + +Sensor.cpp +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/Sensor.h b/BaseStation-1.2.1/DCCpp_Uno/Sensor.h new file mode 100644 index 0000000..62fd272 --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/Sensor.h @@ -0,0 +1,41 @@ +/********************************************************************** + +Sensor.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.cpp b/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.cpp new file mode 100644 index 0000000..7dcf4de --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.cpp @@ -0,0 +1,535 @@ +/********************************************************************** + +SerialCommand.cpp +COPYRIGHT (c) 2013-2015 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 TO NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES +/// PLEASE SEE SPECIFIC WANRINGS IN EACH COMMAND BELOW +/// + +/***** 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/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.h b/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.h new file mode 100644 index 0000000..b0a542f --- /dev/null +++ b/BaseStation-1.2.1/DCCpp_Uno/SerialCommand.h @@ -0,0 +1,31 @@ +/********************************************************************** + +SerialCommand.h +COPYRIGHT (c) 2013-2015 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/BaseStation-1.2.1/PinAssignment.xlsx b/BaseStation-1.2.1/PinAssignment.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2b9355ab87caa54ee39812502eeafa70416d2fd3 GIT binary patch literal 10933 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 +apagar +<0> +Registrar eh500 sin velocidad + + +encender f0 + +encender f0 y f2 + +apagar todas + diff --git a/protocololoconet.txt b/protocololoconet.txt new file mode 100644 index 0000000..cd278dc --- /dev/null +++ b/protocololoconet.txt @@ -0,0 +1,58 @@ +Loco 3 a velocidad 8 + +Locobuffer TCP started +Received a message of 4 Bytes from loconet, +RX: BF 00 03 43 +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:62 0x:66 +10 0x:20 0x:30 0x:30 0x:20 0x:30 0x:33 0x:20 0x:34 0x:33 0x:0D +20 0x:0A + +Received a message of 14 Bytes from loconet, +RX: E7 0E 01 23 03 00 00 07 00 00 00 00 00 30 +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:65 0x:37 +10 0x:20 0x:30 0x:65 0x:20 0x:30 0x:31 0x:20 0x:32 0x:33 0x:20 +20 0x:30 0x:33 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:30 +30 0x:37 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 +40 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:33 0x:30 0x:0D +50 0x:0A + +Received a message of 4 Bytes from loconet, +RX: BA 01 01 45 +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:62 0x:61 +10 0x:20 0x:30 0x:31 0x:20 0x:30 0x:31 0x:20 0x:34 0x:35 0x:0D +20 0x:0A + +Received a message of 14 Bytes from loconet, +RX: E7 0E 01 33 03 00 00 07 00 00 00 00 00 20 +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:65 0x:37 +10 0x:20 0x:30 0x:65 0x:20 0x:30 0x:31 0x:20 0x:33 0x:33 0x:20 +20 0x:30 0x:33 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:30 +30 0x:37 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 +40 0x:20 0x:30 0x:30 0x:20 0x:30 0x:30 0x:20 0x:32 0x:30 0x:0D +50 0x:0A + +Received a message of 4 Bytes from loconet, +RX: A0 01 0A 54 +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:61 0x:30 +10 0x:20 0x:30 0x:31 0x:20 0x:30 0x:61 0x:20 0x:35 0x:34 0x:0D +20 0x:0A + +Received a message of 4 Bytes from loconet, +RX: A1 01 30 6F +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:61 0x:31 +10 0x:20 0x:30 0x:31 0x:20 0x:33 0x:30 0x:20 0x:36 0x:66 0x:0D +20 0x:0A + +Received a message of 4 Bytes from loconet, +RX: A2 01 00 5C +Dumping TX TCP Packet + 0: 0x:52 0x:45 0x:43 0x:45 0x:49 0x:56 0x:45 0x:20 0x:61 0x:32 +10 0x:20 0x:30 0x:31 0x:20 0x:30 0x:30 0x:20 0x:35 0x:63 0x:0D +20 0x:0A +