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