-
Notifications
You must be signed in to change notification settings - Fork 0
/
DCCpp.ino
585 lines (434 loc) · 27.6 KB
/
DCCpp.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
/**********************************************************************
DCC++ BASE STATION
COPYRIGHT (c) 2013-2016 Gregg E. Berman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses
**********************************************************************/
/**********************************************************************
DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega
using the Arduino IDE 1.6.6.
It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others)
to be used as a fully-functioning digital command and control (DCC) base station
for controlling model train layouts that conform to current National Model
Railroad Association (NMRA) DCC standards.
This version of DCC++ BASE STATION supports:
* 2-byte and 4-byte locomotive addressing
* Simultaneous control of multiple locomotives
* 128-step speed throttling
* Cab functions F0-F28
* Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses
- includes optional functionailty to monitor and store of the direction of any connected turnouts
* Programming on the Main Operations Track
- write configuration variable bytes
- set/clear specific configuration variable bits
* Programming on the Programming Track
- write configuration variable bytes
- set/clear specific configuration variable bits
- read configuration variable bytes
DCC++ BASE STATION is controlled with simple text commands received via
the Arduino's serial interface. Users can type these commands directly
into the Arduino IDE Serial Monitor, or can send such commands from another
device or computer program.
When compiled for the Arduino Mega, an Ethernet Shield can be used for network
communications instead of using serial communications.
DCC++ CONTROLLER, available separately under a similar open-source
license, is a Java program written using the Processing library and Processing IDE
that provides a complete and configurable graphic interface to control model train layouts
via the DCC++ BASE STATION.
With the exception of a standard 15V power supply that can be purchased in
any electronics store, no additional hardware is required.
Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or
commercial hardware, software, interfaces, specifications, or methods related
to the control of model trains using NMRA DCC standards. Both programs are wholly
original, developed by the author, and are not derived from any known commercial,
free, or open-source model railroad control packages by any other parties.
However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and
embedded libraries associated with Arduino and Processing. Tremendous thanks to those
responsible for these terrific open-source initiatives that enable programs like
DCC++ to be developed and distributed in the same fashion.
REFERENCES:
NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices
Arduino: http://www.arduino.cc/
Processing: http://processing.org/
GNU General Public License: http://opensource.org/licenses/GPL-3.0
BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION:
DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0,
and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V
unipolar signals that each properly encode zero and one bits conforming with
DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B.
Series of DCC bit streams are bundled into Packets that each form the basis of
a standard DCC instruction. Packets are stored in Packet Registers that contain
methods for updating and queuing according to text commands sent by the user
(or another program) over the serial interface. There is one set of registers that controls
the main operations track and one that controls the programming track.
For the main operations track, packets to store cab throttle settings are stored in
registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp.h).
It is generally considered good practice to continuously send throttle control packets
to every cab so that if an engine should momentarily lose electrical connectivity with the tracks,
it will very quickly receive another throttle control signal as soon as connectivity is
restored (such as when a trin passes over rough portion of track or the frog of a turnout).
DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter
that has been loaded with a throttle control setting for a given cab. For each register, it
transmits the appropriate DCC packet bits to the track, then moves onto the next register
without any pausing to ensure continuous bi-polar power is being provided to the tracks.
Updates to the throttle setting stored in any given packet register are done in a double-buffered
fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits
can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal.
The cabs identified in each stored throttle setting should be unique across registers. If two registers
contain throttle setting for the same cab, the throttle in the engine will oscillate between the two,
which is probably not a desireable outcome.
For both the main operations track and the programming track there is also a special packet register with id=0
that is used to store all other DCC packets that do not require continious transmittal to the tracks.
This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables.
It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure
proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet
and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself.
Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of
verifying proper transmittal before acting on the instructions contained in those packets
An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached
on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals
produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals.
This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10)
to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect
the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13).
For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the
Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2)
to the Motor Shield's DIRECTION B input (pin 13).
Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp.h for details).
When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be
connected directly to the tracks. This software assumes CHANNEL A is connected
to the Main Operations Track, and CHANNEL B is connected to the Programming Track.
DCC++ BASE STATION in split into multiple modules, each with its own header file:
DCCpp_Uno: declares required global objects and contains initial Arduino setup()
and Arduino loop() functions, as well as interrput code for OC0B and OC1B.
Also includes declarations of optional array of Turn-Outs and optional array of Sensors
SerialCommand: contains methods to read and interpret text commands from the serial line,
process those instructions, and, if necessary call appropriate Packet RegisterList methods
to update either the Main Track or Programming Track Packet Registers
PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions
CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and
CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload
is detected
Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled
by a DCC stationary accessory decoder.
Sensor: contains methods to monitor and report on the status of optionally-defined infrared
sensors embedded in the Main Track and connected to various pins on the Arudino Uno
Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use
EEStore: contains methods to store, update, and load various DCC settings and status
(e.g. the states of all defined turnouts) in the EEPROM for recall after power-up
DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters
**********************************************************************/
// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE
#include "DCCpp.h"
#include "PacketRegister.h"
#include "CurrentMonitor.h"
#include "SerialCommand.h"
#include "Accessories.h"
#include "EEStore.h"
#include "Config.h"
//#include <MemoryFree.h>
// Start: DO NO EDIT
int PinCounter = 0;
int pins[16];
int Apins[16];
// End: DO NOT EDIT
CurrentMonitor mainMonitor(3,A0,96,"<p2>"); // create monitor for current on Main Track
CurrentMonitor progMonitor(11,A1,96,"<p3>"); // create monitor for current on Program Track
//CurrentMonitor testMonitor(32,A2,96,"<p4>");
unsigned long LastRestartDelay = 0UL;
void showConfiguration();
// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE
#if COMM_TYPE == 1
#if COMM_INTERFACE == 4
WiFiServer INTERFACE(ETHERNET_PORT);
char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS;
int status = WL_IDLE_STATUS;
#else
byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server)
EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer
#endif // COMM_INTERFACE
#endif
// NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS.
// NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES
volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets
volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets
///////////////////////////////////////////////////////////////////////////////
// MAIN ARDUINO LOOP
///////////////////////////////////////////////////////////////////////////////
void loop(){
SerialCommand::process(); // check for, and process, and new serial commands
if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
mainMonitor.check();
progMonitor.check();
//testMonitor.check();
}
if ((millis() - LastRestartDelay) >= MAIN_LINE_RESET_DELAY)
{
LastRestartDelay = millis();
mainMonitor.Reset();
progMonitor.Reset();
//testMonitor.Reset();
}
//Serial.println(freeMemory());
} // loop
///////////////////////////////////////////////////////////////////////////////
// INITIAL SETUP
///////////////////////////////////////////////////////////////////////////////
void setup(){
Serial.begin(115200); // configure serial interface
Serial.flush();
EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM
Serial.print("<iDCC++ BASE STATION FOR ARDUINO "); // Print Status to Serial Line regardless of COMM_TYPE setting so use can open Serial Monitor and check configurtion
Serial.print(ARDUINO_TYPE);
Serial.print(" / ");
Serial.print(MOTOR_SHIELD_NAME);
Serial.print(": V-");
Serial.print(VERSION);
Serial.print(" / ");
Serial.print(__DATE__);
Serial.print(" ");
Serial.print(__TIME__);
Serial.print(">");
Serial.print("<J");
//Serial.print(DccServer::serverID);
Serial.print(">");
// DccServer::setServer(DccServer::serverID); // Start WIRE in SERVER MODE address loaded from EEPROM (0-119, where 0=DCC++ MASTER, else DCC++ BOOSTER)
#if COMM_TYPE == 1
#if COMM_INTERFACE == 4
WiFi.setPins(53,48,49); // CS, IRQ, RST
while ( status != WL_CONNECTED) {
//Serial.print("Attempting to connect to Network named: ");
//Serial.println(ssid); // print the network name (SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
#else
#ifdef IP_ADDRESS
Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address
#else
Ethernet.begin(mac); // Start networking using DHCP to get an IP Address
#endif
#endif // COMM_INTERFACE
INTERFACE.begin();
#endif
SerialCommand::init(&mainRegs, &progRegs, &mainMonitor, &progMonitor); // create structure to read and parse commands from serial line
Serial.print("<N");
Serial.print(COMM_TYPE);
Serial.print(": ");
#if COMM_TYPE == 0
Serial.print("SERIAL>");
#elif COMM_TYPE == 1
#if COMM_INTERFACE == 4
Serial.print(ssid);
#ifdef USE_IIC_LCD
lcd.setCursor(1,1);
lcd.print(ssid);
#endif // USE_IIC_LCD
#else
Serial.print(Ethernet.localIP());
#ifdef USE_IIC_LCD
lcd.setCursor(1,1);
lcd.print(Ethernet.localIP);
#endif // USE_IIC_LCD
#endif
Serial.print(">");
#endif
//DccServer::init(); // set up DCC++ Server
// CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS
// Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK
// Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin
// Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
#define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW);
pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A
bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A
bitSet(TCCR1A,WGM11);
bitSet(TCCR1B,WGM12);
bitSet(TCCR1B,WGM13);
bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary)
bitSet(TCCR1A,COM1B0);
bitClear(TCCR1B,CS12); // set Timer 1 prescale=1
bitClear(TCCR1B,CS11);
bitSet(TCCR1B,CS10);
OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1;
OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1;
//pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A
mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B)
// CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
// Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin
// Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency
// 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
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28
#define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A
bitSet(TCCR0A,WGM01);
bitSet(TCCR0B,WGM02);
bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary)
bitSet(TCCR0A,COM0B0);
bitClear(TCCR0B,CS02); // set Timer 0 prescale=64
bitSet(TCCR0B,CS01);
bitSet(TCCR0B,CS00);
OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0;
OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0;
//pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B)
#else // Configuration for MEGA
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
// Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin
// Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855
#define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A
bitSet(TCCR3A,WGM31);
bitSet(TCCR3B,WGM32);
bitSet(TCCR3B,WGM33);
bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary)
bitSet(TCCR3A,COM3B0);
bitClear(TCCR3B,CS32); // set Timer 3 prescale=1
bitClear(TCCR3B,CS31);
bitSet(TCCR3B,CS30);
OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3;
OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3;
//pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B)
#endif
} // setup
///////////////////////////////////////////////////////////////////////////////
// DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
///////////////////////////////////////////////////////////////////////////////
// The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
// It is designed to read the current bit of the current register packet and
// updates the OCNA and OCNB counters of Timer-N to values that will either produce
// a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
// DCC ZERO and DCC ONE bits.
// These are hardware-driven interrupts that will be called automatically when triggered regardless of what
// DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled.
// Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
// (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
// in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic
// of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB
// registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
// interrupt code completes and can be called again.
// A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
// DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
// Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
// Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro
// that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating
// some of the logic for each interrupt, but saves additional time.
// As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
// when a new register is loaded and the logic needs to switch active register packet pointers.
// THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1)
#define DCC_SIGNAL(R,N) \
if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \
R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \
if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \
R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \
} else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \
R.currentReg=R.nextReg; /* update currentReg to nextReg */ \
R.nextReg=NULL; /* reset nextReg to NULL */ \
R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \
R.currentReg->activePacket=R.currentReg->updatePacket; \
R.currentReg->updatePacket=R.tempPacket; \
} else{ /* ELSE simply move to next Register */ \
if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \
R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \
R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \
} /* END-ELSE */ \
} /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \
\
if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \
OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \
OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \
} else{ /* ELSE it is a ZERO */ \
OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \
OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \
} /* END-ELSE */ \
\
R.currentBit++; /* point to next bit in current Packet */
///////////////////////////////////////////////////////////////////////////////
// NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
DCC_SIGNAL(mainRegs,1)
}
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
DCC_SIGNAL(progRegs,0)
}
#else // Configuration for MEGA
ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
DCC_SIGNAL(progRegs,3)
}
#endif
///////////////////////////////////////////////////////////////////////////////
// PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE
// - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH
void showConfiguration(){
Serial.print("\n*** DCC++ CONFIGURATION ***\n");
Serial.print("\nVERSION: ");
Serial.print(VERSION);
Serial.print("\nCOMPILED: ");
Serial.print(__DATE__);
Serial.print(" ");
Serial.print(__TIME__);
Serial.print("\nARDUINO: ");
Serial.print(ARDUINO_TYPE);
Serial.print("\n\nMOTOR SHIELD: ");
Serial.print(MOTOR_SHIELD_NAME);
Serial.print("\n\nDCC SIG MAIN: ");
Serial.print(DCC_SIGNAL_PIN_MAIN);
Serial.print("\n DIRECTION: ");
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A);
Serial.print("\n ENABLE: ");
Serial.print(SIGNAL_ENABLE_PIN_MAIN);
Serial.print("\n CURRENT: ");
Serial.print(CURRENT_MONITOR_PIN_MAIN);
Serial.print("\n\nDCC SIG PROG: ");
Serial.print(DCC_SIGNAL_PIN_PROG);
Serial.print("\n DIRECTION: ");
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B);
Serial.print("\n ENABLE: ");
Serial.print(SIGNAL_ENABLE_PIN_PROG);
Serial.print("\n CURRENT: ");
Serial.print(CURRENT_MONITOR_PIN_PROG);
Serial.print("\n\nNUM TURNOUTS: ");
Serial.print(EEStore::eeStore->data.nTurnouts);
Serial.print("\n\nINTERFACE: ");
#if COMM_TYPE == 0
Serial.print("SERIAL");
#endif
Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO");
while(true);
}
///////////////////////////////////////////////////////////////////////////////