├── DCC++ Arduino Sketch.pdf ├── DCCpp_Uno ├── Accessories.cpp ├── Accessories.h ├── Comm.h ├── Config.h ├── CurrentMonitor.cpp ├── CurrentMonitor.h ├── DCCpp_Uno.h ├── DCCpp_Uno.ino ├── EEStore.cpp ├── EEStore.h ├── Outputs.cpp ├── Outputs.h ├── PacketRegister.cpp ├── PacketRegister.h ├── Sensor.cpp ├── Sensor.h ├── SerialCommand.cpp └── SerialCommand.h └── README.md /DCC++ Arduino Sketch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DccPlusPlus/BaseStation/3c90408653175ef614d472723343d6bc310905d3/DCC++ Arduino Sketch.pdf -------------------------------------------------------------------------------- /DCCpp_Uno/Accessories.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Accessories.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | /********************************************************************** 10 | 11 | DCC++ BASE STATION can keep track of the direction of any turnout that is controlled 12 | by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories 13 | connected in this fashion, can always be operated using the DCC BASE STATION Accessory command: 14 | 15 | 16 | 17 | However, this general command simply sends the appropriate DCC instruction packet to the main tracks 18 | to operate connected accessories. It does not store or retain any information regarding the current 19 | status of that accessory. 20 | 21 | To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically 22 | invoke the required command as needed, first define/edit/delete such turnouts using the following 23 | variations of the "T" command: 24 | 25 | : creates a new turnout ID, with specified ADDRESS and SUBADDRESS 26 | if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS 27 | returns: if successful and if unsuccessful (e.g. out of memory) 28 | 29 | : deletes definition of turnout ID 30 | returns: if successful and if unsuccessful (e.g. ID does not exist) 31 | 32 | : lists all defined turnouts 33 | returns: for each defined turnout or if no turnouts defined 34 | 35 | where 36 | 37 | ID: the numeric ID (0-32767) of the turnout to control 38 | ADDRESS: the primary address of the decoder controlling this turnout (0-511) 39 | SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3) 40 | 41 | Once all turnouts have been properly defined, use the command to store their definitions to EEPROM. 42 | If you later make edits/additions/deletions to the turnout definitions, you must invoke the command if you want those 43 | new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. 44 | 45 | To "throw" turnouts that have been defined use: 46 | 47 | : sets turnout ID to either the "thrown" or "unthrown" position 48 | returns: , or if turnout ID does not exist 49 | 50 | where 51 | 52 | ID: the numeric ID (0-32767) of the turnout to control 53 | THROW: 0 (unthrown) or 1 (thrown) 54 | 55 | When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so 56 | that it is retained even without power. A list of the current directions of each Turnout in the form is generated 57 | by this sketch whenever the status command is invoked. This provides an efficient way of initializing 58 | the directions of any Turnouts being monitored or controlled by a separate interface or GUI program. 59 | 60 | **********************************************************************/ 61 | 62 | #include "Accessories.h" 63 | #include "SerialCommand.h" 64 | #include "DCCpp_Uno.h" 65 | #include "EEStore.h" 66 | #include 67 | #include "Comm.h" 68 | 69 | /////////////////////////////////////////////////////////////////////////////// 70 | 71 | void Turnout::activate(int s){ 72 | char c[20]; 73 | data.tStatus=(s>0); // if s>0 set turnout=ON, else if zero or negative set turnout=OFF 74 | sprintf(c,"a %d %d %d",data.address,data.subAddress,data.tStatus); 75 | SerialCommand::parse(c); 76 | if(num>0) 77 | EEPROM.put(num,data.tStatus); 78 | INTERFACE.print(""); 82 | else 83 | INTERFACE.print(" 1>"); 84 | } 85 | 86 | /////////////////////////////////////////////////////////////////////////////// 87 | 88 | Turnout* Turnout::get(int n){ 89 | Turnout *tt; 90 | for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout); 91 | return(tt); 92 | } 93 | /////////////////////////////////////////////////////////////////////////////// 94 | 95 | void Turnout::remove(int n){ 96 | Turnout *tt,*pp; 97 | 98 | for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout); 99 | 100 | if(tt==NULL){ 101 | INTERFACE.print(""); 102 | return; 103 | } 104 | 105 | if(tt==firstTurnout) 106 | firstTurnout=tt->nextTurnout; 107 | else 108 | pp->nextTurnout=tt->nextTurnout; 109 | 110 | free(tt); 111 | 112 | INTERFACE.print(""); 113 | } 114 | 115 | /////////////////////////////////////////////////////////////////////////////// 116 | 117 | void Turnout::show(int n){ 118 | Turnout *tt; 119 | 120 | if(firstTurnout==NULL){ 121 | INTERFACE.print(""); 122 | return; 123 | } 124 | 125 | for(tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){ 126 | INTERFACE.print("data.id); 128 | if(n==1){ 129 | INTERFACE.print(" "); 130 | INTERFACE.print(tt->data.address); 131 | INTERFACE.print(" "); 132 | INTERFACE.print(tt->data.subAddress); 133 | } 134 | if(tt->data.tStatus==0) 135 | INTERFACE.print(" 0>"); 136 | else 137 | INTERFACE.print(" 1>"); 138 | } 139 | } 140 | 141 | /////////////////////////////////////////////////////////////////////////////// 142 | 143 | void Turnout::parse(char *c){ 144 | int n,s,m; 145 | Turnout *t; 146 | 147 | switch(sscanf(c,"%d %d %d",&n,&s,&m)){ 148 | 149 | case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown) 150 | t=get(n); 151 | if(t!=NULL) 152 | t->activate(s); 153 | else 154 | INTERFACE.print(""); 155 | break; 156 | 157 | case 3: // argument is string with id number of turnout followed by an address and subAddress 158 | create(n,s,m,1); 159 | break; 160 | 161 | case 1: // argument is a string with id number only 162 | remove(n); 163 | break; 164 | 165 | case -1: // no arguments 166 | show(1); // verbose show 167 | break; 168 | } 169 | } 170 | 171 | /////////////////////////////////////////////////////////////////////////////// 172 | 173 | void Turnout::load(){ 174 | struct TurnoutData data; 175 | Turnout *tt; 176 | 177 | for(int i=0;idata.nTurnouts;i++){ 178 | EEPROM.get(EEStore::pointer(),data); 179 | tt=create(data.id,data.address,data.subAddress); 180 | tt->data.tStatus=data.tStatus; 181 | tt->num=EEStore::pointer(); 182 | EEStore::advance(sizeof(tt->data)); 183 | } 184 | } 185 | 186 | /////////////////////////////////////////////////////////////////////////////// 187 | 188 | void Turnout::store(){ 189 | Turnout *tt; 190 | 191 | tt=firstTurnout; 192 | EEStore::eeStore->data.nTurnouts=0; 193 | 194 | while(tt!=NULL){ 195 | tt->num=EEStore::pointer(); 196 | EEPROM.put(EEStore::pointer(),tt->data); 197 | EEStore::advance(sizeof(tt->data)); 198 | tt=tt->nextTurnout; 199 | EEStore::eeStore->data.nTurnouts++; 200 | } 201 | 202 | } 203 | /////////////////////////////////////////////////////////////////////////////// 204 | 205 | Turnout *Turnout::create(int id, int add, int subAdd, int v){ 206 | Turnout *tt; 207 | 208 | if(firstTurnout==NULL){ 209 | firstTurnout=(Turnout *)calloc(1,sizeof(Turnout)); 210 | tt=firstTurnout; 211 | } else if((tt=get(id))==NULL){ 212 | tt=firstTurnout; 213 | while(tt->nextTurnout!=NULL) 214 | tt=tt->nextTurnout; 215 | tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout)); 216 | tt=tt->nextTurnout; 217 | } 218 | 219 | if(tt==NULL){ // problem allocating memory 220 | if(v==1) 221 | INTERFACE.print(""); 222 | return(tt); 223 | } 224 | 225 | tt->data.id=id; 226 | tt->data.address=add; 227 | tt->data.subAddress=subAdd; 228 | tt->data.tStatus=0; 229 | if(v==1) 230 | INTERFACE.print(""); 231 | return(tt); 232 | 233 | } 234 | 235 | /////////////////////////////////////////////////////////////////////////////// 236 | 237 | Turnout *Turnout::firstTurnout=NULL; 238 | 239 | 240 | -------------------------------------------------------------------------------- /DCCpp_Uno/Accessories.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Accessories.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "Arduino.h" 11 | 12 | #ifndef Accessories_h 13 | #define Accessories_h 14 | 15 | struct TurnoutData { 16 | byte tStatus; 17 | byte subAddress; 18 | int id; 19 | int address; 20 | }; 21 | 22 | struct Turnout{ 23 | static Turnout *firstTurnout; 24 | int num; 25 | struct TurnoutData data; 26 | Turnout *nextTurnout; 27 | void activate(int s); 28 | static void parse(char *c); 29 | static Turnout* get(int); 30 | static void remove(int); 31 | static void load(); 32 | static void store(); 33 | static Turnout *create(int, int, int, int=0); 34 | static void show(int=0); 35 | }; // Turnout 36 | 37 | #endif 38 | 39 | 40 | -------------------------------------------------------------------------------- /DCCpp_Uno/Comm.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | /********************************************************************** 4 | 5 | Comm.h 6 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 7 | 8 | Part of DCC++ BASE STATION for the Arduino 9 | 10 | **********************************************************************/ 11 | 12 | #include "Config.h" 13 | 14 | #if COMM_TYPE == 1 // Ethernet Shield Card Selected 15 | 16 | #if COMM_INTERFACE == 1 17 | #define COMM_SHIELD_NAME "ARDUINO-CC ETHERNET SHIELD (WIZNET 5100)" 18 | #include // built-in Arduino.cc library 19 | 20 | #elif COMM_INTERFACE == 2 21 | #define COMM_SHIELD_NAME "ARDUINO-ORG ETHERNET-2 SHIELD (WIZNET 5500)" 22 | #include // https://github.com/arduino-org/Arduino 23 | 24 | #elif COMM_INTERFACE == 3 25 | #define COMM_SHIELD_NAME "SEEED STUDIO ETHERNET SHIELD (WIZNET 5200)" 26 | #include // https://github.com/Seeed-Studio/Ethernet_Shield_W5200 27 | 28 | #endif 29 | 30 | extern EthernetServer INTERFACE; 31 | #endif 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /DCCpp_Uno/Config.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Config.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | ///////////////////////////////////////////////////////////////////////////////////// 11 | // 12 | // DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: 13 | // 14 | // 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) 15 | // 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL) 16 | 17 | #define MOTOR_SHIELD_TYPE 0 18 | 19 | ///////////////////////////////////////////////////////////////////////////////////// 20 | // 21 | // DEFINE NUMBER OF MAIN TRACK REGISTER 22 | 23 | #define MAX_MAIN_REGISTERS 12 24 | 25 | ///////////////////////////////////////////////////////////////////////////////////// 26 | // 27 | // DEFINE COMMUNICATIONS INTERFACE 28 | // 29 | // 0 = Built-in Serial Port 30 | // 1 = Arduino.cc Ethernet/SD-Card Shield 31 | // 2 = Arduino.org Ethernet/SD-Card Shield 32 | // 3 = Seeed Studio Ethernet/SD-Card Shield W5200 33 | 34 | #define COMM_INTERFACE 0 35 | 36 | ///////////////////////////////////////////////////////////////////////////////////// 37 | // 38 | // DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP 39 | // 40 | 41 | //#define IP_ADDRESS { 192, 168, 1, 200 } 42 | 43 | ///////////////////////////////////////////////////////////////////////////////////// 44 | // 45 | // DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE 46 | // 47 | 48 | #define ETHERNET_PORT 2560 49 | 50 | ///////////////////////////////////////////////////////////////////////////////////// 51 | // 52 | // DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE 53 | // 54 | 55 | #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } 56 | 57 | ///////////////////////////////////////////////////////////////////////////////////// 58 | 59 | -------------------------------------------------------------------------------- /DCCpp_Uno/CurrentMonitor.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | CurrentMonitor.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "DCCpp_Uno.h" 11 | #include "CurrentMonitor.h" 12 | #include "Comm.h" 13 | 14 | /////////////////////////////////////////////////////////////////////////////// 15 | 16 | CurrentMonitor::CurrentMonitor(int pin, char *msg){ 17 | this->pin=pin; 18 | this->msg=msg; 19 | current=0; 20 | } // CurrentMonitor::CurrentMonitor 21 | 22 | boolean CurrentMonitor::checkTime(){ 23 | 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) 32 | digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); // disable both Motor Shield Channels 33 | digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); // regardless of which caused current overload 34 | INTERFACE.print(msg); // print corresponding error message 35 | } 36 | } // CurrentMonitor::check 37 | 38 | long int CurrentMonitor::sampleTime=0; 39 | 40 | -------------------------------------------------------------------------------- /DCCpp_Uno/CurrentMonitor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | CurrentMonitor.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #ifndef CurrentMonitor_h 11 | #define CurrentMonitor_h 12 | 13 | #include "Arduino.h" 14 | 15 | #define CURRENT_SAMPLE_SMOOTHING 0.01 16 | #define CURRENT_SAMPLE_MAX 300 17 | 18 | #ifdef ARDUINO_AVR_UNO // Configuration for UNO 19 | #define CURRENT_SAMPLE_TIME 10 20 | #else // Configuration for MEGA 21 | #define CURRENT_SAMPLE_TIME 1 22 | #endif 23 | 24 | struct CurrentMonitor{ 25 | static long int sampleTime; 26 | int pin; 27 | float current; 28 | char *msg; 29 | CurrentMonitor(int, char *); 30 | static boolean checkTime(); 31 | void check(); 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /DCCpp_Uno/DCCpp_Uno.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | DCCpp_Uno.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "Config.h" 11 | 12 | #ifndef DCCpp_Uno_h 13 | #define DCCpp_Uno_h 14 | 15 | ///////////////////////////////////////////////////////////////////////////////////// 16 | // RELEASE VERSION 17 | ///////////////////////////////////////////////////////////////////////////////////// 18 | 19 | #define VERSION "1.2.1+" 20 | 21 | ///////////////////////////////////////////////////////////////////////////////////// 22 | // AUTO-SELECT ARDUINO BOARD 23 | ///////////////////////////////////////////////////////////////////////////////////// 24 | 25 | #ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) 26 | #define ARDUINO_AVR_MEGA2560 27 | #endif 28 | 29 | #if defined ARDUINO_AVR_UNO 30 | 31 | #define ARDUINO_TYPE "UNO" 32 | 33 | #define DCC_SIGNAL_PIN_MAIN 10 // Ardunio Uno - uses OC1B 34 | #define DCC_SIGNAL_PIN_PROG 5 // Arduino Uno - uses OC0B 35 | 36 | #if COMM_INTERFACE != 0 // Serial was not selected 37 | 38 | #error CANNOT COMPILE - DCC++ FOR THE UNO CAN ONLY USE SERIAL COMMUNICATION - PLEASE SELECT THIS IN THE CONFIG FILE 39 | 40 | #endif 41 | 42 | #elif defined ARDUINO_AVR_MEGA2560 43 | 44 | #define ARDUINO_TYPE "MEGA" 45 | 46 | #define DCC_SIGNAL_PIN_MAIN 12 // Arduino Mega - uses OC1B 47 | #define DCC_SIGNAL_PIN_PROG 2 // Arduino Mega - uses OC3B 48 | 49 | #else 50 | 51 | #error CANNOT COMPILE - DCC++ ONLY WORKS WITH AN ARDUINO UNO OR AN ARDUINO MEGA 1280/2560 52 | 53 | #endif 54 | 55 | ///////////////////////////////////////////////////////////////////////////////////// 56 | // SELECT MOTOR SHIELD 57 | ///////////////////////////////////////////////////////////////////////////////////// 58 | 59 | #if MOTOR_SHIELD_TYPE == 0 60 | 61 | #define MOTOR_SHIELD_NAME "ARDUINO MOTOR SHIELD" 62 | 63 | #define SIGNAL_ENABLE_PIN_MAIN 3 64 | #define SIGNAL_ENABLE_PIN_PROG 11 65 | 66 | #define CURRENT_MONITOR_PIN_MAIN A0 67 | #define CURRENT_MONITOR_PIN_PROG A1 68 | 69 | #define DIRECTION_MOTOR_CHANNEL_PIN_A 12 70 | #define DIRECTION_MOTOR_CHANNEL_PIN_B 13 71 | 72 | #elif MOTOR_SHIELD_TYPE == 1 73 | 74 | #define MOTOR_SHIELD_NAME "POLOLU MC33926 MOTOR SHIELD" 75 | 76 | #define SIGNAL_ENABLE_PIN_MAIN 9 77 | #define SIGNAL_ENABLE_PIN_PROG 11 78 | 79 | #define CURRENT_MONITOR_PIN_MAIN A0 80 | #define CURRENT_MONITOR_PIN_PROG A1 81 | 82 | #define DIRECTION_MOTOR_CHANNEL_PIN_A 7 83 | #define DIRECTION_MOTOR_CHANNEL_PIN_B 8 84 | 85 | #else 86 | 87 | #error CANNOT COMPILE - PLEASE SELECT A PROPER MOTOR SHIELD TYPE 88 | 89 | #endif 90 | 91 | ///////////////////////////////////////////////////////////////////////////////////// 92 | // SELECT COMMUNICATION INTERACE 93 | ///////////////////////////////////////////////////////////////////////////////////// 94 | 95 | #if COMM_INTERFACE == 0 96 | 97 | #define COMM_TYPE 0 98 | #define INTERFACE Serial 99 | 100 | #elif (COMM_INTERFACE==1) || (COMM_INTERFACE==2) || (COMM_INTERFACE==3) 101 | 102 | #define COMM_TYPE 1 103 | #define INTERFACE eServer 104 | #define SDCARD_CS 4 105 | 106 | #else 107 | 108 | #error CANNOT COMPILE - Please select a proper value for COMM_INTERFACE in CONFIG.H file 109 | 110 | #endif 111 | 112 | ///////////////////////////////////////////////////////////////////////////////////// 113 | // SET WHETHER TO SHOW PACKETS - DIAGNOSTIC MODE ONLY 114 | ///////////////////////////////////////////////////////////////////////////////////// 115 | 116 | // If SHOW_PACKETS is set to 1, then for select main operations track commands that modify an internal DCC packet register, 117 | // if printFlag for that command is also set to 1, DCC++ BASE STATION will additionally return the 118 | // DCC packet contents of the modified register in the following format: 119 | 120 | // <* REG: B1 B2 ... Bn CSUM / REPEAT> 121 | // 122 | // REG: the number of the main operations track packet register that was modified 123 | // B1: the first hexidecimal byte of the DCC packet 124 | // B2: the second hexidecimal byte of the DCC packet 125 | // Bn: the nth hexidecimal byte of the DCC packet 126 | // CSUM: a checksum byte that is required to be the final byte in any DCC packet 127 | // REPEAT: the number of times the DCC packet was re-transmitted to the tracks after its iniital transmission 128 | 129 | #define SHOW_PACKETS 0 // set to zero to disable printing of every packet for select main operations track commands 130 | 131 | ///////////////////////////////////////////////////////////////////////////////////// 132 | 133 | #endif 134 | 135 | 136 | -------------------------------------------------------------------------------- /DCCpp_Uno/DCCpp_Uno.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | DCC++ BASE STATION 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see http://www.gnu.org/licenses 18 | 19 | **********************************************************************/ 20 | /********************************************************************** 21 | 22 | DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega 23 | using the Arduino IDE 1.6.6. 24 | 25 | It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others) 26 | to be used as a fully-functioning digital command and control (DCC) base station 27 | for controlling model train layouts that conform to current National Model 28 | Railroad Association (NMRA) DCC standards. 29 | 30 | This version of DCC++ BASE STATION supports: 31 | 32 | * 2-byte and 4-byte locomotive addressing 33 | * Simultaneous control of multiple locomotives 34 | * 128-step speed throttling 35 | * Cab functions F0-F28 36 | * Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses 37 | - includes optional functionailty to monitor and store of the direction of any connected turnouts 38 | * Programming on the Main Operations Track 39 | - write configuration variable bytes 40 | - set/clear specific configuration variable bits 41 | * Programming on the Programming Track 42 | - write configuration variable bytes 43 | - set/clear specific configuration variable bits 44 | - read configuration variable bytes 45 | 46 | DCC++ BASE STATION is controlled with simple text commands received via 47 | the Arduino's serial interface. Users can type these commands directly 48 | into the Arduino IDE Serial Monitor, or can send such commands from another 49 | device or computer program. 50 | 51 | When compiled for the Arduino Mega, an Ethernet Shield can be used for network 52 | communications instead of using serial communications. 53 | 54 | DCC++ CONTROLLER, available separately under a similar open-source 55 | license, is a Java program written using the Processing library and Processing IDE 56 | that provides a complete and configurable graphic interface to control model train layouts 57 | via the DCC++ BASE STATION. 58 | 59 | With the exception of a standard 15V power supply that can be purchased in 60 | any electronics store, no additional hardware is required. 61 | 62 | Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or 63 | commercial hardware, software, interfaces, specifications, or methods related 64 | to the control of model trains using NMRA DCC standards. Both programs are wholly 65 | original, developed by the author, and are not derived from any known commercial, 66 | free, or open-source model railroad control packages by any other parties. 67 | 68 | However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and 69 | embedded libraries associated with Arduino and Processing. Tremendous thanks to those 70 | responsible for these terrific open-source initiatives that enable programs like 71 | DCC++ to be developed and distributed in the same fashion. 72 | 73 | REFERENCES: 74 | 75 | NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices 76 | Arduino: http://www.arduino.cc/ 77 | Processing: http://processing.org/ 78 | GNU General Public License: http://opensource.org/licenses/GPL-3.0 79 | 80 | BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION: 81 | 82 | DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0, 83 | and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V 84 | unipolar signals that each properly encode zero and one bits conforming with 85 | DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B. 86 | 87 | Series of DCC bit streams are bundled into Packets that each form the basis of 88 | a standard DCC instruction. Packets are stored in Packet Registers that contain 89 | methods for updating and queuing according to text commands sent by the user 90 | (or another program) over the serial interface. There is one set of registers that controls 91 | the main operations track and one that controls the programming track. 92 | 93 | For the main operations track, packets to store cab throttle settings are stored in 94 | registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp_Uno.h). 95 | It is generally considered good practice to continuously send throttle control packets 96 | to every cab so that if an engine should momentarily lose electrical connectivity with the tracks, 97 | it will very quickly receive another throttle control signal as soon as connectivity is 98 | restored (such as when a trin passes over rough portion of track or the frog of a turnout). 99 | 100 | DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter 101 | that has been loaded with a throttle control setting for a given cab. For each register, it 102 | transmits the appropriate DCC packet bits to the track, then moves onto the next register 103 | without any pausing to ensure continuous bi-polar power is being provided to the tracks. 104 | Updates to the throttle setting stored in any given packet register are done in a double-buffered 105 | fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits 106 | can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal. 107 | The cabs identified in each stored throttle setting should be unique across registers. If two registers 108 | contain throttle setting for the same cab, the throttle in the engine will oscillate between the two, 109 | which is probably not a desireable outcome. 110 | 111 | For both the main operations track and the programming track there is also a special packet register with id=0 112 | that is used to store all other DCC packets that do not require continious transmittal to the tracks. 113 | This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables. 114 | It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure 115 | proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet 116 | and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself. 117 | Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of 118 | verifying proper transmittal before acting on the instructions contained in those packets 119 | 120 | An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached 121 | on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals 122 | produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals. 123 | 124 | This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10) 125 | to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect 126 | the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13). 127 | 128 | For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the 129 | Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2) 130 | to the Motor Shield's DIRECTION B input (pin 13). 131 | 132 | Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp_Uno.h for details). 133 | 134 | When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be 135 | connected directly to the tracks. This software assumes CHANNEL A is connected 136 | to the Main Operations Track, and CHANNEL B is connected to the Programming Track. 137 | 138 | DCC++ BASE STATION in split into multiple modules, each with its own header file: 139 | 140 | DCCpp_Uno: declares required global objects and contains initial Arduino setup() 141 | and Arduino loop() functions, as well as interrput code for OC0B and OC1B. 142 | Also includes declarations of optional array of Turn-Outs and optional array of Sensors 143 | 144 | SerialCommand: contains methods to read and interpret text commands from the serial line, 145 | process those instructions, and, if necessary call appropriate Packet RegisterList methods 146 | to update either the Main Track or Programming Track Packet Registers 147 | 148 | PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions 149 | 150 | CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and 151 | CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload 152 | is detected 153 | 154 | Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled 155 | by a DCC stationary accessory decoder. 156 | 157 | Sensor: contains methods to monitor and report on the status of optionally-defined infrared 158 | sensors embedded in the Main Track and connected to various pins on the Arudino Uno 159 | 160 | Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use 161 | 162 | EEStore: contains methods to store, update, and load various DCC settings and status 163 | (e.g. the states of all defined turnouts) in the EEPROM for recall after power-up 164 | 165 | DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters 166 | 167 | **********************************************************************/ 168 | 169 | // BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE 170 | 171 | #include "DCCpp_Uno.h" 172 | #include "PacketRegister.h" 173 | #include "CurrentMonitor.h" 174 | #include "Sensor.h" 175 | #include "SerialCommand.h" 176 | #include "Accessories.h" 177 | #include "EEStore.h" 178 | #include "Config.h" 179 | #include "Comm.h" 180 | 181 | void showConfiguration(); 182 | 183 | // SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE 184 | 185 | #if COMM_TYPE == 1 186 | byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server) 187 | EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer 188 | #endif 189 | 190 | // NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS. 191 | // NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES 192 | 193 | volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets 194 | volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets 195 | 196 | CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track 197 | CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track 198 | 199 | /////////////////////////////////////////////////////////////////////////////// 200 | // MAIN ARDUINO LOOP 201 | /////////////////////////////////////////////////////////////////////////////// 202 | 203 | void loop(){ 204 | 205 | SerialCommand::process(); // check for, and process, and new serial commands 206 | 207 | if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks 208 | mainMonitor.check(); 209 | progMonitor.check(); 210 | } 211 | 212 | Sensor::check(); // check sensors for activate/de-activate 213 | 214 | } // loop 215 | 216 | /////////////////////////////////////////////////////////////////////////////// 217 | // INITIAL SETUP 218 | /////////////////////////////////////////////////////////////////////////////// 219 | 220 | void setup(){ 221 | 222 | Serial.begin(115200); // configure serial interface 223 | Serial.flush(); 224 | 225 | #ifdef SDCARD_CS 226 | pinMode(SDCARD_CS,OUTPUT); 227 | digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card 228 | #endif 229 | 230 | EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM 231 | 232 | pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt 233 | digitalWrite(A5,HIGH); 234 | if(!digitalRead(A5)) 235 | showConfiguration(); 236 | 237 | Serial.print(""); 248 | 249 | #if COMM_TYPE == 1 250 | #ifdef IP_ADDRESS 251 | Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address 252 | #else 253 | Ethernet.begin(mac); // Start networking using DHCP to get an IP Address 254 | #endif 255 | INTERFACE.begin(); 256 | #endif 257 | 258 | SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line 259 | 260 | Serial.print(""); 266 | #elif COMM_TYPE == 1 267 | Serial.print(Ethernet.localIP()); 268 | Serial.print(">"); 269 | #endif 270 | 271 | // CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS 272 | 273 | // Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK 274 | // Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin 275 | // Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency 276 | // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle 277 | 278 | #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199 279 | #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599 280 | 281 | #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855 282 | #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927 283 | 284 | pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) 285 | digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW); 286 | 287 | pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A 288 | 289 | bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A 290 | bitSet(TCCR1A,WGM11); 291 | bitSet(TCCR1B,WGM12); 292 | bitSet(TCCR1B,WGM13); 293 | 294 | bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary) 295 | bitSet(TCCR1A,COM1B0); 296 | 297 | bitClear(TCCR1B,CS12); // set Timer 1 prescale=1 298 | bitClear(TCCR1B,CS11); 299 | bitSet(TCCR1B,CS10); 300 | 301 | OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1; 302 | OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1; 303 | 304 | pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A 305 | 306 | mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 307 | 308 | bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B) 309 | 310 | // CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS 311 | 312 | #ifdef ARDUINO_AVR_UNO // Configuration for UNO 313 | 314 | // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK 315 | // Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin 316 | // Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency 317 | // 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 318 | 319 | #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49 320 | #define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24 321 | 322 | #define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28 323 | #define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14 324 | 325 | pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) 326 | digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); 327 | 328 | pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B 329 | 330 | bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A 331 | bitSet(TCCR0A,WGM01); 332 | bitSet(TCCR0B,WGM02); 333 | 334 | bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary) 335 | bitSet(TCCR0A,COM0B0); 336 | 337 | bitClear(TCCR0B,CS02); // set Timer 0 prescale=64 338 | bitSet(TCCR0B,CS01); 339 | bitSet(TCCR0B,CS00); 340 | 341 | OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0; 342 | OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0; 343 | 344 | pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B 345 | 346 | progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 347 | 348 | bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B) 349 | 350 | #else // Configuration for MEGA 351 | 352 | // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK 353 | // Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin 354 | // Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency 355 | // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle 356 | 357 | #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199 358 | #define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599 359 | 360 | #define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855 361 | #define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927 362 | 363 | pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) 364 | digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); 365 | 366 | pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B 367 | 368 | bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A 369 | bitSet(TCCR3A,WGM31); 370 | bitSet(TCCR3B,WGM32); 371 | bitSet(TCCR3B,WGM33); 372 | 373 | bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary) 374 | bitSet(TCCR3A,COM3B0); 375 | 376 | bitClear(TCCR3B,CS32); // set Timer 3 prescale=1 377 | bitClear(TCCR3B,CS31); 378 | bitSet(TCCR3B,CS30); 379 | 380 | OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3; 381 | OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3; 382 | 383 | pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B 384 | 385 | progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 386 | 387 | bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B) 388 | 389 | #endif 390 | 391 | } // setup 392 | 393 | /////////////////////////////////////////////////////////////////////////////// 394 | // DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL 395 | /////////////////////////////////////////////////////////////////////////////// 396 | 397 | // The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1. 398 | // It is designed to read the current bit of the current register packet and 399 | // updates the OCNA and OCNB counters of Timer-N to values that will either produce 400 | // a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent 401 | // DCC ZERO and DCC ONE bits. 402 | 403 | // These are hardware-driven interrupts that will be called automatically when triggered regardless of what 404 | // DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled. 405 | // Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts 406 | // (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete 407 | // in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic 408 | // of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB 409 | // registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the 410 | // interrupt code completes and can be called again. 411 | 412 | // A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed 413 | // DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible. 414 | 415 | // Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the 416 | // Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro 417 | // that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating 418 | // some of the logic for each interrupt, but saves additional time. 419 | 420 | // As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds 421 | // when a new register is loaded and the logic needs to switch active register packet pointers. 422 | 423 | // THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1) 424 | 425 | #define DCC_SIGNAL(R,N) \ 426 | if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \ 427 | R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \ 428 | if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \ 429 | R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \ 430 | } else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \ 431 | R.currentReg=R.nextReg; /* update currentReg to nextReg */ \ 432 | R.nextReg=NULL; /* reset nextReg to NULL */ \ 433 | R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \ 434 | R.currentReg->activePacket=R.currentReg->updatePacket; \ 435 | R.currentReg->updatePacket=R.tempPacket; \ 436 | } else{ /* ELSE simply move to next Register */ \ 437 | if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \ 438 | R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \ 439 | R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \ 440 | } /* END-ELSE */ \ 441 | } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \ 442 | \ 443 | if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \ 444 | OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \ 445 | OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \ 446 | } else{ /* ELSE it is a ZERO */ \ 447 | OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \ 448 | OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \ 449 | } /* END-ELSE */ \ 450 | \ 451 | R.currentBit++; /* point to next bit in current Packet */ 452 | 453 | /////////////////////////////////////////////////////////////////////////////// 454 | 455 | // NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT 456 | 457 | ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track 458 | DCC_SIGNAL(mainRegs,1) 459 | } 460 | 461 | #ifdef ARDUINO_AVR_UNO // Configuration for UNO 462 | 463 | ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track 464 | DCC_SIGNAL(progRegs,0) 465 | } 466 | 467 | #else // Configuration for MEGA 468 | 469 | ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track 470 | DCC_SIGNAL(progRegs,3) 471 | } 472 | 473 | #endif 474 | 475 | 476 | /////////////////////////////////////////////////////////////////////////////// 477 | // PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE 478 | // - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH 479 | 480 | void showConfiguration(){ 481 | 482 | int mac_address[]=MAC_ADDRESS; 483 | 484 | Serial.print("\n*** DCC++ CONFIGURATION ***\n"); 485 | 486 | Serial.print("\nVERSION: "); 487 | Serial.print(VERSION); 488 | Serial.print("\nCOMPILED: "); 489 | Serial.print(__DATE__); 490 | Serial.print(" "); 491 | Serial.print(__TIME__); 492 | 493 | Serial.print("\nARDUINO: "); 494 | Serial.print(ARDUINO_TYPE); 495 | 496 | Serial.print("\n\nMOTOR SHIELD: "); 497 | Serial.print(MOTOR_SHIELD_NAME); 498 | 499 | Serial.print("\n\nDCC SIG MAIN: "); 500 | Serial.print(DCC_SIGNAL_PIN_MAIN); 501 | Serial.print("\n DIRECTION: "); 502 | Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A); 503 | Serial.print("\n ENABLE: "); 504 | Serial.print(SIGNAL_ENABLE_PIN_MAIN); 505 | Serial.print("\n CURRENT: "); 506 | Serial.print(CURRENT_MONITOR_PIN_MAIN); 507 | 508 | Serial.print("\n\nDCC SIG PROG: "); 509 | Serial.print(DCC_SIGNAL_PIN_PROG); 510 | Serial.print("\n DIRECTION: "); 511 | Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B); 512 | Serial.print("\n ENABLE: "); 513 | Serial.print(SIGNAL_ENABLE_PIN_PROG); 514 | Serial.print("\n CURRENT: "); 515 | Serial.print(CURRENT_MONITOR_PIN_PROG); 516 | 517 | Serial.print("\n\nNUM TURNOUTS: "); 518 | Serial.print(EEStore::eeStore->data.nTurnouts); 519 | Serial.print("\n SENSORS: "); 520 | Serial.print(EEStore::eeStore->data.nSensors); 521 | Serial.print("\n OUTPUTS: "); 522 | Serial.print(EEStore::eeStore->data.nOutputs); 523 | 524 | Serial.print("\n\nINTERFACE: "); 525 | #if COMM_TYPE == 0 526 | Serial.print("SERIAL"); 527 | #elif COMM_TYPE == 1 528 | Serial.print(COMM_SHIELD_NAME); 529 | Serial.print("\nMAC ADDRESS: "); 530 | for(int i=0;i<5;i++){ 531 | Serial.print(mac_address[i],HEX); 532 | Serial.print(":"); 533 | } 534 | Serial.print(mac_address[5],HEX); 535 | Serial.print("\nPORT: "); 536 | Serial.print(ETHERNET_PORT); 537 | Serial.print("\nIP ADDRESS: "); 538 | 539 | #ifdef IP_ADDRESS 540 | Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address 541 | #else 542 | Ethernet.begin(mac); // Start networking using DHCP to get an IP Address 543 | #endif 544 | 545 | Serial.print(Ethernet.localIP()); 546 | 547 | #ifdef IP_ADDRESS 548 | Serial.print(" (STATIC)"); 549 | #else 550 | Serial.print(" (DHCP)"); 551 | #endif 552 | 553 | #endif 554 | Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO"); 555 | 556 | while(true); 557 | } 558 | 559 | /////////////////////////////////////////////////////////////////////////////// 560 | 561 | 562 | 563 | 564 | -------------------------------------------------------------------------------- /DCCpp_Uno/EEStore.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | EEStore.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "DCCpp_Uno.h" 11 | #include "EEStore.h" 12 | #include "Accessories.h" 13 | #include "Sensor.h" 14 | #include "Outputs.h" 15 | #include 16 | 17 | /////////////////////////////////////////////////////////////////////////////// 18 | 19 | void EEStore::init(){ 20 | 21 | 22 | eeStore=(EEStore *)calloc(1,sizeof(EEStore)); 23 | 24 | EEPROM.get(0,eeStore->data); // get eeStore data 25 | 26 | if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID 27 | sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM 28 | eeStore->data.nTurnouts=0; 29 | eeStore->data.nSensors=0; 30 | eeStore->data.nOutputs=0; 31 | EEPROM.put(0,eeStore->data); 32 | } 33 | 34 | reset(); // set memory pointer to first free EEPROM space 35 | Turnout::load(); // load turnout definitions 36 | Sensor::load(); // load sensor definitions 37 | Output::load(); // load output definitions 38 | 39 | } 40 | 41 | /////////////////////////////////////////////////////////////////////////////// 42 | 43 | void EEStore::clear(){ 44 | 45 | sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM 46 | eeStore->data.nTurnouts=0; 47 | eeStore->data.nSensors=0; 48 | eeStore->data.nOutputs=0; 49 | EEPROM.put(0,eeStore->data); 50 | 51 | } 52 | 53 | /////////////////////////////////////////////////////////////////////////////// 54 | 55 | void EEStore::store(){ 56 | reset(); 57 | Turnout::store(); 58 | Sensor::store(); 59 | Output::store(); 60 | EEPROM.put(0,eeStore->data); 61 | } 62 | 63 | /////////////////////////////////////////////////////////////////////////////// 64 | 65 | void EEStore::advance(int n){ 66 | eeAddress+=n; 67 | } 68 | 69 | /////////////////////////////////////////////////////////////////////////////// 70 | 71 | void EEStore::reset(){ 72 | eeAddress=sizeof(EEStore); 73 | } 74 | /////////////////////////////////////////////////////////////////////////////// 75 | 76 | int EEStore::pointer(){ 77 | return(eeAddress); 78 | } 79 | /////////////////////////////////////////////////////////////////////////////// 80 | 81 | EEStore *EEStore::eeStore=NULL; 82 | int EEStore::eeAddress=0; 83 | 84 | -------------------------------------------------------------------------------- /DCCpp_Uno/EEStore.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | EEStore.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #ifndef EEStore_h 11 | #define EEStore_h 12 | 13 | #define EESTORE_ID "DCC++" 14 | 15 | struct EEStoreData{ 16 | char id[sizeof(EESTORE_ID)]; 17 | int nTurnouts; 18 | int nSensors; 19 | int nOutputs; 20 | }; 21 | 22 | struct EEStore{ 23 | static EEStore *eeStore; 24 | EEStoreData data; 25 | static int eeAddress; 26 | static void init(); 27 | static void reset(); 28 | static int pointer(); 29 | static void advance(int); 30 | static void store(); 31 | static void clear(); 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /DCCpp_Uno/Outputs.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Outputs.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | /********************************************************************** 10 | 11 | DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes. 12 | Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW. 13 | However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH. 14 | 15 | Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up. 16 | The default is to set each defined pin to active or inactive according to its restored state. 17 | However, the default behavior can be modified so that any pin can be forced to be either active or inactive 18 | upon power-up regardless of its previous state before power-down. 19 | 20 | To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete 21 | output definitions using the following variation of the "Z" command: 22 | 23 | : creates a new output ID, with specified PIN and IFLAG values. 24 | if output ID already exists, it is updated with specificed PIN and IFLAG. 25 | note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW 26 | according to IFLAG value specifcied (see below). 27 | returns: if successful and if unsuccessful (e.g. out of memory) 28 | 29 | : deletes definition of output ID 30 | returns: if successful and if unsuccessful (e.g. ID does not exist) 31 | 32 | : lists all defined output pins 33 | returns: for each defined output pin or if no output pins defined 34 | 35 | where 36 | 37 | ID: the numeric ID (0-32767) of the output 38 | PIN: the arduino pin number to use for the output 39 | STATE: the state of the output (0=INACTIVE / 1=ACTIVE) 40 | IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows: 41 | 42 | IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW) 43 | 1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH) 44 | 45 | IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending 46 | on state before power-down; state of pin set to INACTIVE when first created 47 | 1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE 48 | depending on IFLAG, bit 2 49 | 50 | IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created 51 | 1 = state of pin set to ACTIVE uponm power-up or when first created 52 | 53 | Once all outputs have been properly defined, use the command to store their definitions to EEPROM. 54 | If you later make edits/additions/deletions to the output definitions, you must invoke the command if you want those 55 | new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. 56 | 57 | To change the state of outputs that have been defined use: 58 | 59 | : sets output ID to either ACTIVE or INACTIVE state 60 | returns: , or if turnout ID does not exist 61 | 62 | where 63 | 64 | ID: the numeric ID (0-32767) of the turnout to control 65 | STATE: the state of the output (0=INACTIVE / 1=ACTIVE) 66 | 67 | When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so 68 | that it is retained even without power. A list of the current states of each output in the form is generated 69 | by this sketch whenever the status command is invoked. This provides an efficient way of initializing 70 | the state of any outputs being monitored or controlled by a separate interface or GUI program. 71 | 72 | **********************************************************************/ 73 | 74 | #include "Outputs.h" 75 | #include "SerialCommand.h" 76 | #include "DCCpp_Uno.h" 77 | #include "EEStore.h" 78 | #include 79 | #include "Comm.h" 80 | 81 | /////////////////////////////////////////////////////////////////////////////// 82 | 83 | void Output::activate(int s){ 84 | data.oStatus=(s>0); // if s>0, set status to active, else inactive 85 | 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) 86 | if(num>0) 87 | EEPROM.put(num,data.oStatus); 88 | INTERFACE.print(""); 92 | else 93 | INTERFACE.print(" 1>"); 94 | } 95 | 96 | /////////////////////////////////////////////////////////////////////////////// 97 | 98 | Output* Output::get(int n){ 99 | Output *tt; 100 | for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput); 101 | return(tt); 102 | } 103 | /////////////////////////////////////////////////////////////////////////////// 104 | 105 | void Output::remove(int n){ 106 | Output *tt,*pp; 107 | 108 | for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput); 109 | 110 | if(tt==NULL){ 111 | INTERFACE.print(""); 112 | return; 113 | } 114 | 115 | if(tt==firstOutput) 116 | firstOutput=tt->nextOutput; 117 | else 118 | pp->nextOutput=tt->nextOutput; 119 | 120 | free(tt); 121 | 122 | INTERFACE.print(""); 123 | } 124 | 125 | /////////////////////////////////////////////////////////////////////////////// 126 | 127 | void Output::show(int n){ 128 | Output *tt; 129 | 130 | if(firstOutput==NULL){ 131 | INTERFACE.print(""); 132 | return; 133 | } 134 | 135 | for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ 136 | INTERFACE.print("data.id); 138 | if(n==1){ 139 | INTERFACE.print(" "); 140 | INTERFACE.print(tt->data.pin); 141 | INTERFACE.print(" "); 142 | INTERFACE.print(tt->data.iFlag); 143 | } 144 | if(tt->data.oStatus==0) 145 | INTERFACE.print(" 0>"); 146 | else 147 | INTERFACE.print(" 1>"); 148 | } 149 | } 150 | 151 | /////////////////////////////////////////////////////////////////////////////// 152 | 153 | void Output::parse(char *c){ 154 | int n,s,m; 155 | Output *t; 156 | 157 | switch(sscanf(c,"%d %d %d",&n,&s,&m)){ 158 | 159 | case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) 160 | t=get(n); 161 | if(t!=NULL) 162 | t->activate(s); 163 | else 164 | INTERFACE.print(""); 165 | break; 166 | 167 | case 3: // argument is string with id number of output followed by a pin number and invert flag 168 | create(n,s,m,1); 169 | break; 170 | 171 | case 1: // argument is a string with id number only 172 | remove(n); 173 | break; 174 | 175 | case -1: // no arguments 176 | show(1); // verbose show 177 | break; 178 | } 179 | } 180 | 181 | /////////////////////////////////////////////////////////////////////////////// 182 | 183 | void Output::load(){ 184 | struct OutputData data; 185 | Output *tt; 186 | 187 | for(int i=0;idata.nOutputs;i++){ 188 | EEPROM.get(EEStore::pointer(),data); 189 | tt=create(data.id,data.pin,data.iFlag); 190 | 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 191 | digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); 192 | pinMode(tt->data.pin,OUTPUT); 193 | tt->num=EEStore::pointer(); 194 | EEStore::advance(sizeof(tt->data)); 195 | } 196 | } 197 | 198 | /////////////////////////////////////////////////////////////////////////////// 199 | 200 | void Output::store(){ 201 | Output *tt; 202 | 203 | tt=firstOutput; 204 | EEStore::eeStore->data.nOutputs=0; 205 | 206 | while(tt!=NULL){ 207 | tt->num=EEStore::pointer(); 208 | EEPROM.put(EEStore::pointer(),tt->data); 209 | EEStore::advance(sizeof(tt->data)); 210 | tt=tt->nextOutput; 211 | EEStore::eeStore->data.nOutputs++; 212 | } 213 | 214 | } 215 | /////////////////////////////////////////////////////////////////////////////// 216 | 217 | Output *Output::create(int id, int pin, int iFlag, int v){ 218 | Output *tt; 219 | 220 | if(firstOutput==NULL){ 221 | firstOutput=(Output *)calloc(1,sizeof(Output)); 222 | tt=firstOutput; 223 | } else if((tt=get(id))==NULL){ 224 | tt=firstOutput; 225 | while(tt->nextOutput!=NULL) 226 | tt=tt->nextOutput; 227 | tt->nextOutput=(Output *)calloc(1,sizeof(Output)); 228 | tt=tt->nextOutput; 229 | } 230 | 231 | if(tt==NULL){ // problem allocating memory 232 | if(v==1) 233 | INTERFACE.print(""); 234 | return(tt); 235 | } 236 | 237 | tt->data.id=id; 238 | tt->data.pin=pin; 239 | tt->data.iFlag=iFlag; 240 | tt->data.oStatus=0; 241 | 242 | if(v==1){ 243 | 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 244 | digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); 245 | pinMode(tt->data.pin,OUTPUT); 246 | INTERFACE.print(""); 247 | } 248 | 249 | return(tt); 250 | 251 | } 252 | 253 | /////////////////////////////////////////////////////////////////////////////// 254 | 255 | Output *Output::firstOutput=NULL; 256 | 257 | -------------------------------------------------------------------------------- /DCCpp_Uno/Outputs.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Outputs.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "Arduino.h" 11 | 12 | #ifndef Outputs_h 13 | #define Outputs_h 14 | 15 | struct OutputData { 16 | byte oStatus; 17 | int id; 18 | byte pin; 19 | byte iFlag; 20 | }; 21 | 22 | struct Output{ 23 | static Output *firstOutput; 24 | int num; 25 | struct OutputData data; 26 | Output *nextOutput; 27 | void activate(int s); 28 | static void parse(char *c); 29 | static Output* get(int); 30 | static void remove(int); 31 | static void load(); 32 | static void store(); 33 | static Output *create(int, int, int, int=0); 34 | static void show(int=0); 35 | }; // Output 36 | 37 | #endif 38 | 39 | 40 | -------------------------------------------------------------------------------- /DCCpp_Uno/PacketRegister.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | PacketRegister.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #include "DCCpp_Uno.h" 11 | #include "PacketRegister.h" 12 | #include "Comm.h" 13 | 14 | /////////////////////////////////////////////////////////////////////////////// 15 | 16 | void Register::initPackets(){ 17 | activePacket=packet; 18 | updatePacket=packet+1; 19 | } // Register::initPackets 20 | 21 | /////////////////////////////////////////////////////////////////////////////// 22 | 23 | RegisterList::RegisterList(int maxNumRegs){ 24 | this->maxNumRegs=maxNumRegs; 25 | reg=(Register *)calloc((maxNumRegs+1),sizeof(Register)); 26 | for(int i=0;i<=maxNumRegs;i++) 27 | reg[i].initPackets(); 28 | regMap=(Register **)calloc((maxNumRegs+1),sizeof(Register *)); 29 | speedTable=(int *)calloc((maxNumRegs+1),sizeof(int *)); 30 | currentReg=reg; 31 | regMap[0]=reg; 32 | maxLoadedReg=reg; 33 | nextReg=NULL; 34 | currentBit=0; 35 | nRepeat=0; 36 | } // RegisterList::RegisterList 37 | 38 | /////////////////////////////////////////////////////////////////////////////// 39 | 40 | // LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE) 41 | // CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS 42 | // BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS) 43 | 44 | void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile { 45 | 46 | nReg=nReg%((maxNumRegs+1)); // force nReg to be between 0 and maxNumRegs, inclusive 47 | 48 | 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 49 | 50 | if(regMap[nReg]==NULL) // first time this Register Number has been called 51 | regMap[nReg]=maxLoadedReg+1; // set Register Pointer for this Register Number to next available Register 52 | 53 | Register *r=regMap[nReg]; // set Register to be updated 54 | Packet *p=r->updatePacket; // set Packet in the Register to be updated 55 | byte *buf=p->buf; // set byte buffer in the Packet to be updated 56 | 57 | b[nBytes]=b[0]; // copy first byte into what will become the checksum byte 58 | for(int i=1;i>1; // b[2], bits 7-1 68 | buf[6]=b[2]<<7; // b[2], bit 0 69 | 70 | if(nBytes==3){ 71 | p->nBits=49; 72 | } else{ 73 | buf[6]+=b[3]>>2; // b[3], bits 7-2 74 | buf[7]=b[3]<<6; // b[3], bit 1-0 75 | if(nBytes==4){ 76 | p->nBits=58; 77 | } else{ 78 | buf[7]+=b[4]>>3; // b[4], bits 7-3 79 | buf[8]=b[4]<<5; // b[4], bits 2-0 80 | if(nBytes==5){ 81 | p->nBits=67; 82 | } else{ 83 | buf[8]+=b[5]>>4; // b[5], bits 7-4 84 | buf[9]=b[5]<<4; // b[5], bits 3-0 85 | p->nBits=76; 86 | } // >5 bytes 87 | } // >4 bytes 88 | } // >3 bytes 89 | 90 | nextReg=r; 91 | this->nRepeat=nRepeat; 92 | maxLoadedReg=max(maxLoadedReg,nextReg); 93 | 94 | if(printFlag && SHOW_PACKETS) // for debugging purposes 95 | printPacket(nReg,b,nBytes,nRepeat); 96 | 97 | } // RegisterList::loadPacket 98 | 99 | /////////////////////////////////////////////////////////////////////////////// 100 | 101 | void RegisterList::setThrottle(char *s) volatile{ 102 | byte b[5]; // save space for checksum byte 103 | int nReg; 104 | int cab; 105 | int tSpeed; 106 | int tDirection; 107 | byte nB=0; 108 | 109 | if(sscanf(s,"%d %d %d %d",&nReg,&cab,&tSpeed,&tDirection)!=4) 110 | return; 111 | 112 | if(nReg<1 || nReg>maxNumRegs) 113 | return; 114 | 115 | if(cab>127) 116 | b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address 117 | 118 | b[nB++]=lowByte(cab); 119 | b[nB++]=0x3F; // 128-step speed control byte 120 | if(tSpeed>=0) 121 | b[nB++]=tSpeed+(tSpeed>0)+tDirection*128; // max speed is 126, but speed codes range from 2-127 (0=stop, 1=emergency stop) 122 | else{ 123 | b[nB++]=1; 124 | tSpeed=0; 125 | } 126 | 127 | loadPacket(nReg,b,nB,0,1); 128 | 129 | INTERFACE.print(""); 134 | 135 | speedTable[nReg]=tDirection==1?tSpeed:-tSpeed; 136 | 137 | } // RegisterList::setThrottle() 138 | 139 | /////////////////////////////////////////////////////////////////////////////// 140 | 141 | void RegisterList::setFunction(char *s) volatile{ 142 | byte b[5]; // save space for checksum byte 143 | int cab; 144 | int fByte, eByte; 145 | int nParams; 146 | byte nB=0; 147 | 148 | nParams=sscanf(s,"%d %d %d",&cab,&fByte,&eByte); 149 | 150 | if(nParams<2) 151 | return; 152 | 153 | if(cab>127) 154 | b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address 155 | 156 | b[nB++]=lowByte(cab); 157 | 158 | if(nParams==2){ // this is a request for functions FL,F1-F12 159 | 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 160 | } else { // this is a request for functions F13-F28 161 | b[nB++]=(fByte | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28) 162 | b[nB++]=eByte; 163 | } 164 | 165 | loadPacket(0,b,nB,4,1); 166 | 167 | } // RegisterList::setFunction() 168 | 169 | /////////////////////////////////////////////////////////////////////////////// 170 | 171 | void RegisterList::setAccessory(char *s) volatile{ 172 | byte b[3]; // save space for checksum byte 173 | int aAdd; // the accessory address (0-511 = 9 bits) 174 | int aNum; // the accessory number within that address (0-3) 175 | int activate; // flag indicated whether accessory should be activated (1) or deactivated (0) following NMRA recommended convention 176 | 177 | if(sscanf(s,"%d %d %d",&aAdd,&aNum,&activate)!=3) 178 | return; 179 | 180 | b[0]=aAdd%64+128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address 181 | 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 182 | 183 | loadPacket(0,b,2,4,1); 184 | 185 | } // RegisterList::setAccessory() 186 | 187 | /////////////////////////////////////////////////////////////////////////////// 188 | 189 | void RegisterList::writeTextPacket(char *s) volatile{ 190 | 191 | int nReg; 192 | byte b[6]; 193 | int nBytes; 194 | volatile RegisterList *regs; 195 | 196 | nBytes=sscanf(s,"%d %x %x %x %x %x",&nReg,b,b+1,b+2,b+3,b+4)-1; 197 | 198 | if(nBytes<2 || nBytes>5){ // invalid valid packet 199 | INTERFACE.print(""); 200 | return; 201 | } 202 | 203 | loadPacket(nReg,b,nBytes,0,1); 204 | 205 | } // RegisterList::writeTextPacket() 206 | 207 | /////////////////////////////////////////////////////////////////////////////// 208 | 209 | void RegisterList::readCV(char *s) volatile{ 210 | byte bRead[4]; 211 | int bValue; 212 | int c,d,base; 213 | int cv, callBack, callBackSub; 214 | 215 | if(sscanf(s,"%d %d %d",&cv,&callBack,&callBackSub)!=3) // cv = 1-1024 216 | return; 217 | cv--; // actual CV addresses are cv-1 (0-1023) 218 | 219 | bRead[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 220 | bRead[1]=lowByte(cv); 221 | 222 | bValue=0; 223 | 224 | for(int i=0;i<8;i++){ 225 | 226 | c=0; 227 | d=0; 228 | base=0; 229 | 230 | for(int j=0;jACK_SAMPLE_THRESHOLD) 243 | d=1; 244 | } 245 | 246 | bitWrite(bValue,i,d); 247 | } 248 | 249 | c=0; 250 | d=0; 251 | base=0; 252 | 253 | for(int j=0;jACK_SAMPLE_THRESHOLD) 267 | d=1; 268 | } 269 | 270 | if(d==0) // verify unsuccessful 271 | bValue=-1; 272 | 273 | INTERFACE.print(""); 282 | 283 | } // RegisterList::readCV() 284 | 285 | /////////////////////////////////////////////////////////////////////////////// 286 | 287 | void RegisterList::writeCVByte(char *s) volatile{ 288 | byte bWrite[4]; 289 | int bValue; 290 | int c,d,base; 291 | int cv, callBack, callBackSub; 292 | 293 | if(sscanf(s,"%d %d %d %d",&cv,&bValue,&callBack,&callBackSub)!=4) // cv = 1-1024 294 | return; 295 | cv--; // actual CV addresses are cv-1 (0-1023) 296 | 297 | bWrite[0]=0x7C+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 298 | bWrite[1]=lowByte(cv); 299 | bWrite[2]=bValue; 300 | 301 | loadPacket(0,resetPacket,2,1); 302 | loadPacket(0,bWrite,3,4); 303 | loadPacket(0,resetPacket,2,1); 304 | loadPacket(0,idlePacket,2,10); 305 | 306 | c=0; 307 | d=0; 308 | base=0; 309 | 310 | for(int j=0;jACK_SAMPLE_THRESHOLD) 323 | d=1; 324 | } 325 | 326 | if(d==0) // verify unsuccessful 327 | bValue=-1; 328 | 329 | INTERFACE.print(""); 338 | 339 | } // RegisterList::writeCVByte() 340 | 341 | /////////////////////////////////////////////////////////////////////////////// 342 | 343 | void RegisterList::writeCVBit(char *s) volatile{ 344 | byte bWrite[4]; 345 | int bNum,bValue; 346 | int c,d,base; 347 | int cv, callBack, callBackSub; 348 | 349 | if(sscanf(s,"%d %d %d %d %d",&cv,&bNum,&bValue,&callBack,&callBackSub)!=5) // cv = 1-1024 350 | return; 351 | cv--; // actual CV addresses are cv-1 (0-1023) 352 | bValue=bValue%2; 353 | bNum=bNum%8; 354 | 355 | bWrite[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 356 | bWrite[1]=lowByte(cv); 357 | bWrite[2]=0xF0+bValue*8+bNum; 358 | 359 | loadPacket(0,resetPacket,2,1); 360 | loadPacket(0,bWrite,3,4); 361 | loadPacket(0,resetPacket,2,1); 362 | loadPacket(0,idlePacket,2,10); 363 | 364 | c=0; 365 | d=0; 366 | base=0; 367 | 368 | for(int j=0;jACK_SAMPLE_THRESHOLD) 381 | d=1; 382 | } 383 | 384 | if(d==0) // verify unsuccessful 385 | bValue=-1; 386 | 387 | INTERFACE.print(""); 398 | 399 | } // RegisterList::writeCVBit() 400 | 401 | /////////////////////////////////////////////////////////////////////////////// 402 | 403 | void RegisterList::writeCVByteMain(char *s) volatile{ 404 | byte b[6]; // save space for checksum byte 405 | int cab; 406 | int cv; 407 | int bValue; 408 | byte nB=0; 409 | 410 | if(sscanf(s,"%d %d %d",&cab,&cv,&bValue)!=3) 411 | return; 412 | cv--; 413 | 414 | if(cab>127) 415 | b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address 416 | 417 | b[nB++]=lowByte(cab); 418 | b[nB++]=0xEC+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 419 | b[nB++]=lowByte(cv); 420 | b[nB++]=bValue; 421 | 422 | loadPacket(0,b,nB,4); 423 | 424 | } // RegisterList::writeCVByteMain() 425 | 426 | /////////////////////////////////////////////////////////////////////////////// 427 | 428 | void RegisterList::writeCVBitMain(char *s) volatile{ 429 | byte b[6]; // save space for checksum byte 430 | int cab; 431 | int cv; 432 | int bNum; 433 | int bValue; 434 | byte nB=0; 435 | 436 | if(sscanf(s,"%d %d %d %d",&cab,&cv,&bNum,&bValue)!=4) 437 | return; 438 | cv--; 439 | 440 | bValue=bValue%2; 441 | bNum=bNum%8; 442 | 443 | if(cab>127) 444 | b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address 445 | 446 | b[nB++]=lowByte(cab); 447 | b[nB++]=0xE8+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 448 | b[nB++]=lowByte(cv); 449 | b[nB++]=0xF0+bValue*8+bNum; 450 | 451 | loadPacket(0,b,nB,4); 452 | 453 | } // RegisterList::writeCVBitMain() 454 | 455 | /////////////////////////////////////////////////////////////////////////////// 456 | 457 | void RegisterList::printPacket(int nReg, byte *b, int nBytes, int nRepeat) volatile { 458 | 459 | INTERFACE.print("<*"); 460 | INTERFACE.print(nReg); 461 | INTERFACE.print(":"); 462 | for(int i=0;i"); 469 | } // RegisterList::printPacket() 470 | 471 | /////////////////////////////////////////////////////////////////////////////// 472 | 473 | byte RegisterList::idlePacket[3]={0xFF,0x00,0}; // always leave extra byte for checksum computation 474 | byte RegisterList::resetPacket[3]={0x00,0x00,0}; 475 | 476 | 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 477 | -------------------------------------------------------------------------------- /DCCpp_Uno/PacketRegister.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | PacketRegister.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #ifndef PacketRegister_h 11 | #define PacketRegister_h 12 | 13 | #include "Arduino.h" 14 | 15 | // Define constants used for reading CVs from the Programming Track 16 | 17 | #define ACK_BASE_COUNT 100 // number of analogRead samples to take before each CV verify to establish a baseline current 18 | #define ACK_SAMPLE_COUNT 500 // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent 19 | #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 20 | #define ACK_SAMPLE_THRESHOLD 30 // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT 21 | 22 | // Define a series of registers that can be sequentially accessed over a loop to generate a repeating series of DCC Packets 23 | 24 | struct Packet{ 25 | byte buf[10]; 26 | byte nBits; 27 | }; // Packet 28 | 29 | struct Register{ 30 | Packet packet[2]; 31 | Packet *activePacket; 32 | Packet *updatePacket; 33 | void initPackets(); 34 | }; // Register 35 | 36 | struct RegisterList{ 37 | int maxNumRegs; 38 | Register *reg; 39 | Register **regMap; 40 | Register *currentReg; 41 | Register *maxLoadedReg; 42 | Register *nextReg; 43 | Packet *tempPacket; 44 | byte currentBit; 45 | byte nRepeat; 46 | int *speedTable; 47 | static byte idlePacket[]; 48 | static byte resetPacket[]; 49 | static byte bitMask[]; 50 | RegisterList(int); 51 | void loadPacket(int, byte *, int, int, int=0) volatile; 52 | void setThrottle(char *) volatile; 53 | void setFunction(char *) volatile; 54 | void setAccessory(char *) volatile; 55 | void writeTextPacket(char *) volatile; 56 | void readCV(char *) volatile; 57 | void writeCVByte(char *) volatile; 58 | void writeCVBit(char *) volatile; 59 | void writeCVByteMain(char *) volatile; 60 | void writeCVBitMain(char *s) volatile; 61 | void printPacket(int, byte *, int, int) volatile; 62 | }; 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /DCCpp_Uno/Sensor.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Sensor.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | /********************************************************************** 10 | 11 | DCC++ BASE STATION supports Sensor inputs that can be connected to any Aruidno Pin 12 | not in use by this program. Sensors can be of any type (infrared, magentic, mechanical...). 13 | The only requirement is that when "activated" the Sensor must force the specified Arduino 14 | Pin LOW (i.e. to ground), and when not activated, this Pin should remain HIGH (e.g. 5V), 15 | or be allowed to float HIGH if use of the Arduino Pin's internal pull-up resistor is specified. 16 | 17 | To ensure proper voltage levels, some part of the Sensor circuitry 18 | MUST be tied back to the same ground as used by the Arduino. 19 | 20 | The Sensor code below utilizes exponential smoothing to "de-bounce" spikes generated by 21 | mechanical switches and transistors. This avoids the need to create smoothing circuitry 22 | for each sensor. You may need to change these parameters through trial and error for your specific sensors. 23 | 24 | To have this sketch monitor one or more Arduino pins for sensor triggers, first define/edit/delete 25 | sensor definitions using the following variation of the "S" command: 26 | 27 | : creates a new sensor ID, with specified PIN and PULLUP 28 | if sensor ID already exists, it is updated with specificed PIN and PULLUP 29 | returns: if successful and if unsuccessful (e.g. out of memory) 30 | 31 | : deletes definition of sensor ID 32 | returns: if successful and if unsuccessful (e.g. ID does not exist) 33 | 34 | : lists all defined sensors 35 | returns: for each defined sensor or if no sensors defined 36 | 37 | where 38 | 39 | ID: the numeric ID (0-32767) of the sensor 40 | PIN: the arduino pin number the sensor is connected to 41 | PULLUP: 1=use internal pull-up resistor for PIN, 0=don't use internal pull-up resistor for PIN 42 | 43 | Once all sensors have been properly defined, use the command to store their definitions to EEPROM. 44 | If you later make edits/additions/deletions to the sensor definitions, you must invoke the command if you want those 45 | new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. 46 | 47 | All sensors defined as per above are repeatedly and sequentially checked within the main loop of this sketch. 48 | If a Sensor Pin is found to have transitioned from one state to another, one of the following serial messages are generated: 49 | 50 | - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered) 51 | - for transition of Sensor ID from LOW state to HIGH state (i.e. the sensor is no longer triggered) 52 | 53 | Depending on whether the physical sensor is acting as an "event-trigger" or a "detection-sensor," you may 54 | decide to ignore the return and only react to triggers. 55 | 56 | **********************************************************************/ 57 | 58 | #include "DCCpp_Uno.h" 59 | #include "Sensor.h" 60 | #include "EEStore.h" 61 | #include 62 | #include "Comm.h" 63 | 64 | /////////////////////////////////////////////////////////////////////////////// 65 | 66 | void Sensor::check(){ 67 | Sensor *tt; 68 | 69 | for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ 70 | tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; 71 | 72 | if(!tt->active && tt->signal<0.5){ 73 | tt->active=true; 74 | INTERFACE.print("data.snum); 76 | INTERFACE.print(">"); 77 | } else if(tt->active && tt->signal>0.9){ 78 | tt->active=false; 79 | INTERFACE.print("data.snum); 81 | INTERFACE.print(">"); 82 | } 83 | } // loop over all sensors 84 | 85 | } // Sensor::check 86 | 87 | /////////////////////////////////////////////////////////////////////////////// 88 | 89 | Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ 90 | Sensor *tt; 91 | 92 | if(firstSensor==NULL){ 93 | firstSensor=(Sensor *)calloc(1,sizeof(Sensor)); 94 | tt=firstSensor; 95 | } else if((tt=get(snum))==NULL){ 96 | tt=firstSensor; 97 | while(tt->nextSensor!=NULL) 98 | tt=tt->nextSensor; 99 | tt->nextSensor=(Sensor *)calloc(1,sizeof(Sensor)); 100 | tt=tt->nextSensor; 101 | } 102 | 103 | if(tt==NULL){ // problem allocating memory 104 | if(v==1) 105 | INTERFACE.print(""); 106 | return(tt); 107 | } 108 | 109 | tt->data.snum=snum; 110 | tt->data.pin=pin; 111 | tt->data.pullUp=(pullUp==0?LOW:HIGH); 112 | tt->active=false; 113 | tt->signal=1; 114 | pinMode(pin,INPUT); // set mode to input 115 | 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 116 | 117 | if(v==1) 118 | INTERFACE.print(""); 119 | return(tt); 120 | 121 | } 122 | 123 | /////////////////////////////////////////////////////////////////////////////// 124 | 125 | Sensor* Sensor::get(int n){ 126 | Sensor *tt; 127 | for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextSensor); 128 | return(tt); 129 | } 130 | /////////////////////////////////////////////////////////////////////////////// 131 | 132 | void Sensor::remove(int n){ 133 | Sensor *tt,*pp; 134 | 135 | for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor); 136 | 137 | if(tt==NULL){ 138 | INTERFACE.print(""); 139 | return; 140 | } 141 | 142 | if(tt==firstSensor) 143 | firstSensor=tt->nextSensor; 144 | else 145 | pp->nextSensor=tt->nextSensor; 146 | 147 | free(tt); 148 | 149 | INTERFACE.print(""); 150 | } 151 | 152 | /////////////////////////////////////////////////////////////////////////////// 153 | 154 | void Sensor::show(){ 155 | Sensor *tt; 156 | 157 | if(firstSensor==NULL){ 158 | INTERFACE.print(""); 159 | return; 160 | } 161 | 162 | for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ 163 | INTERFACE.print("data.snum); 165 | INTERFACE.print(" "); 166 | INTERFACE.print(tt->data.pin); 167 | INTERFACE.print(" "); 168 | INTERFACE.print(tt->data.pullUp); 169 | INTERFACE.print(">"); 170 | } 171 | } 172 | 173 | /////////////////////////////////////////////////////////////////////////////// 174 | 175 | void Sensor::status(){ 176 | Sensor *tt; 177 | 178 | if(firstSensor==NULL){ 179 | INTERFACE.print(""); 180 | return; 181 | } 182 | 183 | for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ 184 | INTERFACE.print(tt->active?"data.snum); 186 | INTERFACE.print(">"); 187 | } 188 | } 189 | 190 | /////////////////////////////////////////////////////////////////////////////// 191 | 192 | void Sensor::parse(char *c){ 193 | int n,s,m; 194 | Sensor *t; 195 | 196 | switch(sscanf(c,"%d %d %d",&n,&s,&m)){ 197 | 198 | case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH) 199 | create(n,s,m,1); 200 | break; 201 | 202 | case 1: // argument is a string with id number only 203 | remove(n); 204 | break; 205 | 206 | case -1: // no arguments 207 | show(); 208 | break; 209 | 210 | case 2: // invalid number of arguments 211 | INTERFACE.print(""); 212 | break; 213 | } 214 | } 215 | 216 | /////////////////////////////////////////////////////////////////////////////// 217 | 218 | void Sensor::load(){ 219 | struct SensorData data; 220 | Sensor *tt; 221 | 222 | for(int i=0;idata.nSensors;i++){ 223 | EEPROM.get(EEStore::pointer(),data); 224 | tt=create(data.snum,data.pin,data.pullUp); 225 | EEStore::advance(sizeof(tt->data)); 226 | } 227 | } 228 | 229 | /////////////////////////////////////////////////////////////////////////////// 230 | 231 | void Sensor::store(){ 232 | Sensor *tt; 233 | 234 | tt=firstSensor; 235 | EEStore::eeStore->data.nSensors=0; 236 | 237 | while(tt!=NULL){ 238 | EEPROM.put(EEStore::pointer(),tt->data); 239 | EEStore::advance(sizeof(tt->data)); 240 | tt=tt->nextSensor; 241 | EEStore::eeStore->data.nSensors++; 242 | } 243 | } 244 | 245 | /////////////////////////////////////////////////////////////////////////////// 246 | 247 | Sensor *Sensor::firstSensor=NULL; 248 | 249 | -------------------------------------------------------------------------------- /DCCpp_Uno/Sensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Sensor.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #ifndef Sensor_h 11 | #define Sensor_h 12 | 13 | #include "Arduino.h" 14 | 15 | #define SENSOR_DECAY 0.03 16 | 17 | struct SensorData { 18 | int snum; 19 | byte pin; 20 | byte pullUp; 21 | }; 22 | 23 | struct Sensor{ 24 | static Sensor *firstSensor; 25 | SensorData data; 26 | boolean active; 27 | float signal; 28 | Sensor *nextSensor; 29 | static void load(); 30 | static void store(); 31 | static Sensor *create(int, int, int, int=0); 32 | static Sensor* get(int); 33 | static void remove(int); 34 | static void show(); 35 | static void status(); 36 | static void parse(char *c); 37 | static void check(); 38 | }; // Sensor 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /DCCpp_Uno/SerialCommand.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | SerialCommand.cpp 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | // DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS 11 | // WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS 12 | // ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER 13 | // COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. 14 | 15 | // See SerialCommand::parse() below for defined text commands. 16 | 17 | #include "SerialCommand.h" 18 | #include "DCCpp_Uno.h" 19 | #include "Accessories.h" 20 | #include "Sensor.h" 21 | #include "Outputs.h" 22 | #include "EEStore.h" 23 | #include "Comm.h" 24 | 25 | extern int __heap_start, *__brkval; 26 | 27 | /////////////////////////////////////////////////////////////////////////////// 28 | 29 | char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; 30 | volatile RegisterList *SerialCommand::mRegs; 31 | volatile RegisterList *SerialCommand::pRegs; 32 | CurrentMonitor *SerialCommand::mMonitor; 33 | 34 | /////////////////////////////////////////////////////////////////////////////// 35 | 36 | void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){ 37 | mRegs=_mRegs; 38 | pRegs=_pRegs; 39 | mMonitor=_mMonitor; 40 | sprintf(commandString,""); 41 | } // SerialCommand:SerialCommand 42 | 43 | /////////////////////////////////////////////////////////////////////////////// 44 | 45 | void SerialCommand::process(){ 46 | char c; 47 | 48 | #if COMM_TYPE == 0 49 | 50 | while(INTERFACE.available()>0){ // while there is data on the serial line 51 | c=INTERFACE.read(); 52 | if(c=='<') // start of new command 53 | sprintf(commandString,""); 54 | else if(c=='>') // end of new command 55 | parse(commandString); 56 | else if(strlen(commandString)') 58 | } // while 59 | 60 | #elif COMM_TYPE == 1 61 | 62 | EthernetClient client=INTERFACE.available(); 63 | 64 | if(client){ 65 | while(client.connected() && client.available()){ // while there is data on the network 66 | c=client.read(); 67 | if(c=='<') // start of new command 68 | sprintf(commandString,""); 69 | else if(c=='>') // end of new command 70 | parse(commandString); 71 | else if(strlen(commandString)') 73 | } // while 74 | } 75 | 76 | #endif 77 | 78 | } // SerialCommand:process 79 | 80 | /////////////////////////////////////////////////////////////////////////////// 81 | 82 | void SerialCommand::parse(char *com){ 83 | 84 | switch(com[0]){ 85 | 86 | /***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ 87 | 88 | case 't': // 89 | /* 90 | * sets the throttle for a given register/cab combination 91 | * 92 | * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting 93 | * CAB: the short (1-127) or long (128-10293) address of the engine decoder 94 | * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 95 | * DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train 96 | * 97 | * returns: 98 | * 99 | */ 100 | mRegs->setThrottle(com+1); 101 | break; 102 | 103 | /***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ 104 | 105 | case 'f': // 106 | /* 107 | * turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL) 108 | * NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program 109 | * 110 | * CAB: the short (1-127) or long (128-10293) address of the engine decoder 111 | * 112 | * To set functions F0-F4 on (=1) or off (=0): 113 | * 114 | * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 115 | * BYTE2: omitted 116 | * 117 | * To set functions F5-F8 on (=1) or off (=0): 118 | * 119 | * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 120 | * BYTE2: omitted 121 | * 122 | * To set functions F9-F12 on (=1) or off (=0): 123 | * 124 | * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 125 | * BYTE2: omitted 126 | * 127 | * To set functions F13-F20 on (=1) or off (=0): 128 | * 129 | * BYTE1: 222 130 | * BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128 131 | * 132 | * To set functions F21-F28 on (=1) of off (=0): 133 | * 134 | * BYTE1: 223 135 | * BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128 136 | * 137 | * returns: NONE 138 | * 139 | */ 140 | mRegs->setFunction(com+1); 141 | break; 142 | 143 | /***** OPERATE STATIONARY ACCESSORY DECODERS ****/ 144 | 145 | case 'a': // 146 | /* 147 | * turns an accessory (stationary) decoder on or off 148 | * 149 | * ADDRESS: the primary address of the decoder (0-511) 150 | * SUBADDRESS: the subaddress of the decoder (0-3) 151 | * ACTIVATE: 1=on (set), 0=off (clear) 152 | * 153 | * Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N, 154 | * from 1 through a max of 2044, where 155 | * 156 | * N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0 157 | * 158 | * OR 159 | * 160 | * ADDRESS = INT((N - 1) / 4) + 1 161 | * SUBADDRESS = (N - 1) % 4 162 | * 163 | * returns: NONE 164 | */ 165 | mRegs->setAccessory(com+1); 166 | break; 167 | 168 | /***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ 169 | 170 | case 'T': // 171 | /* 172 | * : sets turnout ID to either the "thrown" or "unthrown" position 173 | * 174 | * ID: the numeric ID (0-32767) of the turnout to control 175 | * THROW: 0 (unthrown) or 1 (thrown) 176 | * 177 | * returns: or if turnout ID does not exist 178 | * 179 | * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND 180 | * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS 181 | */ 182 | Turnout::parse(com+1); 183 | break; 184 | 185 | /***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ 186 | 187 | case 'Z': // 188 | /* 189 | * : sets output ID to either the "active" or "inactive" state 190 | * 191 | * ID: the numeric ID (0-32767) of the output to control 192 | * ACTIVATE: 0 (active) or 1 (inactive) 193 | * 194 | * returns: or if output ID does not exist 195 | * 196 | * *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND 197 | * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS 198 | */ 199 | Output::parse(com+1); 200 | break; 201 | 202 | /***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ 203 | 204 | case 'S': 205 | /* 206 | * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND 207 | * USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS 208 | */ 209 | Sensor::parse(com+1); 210 | break; 211 | 212 | /***** SHOW STATUS OF ALL SENSORS ****/ 213 | 214 | case 'Q': // 215 | /* 216 | * returns: the status of each sensor ID in the form (active) or (not active) 217 | */ 218 | Sensor::status(); 219 | break; 220 | 221 | /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ 222 | 223 | case 'w': // 224 | /* 225 | * writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track 226 | * 227 | * CAB: the short (1-127) or long (128-10293) address of the engine decoder 228 | * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) 229 | * VALUE: the value to be written to the Configuration Variable memory location (0-255) 230 | * 231 | * returns: NONE 232 | */ 233 | mRegs->writeCVByteMain(com+1); 234 | break; 235 | 236 | /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ 237 | 238 | case 'b': // 239 | /* 240 | * writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track 241 | * 242 | * CAB: the short (1-127) or long (128-10293) address of the engine decoder 243 | * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) 244 | * BIT: the bit number of the Configurarion Variable regsiter to write (0-7) 245 | * VALUE: the value of the bit to be written (0-1) 246 | * 247 | * returns: NONE 248 | */ 249 | mRegs->writeCVBitMain(com+1); 250 | break; 251 | 252 | /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ 253 | 254 | case 'W': // 255 | /* 256 | * writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track 257 | * 258 | * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) 259 | * VALUE: the value to be written to the Configuration Variable memory location (0-255) 260 | * 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 261 | * 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 262 | * 263 | * returns: writeCVByte(com+1); 267 | break; 268 | 269 | /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ 270 | 271 | case 'B': // 272 | /* 273 | * writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track 274 | * 275 | * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) 276 | * BIT: the bit number of the Configurarion Variable memory location to write (0-7) 277 | * VALUE: the value of the bit to be written (0-1) 278 | * 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 279 | * 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 280 | * 281 | * returns: writeCVBit(com+1); 285 | break; 286 | 287 | /***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ 288 | 289 | case 'R': // 290 | /* 291 | * reads a Configuration Variable from the decoder of an engine on the programming track 292 | * 293 | * CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024) 294 | * 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 295 | * 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 296 | * 297 | * returns: readCV(com+1); 301 | break; 302 | 303 | /***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ 304 | 305 | case '1': // <1> 306 | /* 307 | * enables power from the motor shield to the main operations and programming tracks 308 | * 309 | * returns: 310 | */ 311 | digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); 312 | digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); 313 | INTERFACE.print(""); 314 | break; 315 | 316 | /***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ 317 | 318 | case '0': // <0> 319 | /* 320 | * disables power from the motor shield to the main operations and programming tracks 321 | * 322 | * returns: 323 | */ 324 | digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); 325 | digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); 326 | INTERFACE.print(""); 327 | break; 328 | 329 | /***** READ MAIN OPERATIONS TRACK CURRENT ****/ 330 | 331 | case 'c': // 332 | /* 333 | * reads current being drawn on main operations track 334 | * 335 | * returns: 336 | * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme 337 | */ 338 | INTERFACE.print("current)); 340 | INTERFACE.print(">"); 341 | break; 342 | 343 | /***** READ STATUS OF DCC++ BASE STATION ****/ 344 | 345 | case 's': // 346 | /* 347 | * returns status messages containing track power status, throttle status, turn-out status, and a version number 348 | * 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 349 | * 350 | * returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings 351 | */ 352 | if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN 353 | INTERFACE.print(""); 354 | else 355 | INTERFACE.print(""); 356 | 357 | for(int i=1;i<=MAX_MAIN_REGISTERS;i++){ 358 | if(mRegs->speedTable[i]==0) 359 | continue; 360 | INTERFACE.print("speedTable[i]>0){ 363 | INTERFACE.print(mRegs->speedTable[i]); 364 | INTERFACE.print(" 1>"); 365 | } else{ 366 | INTERFACE.print(-mRegs->speedTable[i]); 367 | INTERFACE.print(" 0>"); 368 | } 369 | } 370 | INTERFACE.print(""); 381 | 382 | INTERFACE.print(""); 388 | #elif COMM_TYPE == 1 389 | INTERFACE.print(Ethernet.localIP()); 390 | INTERFACE.print(">"); 391 | #endif 392 | 393 | Turnout::show(); 394 | Output::show(); 395 | 396 | break; 397 | 398 | /***** STORE SETTINGS IN EEPROM ****/ 399 | 400 | case 'E': // 401 | /* 402 | * stores settings for turnouts and sensors EEPROM 403 | * 404 | * returns: 405 | */ 406 | 407 | EEStore::store(); 408 | INTERFACE.print("data.nTurnouts); 410 | INTERFACE.print(" "); 411 | INTERFACE.print(EEStore::eeStore->data.nSensors); 412 | INTERFACE.print(" "); 413 | INTERFACE.print(EEStore::eeStore->data.nOutputs); 414 | INTERFACE.print(">"); 415 | break; 416 | 417 | /***** CLEAR SETTINGS IN EEPROM ****/ 418 | 419 | case 'e': // 420 | /* 421 | * clears settings for Turnouts in EEPROM 422 | * 423 | * returns: 424 | */ 425 | 426 | EEStore::clear(); 427 | INTERFACE.print(""); 428 | break; 429 | 430 | /***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ 431 | 432 | case ' ': // < > 433 | /* 434 | * simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window 435 | * 436 | * returns: a carriage return 437 | */ 438 | INTERFACE.println(""); 439 | break; 440 | 441 | /// 442 | /// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES 443 | /// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW 444 | /// 445 | 446 | /***** ENTER DIAGNOSTIC MODE ****/ 447 | 448 | case 'D': // 449 | /* 450 | * changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED 451 | * SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS 452 | */ 453 | 454 | Serial.println("\nEntering Diagnostic Mode..."); 455 | delay(1000); 456 | 457 | bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8 458 | bitSet(TCCR1B,CS11); 459 | bitClear(TCCR1B,CS10); 460 | 461 | #ifdef ARDUINO_AVR_UNO // Configuration for UNO 462 | 463 | bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4 464 | bitClear(TCCR0B,CS01); 465 | bitClear(TCCR0B,CS00); 466 | 467 | #else // Configuration for MEGA 468 | 469 | bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8 470 | bitSet(TCCR3B,CS31); 471 | bitClear(TCCR3B,CS30); 472 | 473 | #endif 474 | 475 | CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256 476 | CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS 477 | 478 | break; 479 | 480 | /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ 481 | 482 | case 'M': // 483 | /* 484 | * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track 485 | * 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 486 | * 487 | * 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 488 | * BYTE1: first hexidecimal byte in the packet 489 | * BYTE2: second hexidecimal byte in the packet 490 | * BYTE3: optional third hexidecimal byte in the packet 491 | * BYTE4: optional fourth hexidecimal byte in the packet 492 | * BYTE5: optional fifth hexidecimal byte in the packet 493 | * 494 | * returns: NONE 495 | */ 496 | mRegs->writeTextPacket(com+1); 497 | break; 498 | 499 | /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ 500 | 501 | case 'P': //

502 | /* 503 | * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track 504 | * 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 505 | * 506 | * 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 507 | * BYTE1: first hexidecimal byte in the packet 508 | * BYTE2: second hexidecimal byte in the packet 509 | * BYTE3: optional third hexidecimal byte in the packet 510 | * BYTE4: optional fourth hexidecimal byte in the packet 511 | * BYTE5: optional fifth hexidecimal byte in the packet 512 | * 513 | * returns: NONE 514 | */ 515 | pRegs->writeTextPacket(com+1); 516 | break; 517 | 518 | /***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/ 519 | 520 | case 'F': // 521 | /* 522 | * measure amount of free SRAM memory left on the Arduino based on trick found on the internet. 523 | * Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM. 524 | * Unfortunately not very reliable --- would be great to find a better method 525 | * 526 | * returns: 527 | * where MEM is the number of free bytes remaining in the Arduino's SRAM 528 | */ 529 | int v; 530 | INTERFACE.print(""); 533 | break; 534 | 535 | /***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/ 536 | 537 | case 'L': // 538 | /* 539 | * lists the packet contents of the main operations track registers and the programming track registers 540 | * FOR DIAGNOSTIC AND TESTING USE ONLY 541 | */ 542 | INTERFACE.println(""); 543 | for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){ 544 | INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t"); 545 | INTERFACE.print((int)p); INTERFACE.print("\t"); 546 | INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); 547 | INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); 548 | for(int i=0;i<10;i++){ 549 | INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); 550 | } 551 | INTERFACE.println(""); 552 | } 553 | for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){ 554 | INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t"); 555 | INTERFACE.print((int)p); INTERFACE.print("\t"); 556 | INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); 557 | INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); 558 | for(int i=0;i<10;i++){ 559 | INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); 560 | } 561 | INTERFACE.println(""); 562 | } 563 | INTERFACE.println(""); 564 | break; 565 | 566 | } // switch 567 | }; // SerialCommand::parse 568 | 569 | /////////////////////////////////////////////////////////////////////////////// 570 | 571 | 572 | -------------------------------------------------------------------------------- /DCCpp_Uno/SerialCommand.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | SerialCommand.h 4 | COPYRIGHT (c) 2013-2016 Gregg E. Berman 5 | 6 | Part of DCC++ BASE STATION for the Arduino 7 | 8 | **********************************************************************/ 9 | 10 | #ifndef SerialCommand_h 11 | #define SerialCommand_h 12 | 13 | #include "PacketRegister.h" 14 | #include "CurrentMonitor.h" 15 | 16 | #define MAX_COMMAND_LENGTH 30 17 | 18 | struct SerialCommand{ 19 | static char commandString[MAX_COMMAND_LENGTH+1]; 20 | static volatile RegisterList *mRegs, *pRegs; 21 | static CurrentMonitor *mMonitor; 22 | static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *); 23 | static void parse(char *); 24 | static void process(); 25 | }; // SerialCommand 26 | 27 | #endif 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What’s DCC++ 2 | ------------ 3 | 4 | DCC++ is an open-source hardware and software system for the operation of DCC-equipped model railroads. 5 | 6 | The system consists of two parts, the DCC++ Base Station and the DCC++ Controller. 7 | 8 | The DCC++ Base Station consists of an Arduino micro controller fitted with an Arduino Motor Shield that can be connected directly to the tracks of a model railroad. 9 | 10 | The DCC++ Controller provides operators with a customizable GUI to control their model railroad. It is written in Java using the Processing graphics library and IDE and communicates with the DCC++ Base Station via a standard serial connection over a USB cable or wireless over BlueTooth. 11 | 12 | What’s in this Repository 13 | ------------------------- 14 | 15 | This repository, BaseStation-Uno, contains a complete DCC++ Base Station sketch designed for compiling and uploading into an Arduino Uno. All sketch files are in the folder named DCCpp_Uno. More information about the sketch can be found in the included PDF file. 16 | 17 | To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Uno.ino within the DCCpp_Uno folder using your Arduino IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. 18 | 19 | The latest production release of the Master branch is 1.2.1: 20 | 21 | * Supports both the Arduino Uno and Arduino Mega 22 | * Built-in configuration for both the original Arduino Motor Shield as well as a Pololu MC33926 Motor Shield 23 | * Built-in configuration and support of Ethernet Shields (for use with Mega only) 24 | 25 | For more information on the overall DCC++ system, please follow the links in the PDF file. 26 | 27 | Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository 28 | 29 | The Master branch contains all of the Base Station functionality showed in the DCC++ YouTube channel with the exception of 2 layout-specific modules: 30 | 31 | * Control for an RGB LED Light Strip using pins 44, 45, and 46 on the Mega 32 | * An embedded AutoPilot routine that randomly selects a train to run through the entire layout, after which it is brought back into its original siding and the the patterns repeats with another randomly-selected train. This is the AutoPilot routine showed on the DCC++ YouTube channel. It does not require any computer, not DCC++ Controller to be running (DCC++ Controller contains a much more complicated 3-train Auto Pilot mode, also as shown on the DCC++ YouTube channel). 33 | 34 | Since these modules are very layout-specififc, they are not included in the Master branch. However, they are included in the Development branch. Please feel free to download and copy any relevant code to customize your own version of DCC++ Base Station. 35 | 36 | -December 27, 2015 37 | 38 | --------------------------------------------------------------------------------