DCCpp
This is the library version of a program for Arduino to control railroading DCC devices.
DCCpp.cpp
1 /*************************************************************
2 project: <DCCpp library>
3 author: <Thierry PARIS>
4 description: <DCCpp class>
5 *************************************************************/
6 
7 #include "DCCpp.h"
8 #include "arduino.h"
9 
10 DCCppClass DCCppClass::DCCppInstance;
11 
12 // NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS.
13 // NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES
14 
15 volatile RegisterList DCCppClass::mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets
16 volatile RegisterList DCCppClass::progRegs(2); // create a shorter list of only two registers for Program Track Packets
17 
18 CurrentMonitor DCCppClass::mainMonitor; // create monitor for current on Main Track
19 CurrentMonitor DCCppClass::progMonitor; // create monitor for current on Program Track
20 
21 // *********************************************************** FunctionsState
22 
23 FunctionsState::FunctionsState()
24 {
25  this->clear();
26 }
27 
28 void FunctionsState::clear()
29 {
30  // Clear all functions
31  this->activeFlags[0] = 0;
32  this->activeFlags[1] = 0;
33  this->activeFlags[2] = 0;
34  this->activeFlags[3] = 0;
35 }
36 
37 void FunctionsState::activate(byte inFunctionNumber)
38 {
39  bitSet(this->activeFlags[inFunctionNumber / 8], inFunctionNumber % 8);
40 }
41 
42 void FunctionsState::inactivate(byte inFunctionNumber)
43 {
44  bitClear(this->activeFlags[inFunctionNumber / 8], inFunctionNumber % 8);
45 }
46 
47 bool FunctionsState::isActivated(byte inFunctionNumber)
48 {
49  return bitRead(this->activeFlags[inFunctionNumber / 8], inFunctionNumber % 8);
50 }
51 
52 #ifdef DCCPP_DEBUG_MODE
53 void FunctionsState::printActivated()
54 {
55  for (int i = 0; i < 32; i++)
56  {
57  if (this->isActivated(i))
58  {
59  Serial.print(i);
60  Serial.print(" ");
61  }
62  }
63 
64  Serial.println("");
65 }
66 #endif
67 
68 // *********************************************************** end of FunctionsState
69 
70 // *********************************************************** DCCpp class
71 
72 DCCppClass::DCCppClass()
73 {
74  this->programMode = false;
75  this->panicStopped = false;
76 
77  DCCppConfig::SignalEnablePinMain = UNDEFINED_PIN;
78  DCCppConfig::CurrentMonitorMain = UNDEFINED_PIN;
79 
80  DCCppConfig::SignalEnablePinProg = UNDEFINED_PIN;
81  DCCppConfig::CurrentMonitorProg = UNDEFINED_PIN;
82 
83  DCCppConfig::DirectionMotorA = UNDEFINED_PIN;
84  DCCppConfig::DirectionMotorB = UNDEFINED_PIN;
85 
86  mainMonitor.begin(UNDEFINED_PIN, "");
87  progMonitor.begin(UNDEFINED_PIN, "");
88 }
89 
90 static bool first = true;
91 
93 // MAIN ARDUINO LOOP
95 
96 void DCCppClass::loop()
97 {
98 #ifdef USE_TEXTCOMMAND
99  TextCommand::process(); // check for, and process, and new serial commands
100 #endif
101 
102  if (first)
103  {
104  first = false;
105 #if defined(DCCPP_DEBUG_MODE) && defined(DCCPP_PRINT_DCCPP)
106  showConfiguration();
107 #endif
108  }
109 
110  if (CurrentMonitor::checkTime())
111  { // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
112  this->mainMonitor.check();
113  this->progMonitor.check();
114  }
115 
116 #ifdef USE_SENSOR
117  Sensor::check(); // check sensors for activate/de-activate
118 #endif
119 }
120 
122 // INITIAL SETUP
124 
125 // For Arduino or Pololu shields, signalPinMain must be connected to Direction motor A, and signalPinProg to Direction motor B
126 // If a track is not connected, main or prog, the signalPin should stay to default at 255 (UNDEFINED_PIN).
127 // For H bridge connected directly to the pins, like LMD18200, signalPin and Direction motor should have the same pin number.
128 
129 // For Arduino Motor Shield
130 // beginMain(MOTOR_SHIELD_DIRECTION_MOTOR_CHANNEL_PIN_A, DCC_SIGNAL_PIN_MAIN, MOTOR_SHIELD_SIGNAL_ENABLE_PIN_MAIN, MOTOR_SHIELD_CURRENT_MONITOR_PIN_MAIN);
131 // beginProg(MOTOR_SHIELD_DIRECTION_MOTOR_CHANNEL_PIN_B, DCC_SIGNAL_PIN_PROG, MOTOR_SHIELD_SIGNAL_ENABLE_PIN_PROG, MOTOR_SHIELD_CURRENT_MONITOR_PIN_PROG);
132 
133 // For Pololu Motor Shield
134 // beginMain(POLOLU_DIRECTION_MOTOR_CHANNEL_PIN_A, DCC_SIGNAL_PIN_MAIN, POLOLU_SIGNAL_ENABLE_PIN_MAIN, POLOLU_CURRENT_MONITOR_PIN_MAIN);
135 // beginProg(POLOLU_DIRECTION_MOTOR_CHANNEL_PIN_B, DCC_SIGNAL_PIN_PROG, POLOLU_SIGNAL_ENABLE_PIN_PROG, POLOLU_CURRENT_MONITOR_PIN_PROG);
136 
137 // For single LMD18200
138 // beginMain(UNDEFINED_PIN, DCC_SIGNAL_PIN_MAIN, 3, A0);
139 
140 // For double LMD18200
141 // beginMain(UNDEFINED_PIN, DCC_SIGNAL_PIN_MAIN, 3, A0);
142 // beginProg(UNDEFINED_PIN, DCC_SIGNAL_PIN_PROG, 11, A1);
143 
144 void DCCppClass::beginMain(uint8_t inOptionalDirectionMotor, uint8_t inSignalPin, uint8_t inSignalEnable, uint8_t inCurrentMonitor)
145 {
146  DCCppConfig::DirectionMotorA = inOptionalDirectionMotor;
147  DCCppConfig::SignalEnablePinMain = inSignalEnable; // PWM
148  DCCppConfig::CurrentMonitorMain = inCurrentMonitor;
149 
150  // If no main line, exit.
151  if (DCCppConfig::SignalEnablePinMain == UNDEFINED_PIN)
152  {
153 #ifdef DCCPP_DEBUG_MODE
154  Serial.println("No main line");
155 #endif
156  return;
157  }
158 
159  this->mainMonitor.begin(DCCppConfig::CurrentMonitorMain, (char *) "<p2>");
160 
161  // CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS
162 
163  // Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK
164  // Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin
165  // Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency
166  // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
167 
168 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
169 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
170 
171 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
172 #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
173  if (DCCppConfig::DirectionMotorA != UNDEFINED_PIN)
174  {
175  pinMode(DCCppConfig::DirectionMotorA, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
176  digitalWrite(DCCppConfig::DirectionMotorA, LOW);
177  }
178 
179  pinMode(inSignalPin, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A
180 
181  bitSet(TCCR1A, WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A
182  bitSet(TCCR1A, WGM11);
183  bitSet(TCCR1B, WGM12);
184  bitSet(TCCR1B, WGM13);
185 
186  bitSet(TCCR1A, COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary)
187  bitSet(TCCR1A, COM1B0);
188 
189  bitClear(TCCR1B, CS12); // set Timer 1 prescale=1
190  bitClear(TCCR1B, CS11);
191  bitSet(TCCR1B, CS10);
192 
193  OCR1A = DCC_ONE_BIT_TOTAL_DURATION_TIMER1;
194  OCR1B = DCC_ONE_BIT_PULSE_DURATION_TIMER1;
195 
196  pinMode(DCCppConfig::SignalEnablePinMain, OUTPUT); // master enable for motor channel A
197 
198  mainRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
199 
200  bitSet(TIMSK1, OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B)
201  digitalWrite(DCCppConfig::SignalEnablePinMain, LOW);
202 
203 #ifdef DCCPP_DEBUG_MODE
204  Serial.println(F("beginMain achivied"));
205 #endif
206 }
207 
208 void DCCppClass::beginProg(uint8_t inOptionalDirectionMotor, uint8_t inSignalPin, uint8_t inSignalEnable, uint8_t inCurrentMonitor)
209 {
210  DCCppConfig::DirectionMotorB = inOptionalDirectionMotor;
211  DCCppConfig::SignalEnablePinProg = inSignalEnable;
212  DCCppConfig::CurrentMonitorProg = inCurrentMonitor;
213 
214  // If no prog line, exit.
215  if (DCCppConfig::SignalEnablePinProg == UNDEFINED_PIN)
216  {
217 #ifdef DCCPP_DEBUG_MODE
218  Serial.println("No prog line");
219 #endif
220  return;
221  }
222 
223  this->progMonitor.begin(DCCppConfig::CurrentMonitorProg, (char *) "<p3>");
224 
225  // CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS
226 
227 #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) // Configuration for UNO
228 
229  // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
230  // Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin
231  // Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency
232  // 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
233 
234 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49
235 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24
236 
237 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28
238 #define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14
239 
240  if (DCCppConfig::DirectionMotorB != UNDEFINED_PIN)
241  {
242  pinMode(DCCppConfig::DirectionMotorB, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
243  digitalWrite(DCCppConfig::DirectionMotorB, LOW);
244  }
245 
246  pinMode(inSignalPin, OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
247 
248  bitSet(TCCR0A, WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A
249  bitSet(TCCR0A, WGM01);
250  bitSet(TCCR0B, WGM02);
251 
252  bitSet(TCCR0A, COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary)
253  bitSet(TCCR0A, COM0B0);
254 
255  bitClear(TCCR0B, CS02); // set Timer 0 prescale=64
256  bitSet(TCCR0B, CS01);
257  bitSet(TCCR0B, CS00);
258 
259  OCR0A = DCC_ONE_BIT_TOTAL_DURATION_TIMER0;
260  OCR0B = DCC_ONE_BIT_PULSE_DURATION_TIMER0;
261 
262  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT); // master enable for motor channel B
263 
264  progRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
265 
266  bitSet(TIMSK0, OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B)
267 
268 #else // Configuration for MEGA
269 
270  // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
271  // Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin
272  // Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency
273  // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
274 
275 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199
276 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599
277 
278 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855
279 #define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927
280 
281  if (DCCppConfig::DirectionMotorB != UNDEFINED_PIN)
282  {
283  pinMode(DCCppConfig::DirectionMotorB, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
284  digitalWrite(DCCppConfig::DirectionMotorB, LOW);
285  }
286 
287  pinMode(DCC_SIGNAL_PIN_PROG, OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
288 
289  bitSet(TCCR3A, WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A
290  bitSet(TCCR3A, WGM31);
291  bitSet(TCCR3B, WGM32);
292  bitSet(TCCR3B, WGM33);
293 
294  bitSet(TCCR3A, COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary)
295  bitSet(TCCR3A, COM3B0);
296 
297  bitClear(TCCR3B, CS32); // set Timer 3 prescale=1
298  bitClear(TCCR3B, CS31);
299  bitSet(TCCR3B, CS30);
300 
301  OCR3A = DCC_ONE_BIT_TOTAL_DURATION_TIMER3;
302  OCR3B = DCC_ONE_BIT_PULSE_DURATION_TIMER3;
303 
304  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT); // master enable for motor channel B
305 
306  progRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
307 
308  bitSet(TIMSK3, OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B)
309 
310 #endif
311  digitalWrite(DCCppConfig::SignalEnablePinProg, LOW);
312 
313 #ifdef DCCPP_DEBUG_MODE
314  Serial.println(F("beginProg achivied"));
315 #endif
316 }
317 
318 void DCCppClass::begin()
319 {
320 #ifdef SDCARD_CS
321  pinMode(SDCARD_CS, OUTPUT);
322  digitalWrite(SDCARD_CS, HIGH); // Deselect the SD card
323 #endif
324 
325 #ifdef USE_EEPROM
326  EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM
327  if (EEStore::needsRefreshing())
328  EEStore::store();
329 #endif
330 
331 #ifdef DCCPP_DEBUG_MODE
332  //pinMode(LED_BUILTIN, OUTPUT);
333  Serial.println(F("begin achivied"));
334 #endif
335 
336 } // begin
337 
338 #ifdef USE_ETHERNET
339 void DCCppClass::beginEthernet(uint8_t *inMac, uint8_t *inIp, EthernetProtocol inProtocol)
340 {
341  for (int i = 0; i < 4; i++)
342  DCCppConfig::EthernetIp[i] = inIp[i];
343  for (int i = 0; i < 6; i++)
344  DCCppConfig::EthernetMac[i] = inMac[i];
345 
346  DCCppConfig::Protocol = inProtocol;
347 
348  if (inIp == NULL)
349  Ethernet.begin(inMac); // Start networking using DHCP to get an IP Address
350  else
351  Ethernet.begin(inMac, inIp); // Start networking using STATIC IP Address
352 
353  INTERFACE.begin();
354 #ifdef DCCPP_DEBUG_MODE
355  //pinMode(LED_BUILTIN, OUTPUT);
356  showConfiguration();
357  Serial.println(F("beginEthernet achivied"));
358 #endif
359 } // beginEthernet
360 #endif
361 
363  // DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
365 
366  // The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
367  // It is designed to read the current bit of the current register packet and
368  // updates the OCNA and OCNB counters of Timer-N to values that will either produce
369  // a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
370  // DCC ZERO and DCC ONE bits.
371 
372  // These are hardware-driven interrupts that will be called automatically when triggered regardless of what
373  // DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled.
374  // Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
375  // (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
376  // in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic
377  // of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB
378  // registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
379  // interrupt code completes and can be called again.
380 
381  // A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
382  // DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
383 
384  // Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
385  // Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro
386  // that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating
387  // some of the logic for each interrupt, but saves additional time.
388 
389  // As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
390  // when a new register is loaded and the logic needs to switch active register packet pointers.
391 
392  // THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1)
393 
394 #define DCC_SIGNAL(R,N)
395  if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */
396  R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */
397  if (R.nRepeat>0 && R.currentReg == R.reg) { /* IF current Register is first Register AND should be repeated */
398  R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */
399  }
400  else if (R.nextReg != NULL) { /* ELSE IF another Register has been updated */
401  R.currentReg = R.nextReg; /* update currentReg to nextReg */
402  R.nextReg = NULL; /* reset nextReg to NULL */
403  R.tempPacket = R.currentReg->activePacket; /* flip active and update Packets */
404  R.currentReg->activePacket = R.currentReg->updatePacket;
405  R.currentReg->updatePacket = R.tempPacket;
406  }
407  else { /* ELSE simply move to next Register */
408  if (R.currentReg == R.maxLoadedReg) /* BUT IF this is last Register loaded */
409  R.currentReg = R.reg; /* first reset currentReg to base Register, THEN */
410  R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
411  } /* END-ELSE */
412  } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
413 
414  if (R.currentReg->activePacket->buf[R.currentBit / 8] & R.bitMask[R.currentBit % 8]) { /* IF bit is a ONE */
415  OCR ## N ## A = DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */
416  OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */
417  } else{ /* ELSE it is a ZERO */
418  OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */
419  OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */
420  } /* END-ELSE */
421 
422  R.currentBit++; /* point to next bit in current Packet */
423 
425 // NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
426 
427 ISR(TIMER1_COMPB_vect) { // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
428  DCC_SIGNAL(DCCppClass::mainRegs, 1)
429 }
430 
431 #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) // Configuration for UNO
432 
433 ISR(TIMER0_COMPB_vect) { // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
434  DCC_SIGNAL(DCCppClass::progRegs, 0)
435 }
436 
437 #else // Configuration for MEGA
438 
439 ISR(TIMER3_COMPB_vect) { // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
440  DCC_SIGNAL(DCCppClass::progRegs, 3)
441 }
442 
443 #endif
444 
445 #ifdef DCCPP_PRINT_DCCPP
446 // PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE
448 // - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH
449 
450 void DCCppClass::showConfiguration()
451 {
452  Serial.println(F("*** DCCpp LIBRARY ***"));
453 
454  Serial.print(F("VERSION DCC++: "));
455  Serial.println(VERSION);
456  Serial.println(F("VERSION DCCpp library: 0.7.0"));
457  Serial.print(F("COMPILED: "));
458  Serial.print(__DATE__);
459  Serial.print(F(" "));
460  Serial.println(__TIME__);
461 
462  //Serial.print(F("nARDUINO: "));
463  //Serial.print(ARDUINO_TYPE);
464 
465  //Serial.print(F("nnMOTOR SHIELD: "));
466  //Serial.print(MOTOR_SHIELD_NAME);
467 
468  if (DCCppConfig::SignalEnablePinMain!= UNDEFINED_PIN)
469  {
470  Serial.print(F("nnDCC SIG MAIN(DIR): "));
471  Serial.println(DCC_SIGNAL_PIN_MAIN);
472  Serial.print(F(" DIRECTION: "));
473  Serial.println(DCCppConfig::DirectionMotorA);
474  Serial.print(F(" ENABLE(PWM): "));
475  Serial.println(DCCppConfig::SignalEnablePinMain);
476  Serial.print(F(" CURRENT: "));
477  Serial.println(DCCppConfig::CurrentMonitorMain);
478  }
479 
480  if (DCCppConfig::SignalEnablePinProg!= UNDEFINED_PIN)
481  {
482  Serial.print(F("nnDCC SIG PROG(DIR): "));
483  Serial.println(DCC_SIGNAL_PIN_PROG);
484  Serial.print(F(" DIRECTION: "));
485  Serial.println(DCCppConfig::DirectionMotorB);
486  Serial.print(F(" ENABLE(PWM): "));
487  Serial.println(DCCppConfig::SignalEnablePinProg);
488  Serial.print(F(" CURRENT: "));
489  Serial.println(DCCppConfig::CurrentMonitorProg);
490  }
491 #if defined(USE_EEPROM)
492 #if defined(USE_TURNOUT)
493  Serial.print(F("nnNUM TURNOUTS: "));
494  Serial.println(EEStore::eeStore->data.nTurnouts);
495 #endif
496 #if defined(USE_SENSOR)
497  Serial.print(F(" SENSORS: "));
498  Serial.println(EEStore::eeStore->data.nSensors);
499 #endif
500 #if defined(USE_OUTPUT)
501  Serial.print(F(" OUTPUTS: "));
502  Serial.println(EEStore::eeStore->data.nOutputs);
503 #endif
504 #endif
505 
506 #ifdef USE_TEXTCOMMAND
507  Serial.print(F("nnINTERFACE: "));
508 #ifdef USE_ETHERNET
509  Serial.println(F("ETHERNET "));
510  Serial.print(F("MAC ADDRESS: "));
511  for (int i = 0; i<5; i++) {
512  Serial.print(DCCppConfig::EthernetMac[i], HEX);
513  Serial.print(F(":"));
514  }
515  Serial.println(DCCppConfig::EthernetMac[5], HEX);
516 // Serial.print(F("PORT: "));
517 // Serial.println(DCCppConfig::EthernetPort);
518  Serial.print(F("IP ADDRESS: "));
519  Serial.println(Ethernet.localIP());
520 
521 /*#ifdef IP_ADDRESS
522  Serial.println(F(" (STATIC)"));
523 #else
524  Serial.println(F(" (DHCP)"));
525 #endif*/
526 
527 #else
528  Serial.println(F("SERIAL"));
529 #endif
530 
531 #endif
532 // Serial.print(F("nnPROGRAM HALTED - PLEASE RESTART ARDUINO"));
533 
534 // while (true);
535 // Serial.println("");
536 }
537 #endif
538 
539 void DCCppClass::panicStop(bool inStop)
540 {
541  this->panicStopped = inStop;
542 
543 #ifdef DCCPP_DEBUG_MODE
544  Serial.print(F("DCCpp PanicStop "));
545  Serial.println(inStop ? F("pressed"):F("canceled"));
546 #endif
547 
548  /* activate or not the current output on rails */
549 
550  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
551  digitalWrite(DCCppConfig::SignalEnablePinMain, inStop ? LOW : HIGH);
552  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
553  digitalWrite(DCCppConfig::SignalEnablePinProg, inStop ? LOW : HIGH);
554 }
555 
556 void DCCppClass::powerOn()
557 {
558  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
559  digitalWrite(DCCppConfig::SignalEnablePinProg, HIGH);
560  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
561  digitalWrite(DCCppConfig::SignalEnablePinMain, HIGH);
562  INTERFACE.print("<p1>");
563 #if !defined(USE_ETHERNET)
564  INTERFACE.println("");
565 #endif
566 }
567 
568 void DCCppClass::powerOff()
569 {
570  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
571  digitalWrite(DCCppConfig::SignalEnablePinProg, LOW);
572  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
573  digitalWrite(DCCppConfig::SignalEnablePinMain, LOW);
574  INTERFACE.print("<p0>");
575 #if !defined(USE_ETHERNET)
576  INTERFACE.println("");
577 #endif
578 }
579 
580 /***************************** Driving functions */
581 
582 bool DCCppClass::setThrottle(volatile RegisterList *inpRegs, int nReg, int inLocoId, int inStepsNumber, int inNewSpeed, bool inToLeft)
583 {
584  int val = 0;
585 
586  if (this->panicStopped)
587  val = 1;
588  else
589  if (inNewSpeed > 0)
590  val = map(inNewSpeed, 0, inStepsNumber, 2, 127);
591 
592 #ifdef DCCPP_DEBUG_MODE
593  Serial.print(F("DCCpp SetSpeed "));
594  Serial.print(inNewSpeed);
595  Serial.print(F("/"));
596  Serial.print(inStepsNumber);
597  Serial.print(F(" (in Dcc "));
598  Serial.print(val);
599  Serial.println(F(" )"));
600 #endif
601 
602  inpRegs->setThrottle(nReg, inLocoId, val, inToLeft);
603 
604  return true;
605 }
606 
607 void DCCppClass::setFunctions(volatile RegisterList *inpRegs, int nReg, int inLocoId, FunctionsState inStates)
608 {
609  byte flags = 0;
610 
611  byte oneByte1 = 128; // Group one functions F0-F4
612  byte twoByte1 = 176; // Group two F5-F8
613  byte threeByte1 = 160; // Group three F9-F12
614  byte fourByte2 = 0; // Group four F13-F20
615  byte fiveByte2 = 0; // Group five F21-F28
616 
617  for (byte func = 0; func <= 28; func++)
618  {
619  if (func <= 4)
620  {
621  /*
622  * To set functions F0 - F4 on(= 1) or off(= 0) :
623  *
624  * BYTE1 : 128 + F1 * 1 + F2 * 2 + F3 * 4 + F4 * 8 + F0 * 16
625  * BYTE2 : omitted
626  */
627 
628  flags |= 1;
629  if (inStates.isActivated(func))
630  {
631  if (func == 0)
632  oneByte1 += 16;
633  else
634  oneByte1 += (1 << (func - 1));
635  }
636  }
637  else if (func <= 8)
638  {
639  /*
640  * To set functions F5 - F8 on(= 1) or off(= 0) :
641  *
642  * BYTE1 : 176 + F5 * 1 + F6 * 2 + F7 * 4 + F8 * 8
643  * BYTE2 : omitted
644  */
645 
646  flags |= 2;
647  if (inStates.isActivated(func))
648  twoByte1 += (1 << (func - 5));
649  }
650  else if (func <= 12)
651  {
652  /*
653  * To set functions F9 - F12 on(= 1) or off(= 0) :
654  *
655  * BYTE1 : 160 + F9 * 1 + F10 * 2 + F11 * 4 + F12 * 8
656  * BYTE2 : omitted
657  */
658 
659  flags |= 4;
660  if (inStates.isActivated(func))
661  threeByte1 += (1 << (func - 9));
662  }
663  else if (func <= 20)
664  {
665  /*
666  * To set functions F13 - F20 on(= 1) or off(= 0) :
667  *
668  * BYTE1 : 222
669  * BYTE2 : F13 * 1 + F14 * 2 + F15 * 4 + F16 * 8 + F17 * 16 + F18 * 32 + F19 * 64 + F20 * 128
670  */
671 
672  flags |= 8;
673  if (inStates.isActivated(func))
674  fourByte2 += (1 << (func - 13));
675  }
676  else if (func <= 28)
677  {
678  /*
679  * To set functions F21 - F28 on(= 1) of off(= 0) :
680  *
681  * BYTE1 : 223
682  * BYTE2 : F21 * 1 + F22 * 2 + F23 * 4 + F24 * 8 + F25 * 16 + F26 * 32 + F27 * 64 + F28 * 128
683  */
684 
685  flags |= 16;
686  if (inStates.isActivated(func))
687  fiveByte2 += (1 << (func - 21));
688  }
689  }
690 
691  if (flags & 1)
692  inpRegs->setFunction(nReg, inLocoId, oneByte1, -1);
693  if (flags & 2)
694  inpRegs->setFunction(nReg, inLocoId, twoByte1, -1);
695  if (flags & 4)
696  inpRegs->setFunction(nReg, inLocoId, threeByte1, -1);
697  if (flags & 8)
698  inpRegs->setFunction(nReg, inLocoId, 222, fourByte2);
699  if (flags & 16)
700  inpRegs->setFunction(nReg, inLocoId, 223, fiveByte2);
701 
702 #ifdef DCCPP_DEBUG_MODE
703  Serial.print(F("DCCpp SetFunctions for loco"));
704  Serial.print(inLocoId);
705  Serial.print(" / Activated : ");
706  inStates.printActivated();
707 #endif
708 }
709 
710 void DCCppClass::writeCv(volatile RegisterList *inReg, int inLocoId, int inCv, byte inValue)
711 {
712  inReg->writeCVByte(inCv, inValue, 100, 101);
713 
714 #ifdef DCCPP_DEBUG_MODE
715  Serial.print(F("DCCpp WriteCv "));
716  Serial.print(inCv);
717  Serial.print(F(" : "));
718  Serial.println(inValue);
719 #endif
720 }
721 
722 int DCCppClass::readCv(volatile RegisterList *inReg, int inLocoId, byte inCv)
723 {
724  return inReg->readCVmain(1, 100+inCv, 100+inCv);
725 }
726 
727 void DCCppClass::setAccessory(int inAddress, byte inSubAddress, byte inActivate)
728 {
729  this->mainRegs.setAccessory(inAddress, inSubAddress, inActivate);
730 
731 #ifdef DCCPP_DEBUG_MODE
732  Serial.print(F("DCCpp AccessoryOperation "));
733  Serial.print(inAddress);
734  Serial.print(F(" / "));
735  Serial.print(inSubAddress);
736  Serial.print(F(" : "));
737  Serial.println(inActivate);
738 #endif
739 }
740