564 lines
27 KiB
C++
564 lines
27 KiB
C++
/**********************************************************************
|
|
|
|
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_Uno.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_Uno.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_Uno.h"
|
|
#include "PacketRegister.h"
|
|
#include "CurrentMonitor.h"
|
|
#include "Sensor.h"
|
|
#include "SerialCommand.h"
|
|
#include "Accessories.h"
|
|
#include "EEStore.h"
|
|
#include "Config.h"
|
|
#include "Comm.h"
|
|
|
|
void showConfiguration();
|
|
|
|
// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE
|
|
|
|
#if COMM_TYPE == 1
|
|
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
|
|
|
|
// 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
|
|
|
|
CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,"<p2>"); // create monitor for current on Main Track
|
|
CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,"<p3>"); // create monitor for current on Program Track
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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();
|
|
}
|
|
|
|
Sensor::check(); // check sensors for activate/de-activate
|
|
|
|
} // loop
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// INITIAL SETUP
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void setup(){
|
|
|
|
Serial.begin(115200); // configure serial interface
|
|
Serial.flush();
|
|
|
|
#ifdef SDCARD_CS
|
|
pinMode(SDCARD_CS,OUTPUT);
|
|
digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card
|
|
#endif
|
|
|
|
EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM
|
|
|
|
pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt
|
|
digitalWrite(A5,HIGH);
|
|
if(!digitalRead(A5))
|
|
showConfiguration();
|
|
|
|
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(">");
|
|
|
|
#if COMM_TYPE == 1
|
|
#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
|
|
INTERFACE.begin();
|
|
#endif
|
|
|
|
SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // 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
|
|
Serial.print(Ethernet.localIP());
|
|
Serial.print(">");
|
|
#endif
|
|
|
|
// 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(){
|
|
|
|
int mac_address[]=MAC_ADDRESS;
|
|
|
|
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 SENSORS: ");
|
|
Serial.print(EEStore::eeStore->data.nSensors);
|
|
Serial.print("\n OUTPUTS: ");
|
|
Serial.print(EEStore::eeStore->data.nOutputs);
|
|
|
|
Serial.print("\n\nINTERFACE: ");
|
|
#if COMM_TYPE == 0
|
|
Serial.print("SERIAL");
|
|
#elif COMM_TYPE == 1
|
|
Serial.print(COMM_SHIELD_NAME);
|
|
Serial.print("\nMAC ADDRESS: ");
|
|
for(int i=0;i<5;i++){
|
|
Serial.print(mac_address[i],HEX);
|
|
Serial.print(":");
|
|
}
|
|
Serial.print(mac_address[5],HEX);
|
|
Serial.print("\nPORT: ");
|
|
Serial.print(ETHERNET_PORT);
|
|
Serial.print("\nIP ADDRESS: ");
|
|
|
|
#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
|
|
|
|
Serial.print(Ethernet.localIP());
|
|
|
|
#ifdef IP_ADDRESS
|
|
Serial.print(" (STATIC)");
|
|
#else
|
|
Serial.print(" (DHCP)");
|
|
#endif
|
|
|
|
#endif
|
|
Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO");
|
|
|
|
while(true);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|