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