DCCpp
This is the library version of a program for Arduino to control railroading DCC devices.
DccSignalUno.cpp
1 /*************************************************************
2 project: <DCCpp library>
3 author: <Thierry PARIS>
4 description: <DCCpp signal for Uno/Nano ARduino>
5 *************************************************************/
6 
7 #include "Arduino.h"
8 
9 #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega2560__) // Configuration for UNO/MEGA
10 
11 #include "DCCpp.h"
12 
13 void DCCpp::beginMainDccSignal(uint8_t inSignalPin)
14 {
15 #ifndef USE_ONLY1_INTERRUPT
16  // CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS
17 
18  // Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK
19  // Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin
20  // Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency
21  // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
22 
23 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
24 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
25 
26 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
27 #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
28  if (DCCppConfig::DirectionMotorA != UNDEFINED_PIN)
29  {
30  pinMode(DCCppConfig::DirectionMotorA, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
31  digitalWrite(DCCppConfig::DirectionMotorA, LOW);
32  }
33 
34  if (inSignalPin != UNDEFINED_PIN)
35  pinMode(inSignalPin, OUTPUT); // FOR SHIELDS, THIS ARDUINO OUTPUT PIN MUST BE PHYSICALY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A
36 
37  bitSet(TCCR1A, WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A
38  bitSet(TCCR1A, WGM11);
39  bitSet(TCCR1B, WGM12);
40  bitSet(TCCR1B, WGM13);
41 
42  bitSet(TCCR1A, COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary)
43  bitSet(TCCR1A, COM1B0);
44 
45  bitClear(TCCR1B, CS12); // set Timer 1 prescale=1
46  bitClear(TCCR1B, CS11);
47  bitSet(TCCR1B, CS10);
48 
49  OCR1A = DCC_ONE_BIT_TOTAL_DURATION_TIMER1;
50  OCR1B = DCC_ONE_BIT_PULSE_DURATION_TIMER1;
51 
52  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
53  pinMode(DCCppConfig::SignalEnablePinMain, OUTPUT); // master enable for motor channel A
54 
55  mainRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
56 
57  bitSet(TIMSK1, OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B)
58 #else
59  if (inSignalPin != UNDEFINED_PIN) {
60  pinMode(inSignalPin, OUTPUT);
61  DCCppConfig::SignalPortMaskMain = digitalPinToBitMask(inSignalPin);
62  DCCppConfig::SignalPortInMain = portInputRegister(digitalPinToPort(inSignalPin));
63  }
64 
65  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
66  pinMode(DCCppConfig::SignalEnablePinMain, OUTPUT);
67 
68  /*
69  Use the platform's timer2
70  The timer2 has an 8 bit counter
71  Presclaler = 8 to scale down the 16MHz Arduino processor frequency so that we can use an
72  8bit counter. We need to interrupt every 58us. The frequency of interrupt is thus
73  1.000.000 / 58 = 17241 = 17,241kHz = f
74  So values are:
75  TCCR2A = 1 << WGM21; Set the TCM mode "Clear Timer on Compare Match"
76  TCCR2B: CS22 = 0; CS21 = 1; CS20 = 0 <- prescaler = 8
77  OCR2A = 115; The frequency of the interrupt, OCR2A = 16MHz / (f * prescaler) - 1
78  so in our case (16.000.000 / (17.241 * 8 )) - 1
79  TIMSK2 = 1 << OCIE2A; Enable timer compare interrupt
80  */
81 
82  noInterrupts();
83 
84  TCCR2A = 1 << WGM21; // CTC
85  TCCR2B = (0 << CS22) | (1 << CS21) | (0 << CS20); // prescale 8
86 
87  OCR2A = 115; // Gives interrupt every 58us (for 16 Mhz)
88  bitSet (TIMSK2, OCIE2A); // enable interrupt
89 
90  interrupts();
91 
92  mainRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
93 #endif
94 }
95 
96 void DCCpp::beginProgDccSignal(uint8_t inSignalPin)
97 {
98 #ifndef USE_ONLY1_INTERRUPT
99  // CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS
100 
101 #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) // Configuration for UNO
102 
103  // Direction Pin for Motor Shield Channel B - PROGRAMMING TRACK
104  // Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin
105  // Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency
106  // 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
107 
108 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49
109 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24
110 
111 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28
112 #define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14
113 
114  if (DCCppConfig::DirectionMotorB != UNDEFINED_PIN)
115  {
116  pinMode(DCCppConfig::DirectionMotorB, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
117  digitalWrite(DCCppConfig::DirectionMotorB, LOW);
118  }
119 
120  if (inSignalPin != UNDEFINED_PIN)
121  pinMode(inSignalPin, OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
122 
123  bitSet(TCCR0A, WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A
124  bitSet(TCCR0A, WGM01);
125  bitSet(TCCR0B, WGM02);
126 
127  bitSet(TCCR0A, COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary)
128  bitSet(TCCR0A, COM0B0);
129 
130  bitClear(TCCR0B, CS02); // set Timer 0 prescale=64
131  bitSet(TCCR0B, CS01);
132  bitSet(TCCR0B, CS00);
133 
134  OCR0A = DCC_ONE_BIT_TOTAL_DURATION_TIMER0;
135  OCR0B = DCC_ONE_BIT_PULSE_DURATION_TIMER0;
136 
137  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
138  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT); // master enable for motor channel B
139 
140  progRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
141 
142  bitSet(TIMSK0, OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B)
143 
144 #else // Configuration for MEGA
145 
146  // Direction Pin for Motor Shield Channel B - PROGRAMMING TRACK
147  // Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin
148  // Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency
149  // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
150 
151 #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199
152 #define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599
153 
154 #define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855
155 #define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927
156 
157  if (DCCppConfig::DirectionMotorB != UNDEFINED_PIN)
158  {
159  pinMode(DCCppConfig::DirectionMotorB, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
160  digitalWrite(DCCppConfig::DirectionMotorB, LOW);
161  }
162 
163  pinMode(DCC_SIGNAL_PIN_PROG, OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
164 
165  bitSet(TCCR3A, WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A
166  bitSet(TCCR3A, WGM31);
167  bitSet(TCCR3B, WGM32);
168  bitSet(TCCR3B, WGM33);
169 
170  bitSet(TCCR3A, COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary)
171  bitSet(TCCR3A, COM3B0);
172 
173  bitClear(TCCR3B, CS32); // set Timer 3 prescale=1
174  bitClear(TCCR3B, CS31);
175  bitSet(TCCR3B, CS30);
176 
177  OCR3A = DCC_ONE_BIT_TOTAL_DURATION_TIMER3;
178  OCR3B = DCC_ONE_BIT_PULSE_DURATION_TIMER3;
179 
180  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
181  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT); // master enable for motor channel B
182 
183  progRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
184 
185  bitSet(TIMSK3, OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B)
186 
187 #endif
188  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
189  digitalWrite(DCCppConfig::SignalEnablePinProg, LOW);
190 
191 #else
192  if (inSignalPin != UNDEFINED_PIN) {
193  pinMode(inSignalPin, OUTPUT);
194  DCCppConfig::SignalPortMaskProg = digitalPinToBitMask(inSignalPin);
195  DCCppConfig::SignalPortInProg = portInputRegister(digitalPinToPort(inSignalPin));
196  }
197 
198  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
199  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT);
200 
201  // same code as in beginMain, it uses the same timer2.
202  noInterrupts();
203 
204  bitSet (TCCR2A, WGM21); // CTC
205  TCCR2A = 1 << WGM21; // CTC
206  TCCR2B = (0 << CS22) | (1 << CS21) | (0 << CS20); // prescale 8
207  OCR2A = 115; // Gives interrupt every 58us (for 16 Mhz)
208  bitSet (TIMSK2, OCIE2A); // enable interrupt
209 
210  interrupts();
211 #endif
212 }
213 
214 #ifndef USE_ONLY1_INTERRUPT
215  // DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
218 
219  // The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
220  // It is designed to read the current bit of the current register packet and
221  // updates the OCNA and OCNB counters of Timer-N to values that will either produce
222  // a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
223  // DCC ZERO and DCC ONE bits.
224 
225  // These are hardware-driven interrupts that will be called automatically when triggered regardless of what
226  // DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily disabled.
227  // Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
228  // (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
229  // in much less than 58 microseconds, otherwise there would be no time for the rest of the program to run. Worse, if the logic
230  // of the interrupt code ever caused it to run longer than 58 microseconds, an interrupt trigger would be missed, the OCNA and OCNB
231  // registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
232  // interrupt code completes and can be called again.
233 
234  // A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
235  // DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
236 
237  // Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
238  // Programming Track OCOB interrupt. But rather than create a generic function that incurs additional overhead, we create a macro
239  // that can be invoked with proper parameters for each interrupt. This slightly increases the size of the code base by duplicating
240  // some of the logic for each interrupt, but saves additional time.
241 
242  // As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
243  // when a new register is loaded and the logic needs to switch active register packet pointers.
244 
245  // THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1)
246 
247 #define DCC_SIGNAL(R,N)
248  if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */
249  R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */
250  if (R.nRepeat>0 && R.currentReg == R.reg) { /* IF current Register is first Register AND should be repeated */
251  R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */
252  }
253  else if (R.nextReg != NULL) { /* ELSE IF another Register has been updated */
254  R.currentReg = R.nextReg; /* update currentReg to nextReg */
255  R.nextReg = NULL; /* reset nextReg to NULL */
256  R.tempPacket = R.currentReg->activePacket; /* flip active and update Packets */
257  R.currentReg->activePacket = R.currentReg->updatePacket;
258  R.currentReg->updatePacket = R.tempPacket;
259  }
260  else { /* ELSE simply move to next Register */
261  if (R.currentReg == R.maxLoadedReg) /* BUT IF this is last Register loaded */
262  R.currentReg = R.reg; /* first reset currentReg to base Register, THEN */
263  R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
264  } /* END-ELSE */
265  } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
266 
267  if (R.currentReg->activePacket->buf[R.currentBit / 8] & R.bitMask[R.currentBit % 8]) { /* IF bit is a ONE */
268  OCR ## N ## A = DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */
269  OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */
270  } else{ /* ELSE it is a ZERO */
271  OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */
272  OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */
273  } /* END-ELSE */
274 
275  R.currentBit++; /* point to next bit in current Packet */
276 
278 // NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
279 
280 ISR(TIMER1_COMPB_vect) { // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
281  DCC_SIGNAL(DCCpp::mainRegs, 1)
282 }
283 
284 #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) // Configuration for UNO
285 
286 ISR(TIMER0_COMPB_vect) { // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Programming Track
287  DCC_SIGNAL(DCCpp::progRegs, 0)
288 }
289 
290 #else // Configuration for MEGA
291 
292 ISR(TIMER3_COMPB_vect) { // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Programming Track
293  DCC_SIGNAL(DCCpp::progRegs, 3)
294 }
295 #endif
296 
297 #else
298  // DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
301 
302  // The code below will be called every time an timer interrupt is triggered.
303  // It is designed to read the current bit of the current register packet and
304  // toggles the specified pin for a long (200 microsecond) or a short (116 microsecond) pulse,
305  // which respectively represent DCC ZERO and DCC ONE bits.
306 
307  // Note that we need to create two very similar copies of the code --- one for the Main Track and one for the
308  // Programming Track. But rather than create a generic function that incurs additional overhead, we create a macro
309  // that can be invoked with proper parameters for each interrupt. This slightly increases the size of the code base by duplicating
310  // some of the logic for each interrupt, but saves additional time.
311 
312  // THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=Main or Prog postfix
313 
314 #define DCC_NEXT_BIT(R)
315  if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */
316  R.currentBit = 0; /* reset current bit pointer and determine which Register and Packet to process next--- */
317  if (R.nRepeat>0 && R.currentReg == R.reg){ /* IF current Register is first Register AND should be repeated */
318  R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */
319  }
320  else if (R.nextReg != NULL){ /* ELSE IF another Register has been updated */
321  R.currentReg = R.nextReg; /* update currentReg to nextReg */
322  R.nextReg = NULL; /* reset nextReg to NULL */
323  R.tempPacket = R.currentReg->activePacket; /* flip active and update Packets */
324  R.currentReg->activePacket = R.currentReg->updatePacket;
325  R.currentReg->updatePacket = R.tempPacket;
326  }
327  else { /* ELSE simply move to next Register */
328  if (R.currentReg == R.maxLoadedReg) /* BUT IF this is last Register loaded */
329  R.currentReg = R.reg; /* first reset currentReg to base Register, THEN */
330  R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
331  } /* END-ELSE */
332  } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
333  if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]) {
334  /* For 1 bit, we need 1 periods of 58us timer ticks for each signal level */
335  R.timerPeriods = 1;
336  R.timerPeriodsLeft = 2;
337  } else { /* ELSE it is a ZERO bit */
338  /* For 0 bit, we need 2 period of 58us timer ticks for each signal level. */
339  R.timerPeriods = 2;
340  R.timerPeriodsLeft = 4;
341  } /* END-ELSE */
342 
343  R.currentBit++; /* point to next bit in current Packet */
344 
346 
347 /*
348  For each bit, toggle pin twice: when timer counts to timerPeriods of
349  the register, and when timer counts to 0. Then next bit is activated.
350  */
351 
352 #define CHECK_TIMER_PERIOD(R,N)
353  R.timerPeriodsLeft--;
354  if(R.timerPeriodsLeft == R.timerPeriods) {
355  *DCCppConfig::SignalPortIn ## N = DCCppConfig::SignalPortMask ## N;
356  }
357  if(R.timerPeriodsLeft == 0) {
358  *DCCppConfig::SignalPortIn ## N = DCCppConfig::SignalPortMask ## N;
359  DCC_NEXT_BIT(R);
360  }
361 
362 // NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
363 
364 ISR(TIMER2_COMPA_vect) {
365  if(DCCppConfig::SignalPortMaskMain != 0)
366  CHECK_TIMER_PERIOD(DCCpp::mainRegs, Main)
367  if(DCCppConfig::SignalPortMaskProg != 0)
368  CHECK_TIMER_PERIOD(DCCpp::progRegs, Prog)
369  //*DCCppConfig::SignalPortInMain = DCCppConfig::SignalPortMaskMain;
370 }
371 #endif
372 
374 {
375  bitClear(TCCR1B, CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8
376  bitSet(TCCR1B, CS11);
377  bitClear(TCCR1B, CS10);
378 
379 #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) // Configuration for UNO
380 
381  bitSet(TCCR0B, CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4
382  bitClear(TCCR0B, CS01);
383  bitClear(TCCR0B, CS00);
384 
385 #else // Configuration for MEGA
386 
387  bitClear(TCCR3B, CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8
388  bitSet(TCCR3B, CS31);
389  bitClear(TCCR3B, CS30);
390 
391 #endif
392 
393  CLKPR = 0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256
394  CLKPR = 0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS
395 }
396 
397 #endif
static void setDebugDccMode()
static void beginProgDccSignal(uint8_t inSignalPin)
static void beginMainDccSignal(uint8_t inSignalPin)