diff --git a/ArduinoTimers.h b/ArduinoTimers.h deleted file mode 100644 index c705e4d..0000000 --- a/ArduinoTimers.h +++ /dev/null @@ -1,20 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit and copyright David Cutting -// The files included below come from the same source. -// This library had been included with the DCC code to avoid issues with -// library management for inexperienced users. "It just works (TM)" - -#ifndef ArduinoTimers_h -#define ArduinoTimers_h - -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#include "ATMEGA2560/Timer.h" -#elif defined(ARDUINO_AVR_UNO) -#include "ATMEGA328/Timer.h" -#elif defined(ARDUINO_ARCH_MEGAAVR) -#include "ATMEGA4809/EveryTimerB.h" -#else -#error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags." -#endif - -#endif diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 8b75fc4..4906498 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -49,10 +49,19 @@ void setup() // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - + DCC::begin(MOTOR_SHIELD_TYPE); + + #if defined(RMFT_ACTIVE) + RMFT::begin(); + #endif + + #if __has_include ( "mySetup.h") + #define SETUP(cmd) serialParser.parse(F(cmd)) + #include "mySetup.h" + #undef SETUP + #endif + LCD(1,F("Ready")); } @@ -75,6 +84,10 @@ void loop() EthernetInterface::loop(); #endif +#if defined(RMFT_ACTIVE) + RMFT::loop(); +#endif + LCDDisplay::loop(); // ignored if LCD not in use // Optionally report any decrease in memory (will automatically trigger on first call) diff --git a/DCC.cpp b/DCC.cpp index 0d1affe..b0973bc 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -23,6 +23,7 @@ #include "EEStore.h" #include "GITHUB_SHA.h" #include "version.h" +#include "FSH.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -45,15 +46,15 @@ const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; -void DCC::begin(const FSH* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { - shieldName=(FSH*)motorShieldName; +void DCC::begin( FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { + shieldName=motorShieldName; DIAG(F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); - DCCWaveform::begin(mainDriver,progDriver, timerNumber); + DCCWaveform::begin(mainDriver,progDriver); } void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { @@ -331,10 +332,31 @@ const ackOp FLASH READ_CV_PROG[] = { const ackOp FLASH LOCO_ID_PROG[] = { BASELINE, + SETCV, (ackOp)1, + SETBIT, (ackOp)7, + V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found + + SETCV, (ackOp)19, // CV 19 is consist setting + SETBYTE, (ackOp)0, + VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist) + SETBYTE, (ackOp)128, + VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set) + STARTMERGE, // Setup to read cv 19 + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + V0, WACK, MERGE, + VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it + + SKIPTARGET, // continue here if CV 19 is zero or fails all validation SETCV,(ackOp)29, SETBIT,(ackOp)5, V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero - V1, WACK, NAKFAIL, // fast fail if no loco on track + // Long locoid SETCV, (ackOp)17, // CV 17 is part of locoid STARTMERGE, @@ -366,7 +388,7 @@ const ackOp FLASH LOCO_ID_PROG[] = { SKIPTARGET, SETCV, (ackOp)1, STARTMERGE, - V0, WACK, MERGE, // read and merge bit 1 etc + SETBIT, (ackOp)6, // skip over first bit as we know its a zero V0, WACK, MERGE, V0, WACK, MERGE, V0, WACK, MERGE, @@ -378,6 +400,44 @@ const ackOp FLASH LOCO_ID_PROG[] = { FAIL }; +const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = { + BASELINE, + SETCV,(ackOp)19, + SETBYTE, (ackOp)0, + WB,WACK, // ignore router without cv19 support + // Turn off long address flag + SETCV,(ackOp)29, + SETBIT,(ackOp)5, + W0,WACK,NAKFAIL, + SETCV, (ackOp)1, + SETBYTEL, // low byte of word + WB,WACK,NAKFAIL, + VB,WACK,ITCB, + FAIL +}; + +const ackOp PROGMEM LONG_LOCO_ID_PROG[] = { + BASELINE, + // Clear consist CV 19 + SETCV,(ackOp)19, + SETBYTE, (ackOp)0, + WB,WACK, // ignore router without cv19 support + // Turn on long address flag cv29 bit 5 + SETCV,(ackOp)29, + SETBIT,(ackOp)5, + W1,WACK,NAKFAIL, + // Store high byte of address in cv 17 + SETCV, (ackOp)17, + SETBYTEH, // high byte of word + WB,WACK,NAKFAIL, + VB,WACK,NAKFAIL, + // store + SETCV, (ackOp)18, + SETBYTEL, // low byte of word + WB,WACK,NAKFAIL, + VB,WACK,ITC1, // callback(1) means Ok + FAIL +}; // On the following prog-track functions blocking defaults to false. // blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning. @@ -420,6 +480,12 @@ void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) { ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking); } +void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) { + if (id<=0 || id>9999) callback(-1); + if (id<=127) ackManagerSetup(id,SHORT_LOCO_ID_PROG, callback, blocking); + else ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking); +} + void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco int reg=lookupSpeedTable(cab); if (reg>=0) speedTable[reg].loco=0; @@ -552,7 +618,8 @@ int DCC::nextLoco = 0; ackOp const * DCC::ackManagerProg; byte DCC::ackManagerByte; byte DCC::ackManagerStash; -int DCC::ackManagerCv; +int DCC::ackManagerWord; +int DCC::ackManagerCv; byte DCC::ackManagerBitNum; bool DCC::ackReceived; @@ -567,6 +634,13 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[] if (blocking) ackManagerLoop(blocking); } +void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) { + ackManagerWord=wordval; + ackManagerProg = program; + ackManagerCallback = callback; + if (blocking) ackManagerLoop(blocking); +} + const byte RESET_MIN=8; // tuning of reset counter before sending message // checkRessets return true if the caller should yield back to loop and try later. @@ -668,7 +742,15 @@ void DCC::ackManagerLoop(bool blocking) { case ITCB: // If True callback(byte) if (ackReceived) { ackManagerProg = NULL; // all done now - callback(ackManagerByte); + callback(ackManagerByte); + return; + } + break; + + case ITCB7: // If True callback(byte & 0xF) + if (ackReceived) { + ackManagerProg = NULL; // all done now + callback(ackManagerByte & 0x7F); return; } break; @@ -708,6 +790,19 @@ void DCC::ackManagerLoop(bool blocking) { ackManagerCv=GETFLASH(ackManagerProg); break; + case SETBYTE: + ackManagerProg++; + ackManagerByte=GETFLASH(ackManagerProg); + break; + + case SETBYTEH: + ackManagerByte=highByte(ackManagerWord); + break; + + case SETBYTEL: + ackManagerByte=lowByte(ackManagerWord); + break; + case STASHLOCOID: ackManagerStash=ackManagerByte; // stash value from CV17 break; diff --git a/DCC.h b/DCC.h index db746cb..7099056 100644 --- a/DCC.h +++ b/DCC.h @@ -37,12 +37,16 @@ enum ackOp ITC1, // If True Callback(1) (if prevous WACK got an ACK) ITC0, // If True callback(0); ITCB, // If True callback(byte) + ITCB7, // If True callback(byte &0x7F) NAKFAIL, // if false callback(-1) FAIL, // callback(-1) STARTMERGE, // Clear bit and byte settings ready for merge pass MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) SETBIT, // sets bit number to next prog byte SETCV, // sets cv number to next prog byte + SETBYTE, // sets current byte to next prog byte + SETBYTEH, // sets current byte to word high byte + SETBYTEL, // sets current byte to word low byte STASHLOCOID, // keeps current byte value for later COMBINELOCOID, // combines current value with stashed value and returns it ITSKIP, // skip to SKIPTARGET if ack true @@ -60,7 +64,7 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const FSH *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1); + static void begin(FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); static void loop(); // Public DCC API functions @@ -88,6 +92,7 @@ public: static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); static void getLocoId(ACK_CALLBACK callback, bool blocking = false); + static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false); // Enhanced API functions static void forgetLoco(int cab); // removes any speed reminders for this loco @@ -124,10 +129,12 @@ private: static byte ackManagerByte; static byte ackManagerBitNum; static int ackManagerCv; + static int ackManagerWord; static byte ackManagerStash; static bool ackReceived; static ACK_CALLBACK ackManagerCallback; static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking); + static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking); static void ackManagerLoop(bool blocking); static bool checkResets(bool blocking, uint8_t numResets); static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) diff --git a/DCCEX.h b/DCCEX.h index d89d581..2d1d183 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -15,6 +15,10 @@ #endif #include "LCD_Implementation.h" #include "freeMemory.h" -#include +#if __has_include ( "myAutomation.h") + #include "RMFT.h" + #define RMFT_ACTIVE +#endif + #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 0a78676..04546a3 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -221,16 +221,29 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd) } FILTER_CALLBACK DCCEXParser::filterCallback = 0; +FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0; AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; void DCCEXParser::setFilter(FILTER_CALLBACK filter) { filterCallback = filter; } +void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter) +{ + filterRMFTCallback = filter; +} void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) { atCommandCallback = callback; } +// Parse an F() string +void DCCEXParser::parse(const __FlashStringHelper * cmd) { + int size=strlen_P((char *)cmd)+1; + char buffer[size]; + strcpy_P(buffer,(char *)cmd); + parse(&Serial,(byte *)buffer,true); +} + // See documentation on DCC class for info on this section void DCCEXParser::parse(Print *stream, byte *com, bool blocking) { @@ -245,6 +258,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) if (filterCallback) filterCallback(stream, opcode, params, p); + if (filterRMFTCallback && opcode!='\0') + filterRMFTCallback(stream, opcode, params, p); // Functions return from this switch if complete, break from switch implies error to send switch (opcode) @@ -344,9 +359,12 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; case 'W': // WRITE CV ON PROG - if (!stashCallback(stream, p)) - break; - DCC::writeCVByte(p[0], p[1], callback_W, blocking); + if (!stashCallback(stream, p)) + break; + if (params == 1) // Write new loco id (clearing consist and managing short/long) + DCC::setLocoId(p[0],callback_Wloco, blocking); + else // WRITE CV ON PROG + DCC::writeCVByte(p[0], p[1], callback_W, blocking); return; case 'V': // VERIFY CV ON PROG @@ -434,8 +452,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) } return; - case 'c': // READ CURRENT - StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.get1024Current()); + case 'c': // SEND METER RESPONSES + // + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getCurrentmA(), + DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA()); + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available return; case 'Q': // SENSORS @@ -445,7 +466,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) case 's': // StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); StringFormatter::send(stream, F(""), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); - parseT(stream, 0, p); //send all Turnout states + Turnout::printAll(stream); //send all Turnout states Output::printAll(stream); //send all Output states Sensor::printAll(stream); //send all Sensor states // TODO Send stats of speed reminders table @@ -529,7 +550,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) StringFormatter::send(stream, F("")); return true; - case 0: // + case 0: // list Output definitions { bool gotone = false; for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput) @@ -591,13 +612,14 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[]) { switch (params) { - case 0: // list all turnout states + case 0: // list turnout definitions { bool gotOne = false; for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) { gotOne = true; - StringFormatter::send(stream, F(""), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0); + StringFormatter::send(stream, F(""), tt->data.id, tt->data.address, + tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0); } return gotOne; // will if none found } @@ -646,7 +668,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) StringFormatter::send(stream, F("")); return true; - case 0: // list sensor states + case 0: // list sensor definitions if (Sensor::firstSensor == NULL) return false; for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) @@ -769,6 +791,13 @@ void DCCEXParser::callback_R(int result) void DCCEXParser::callback_Rloco(int result) { - StringFormatter::send(stashStream, F(""), result); + StringFormatter::send(stashStream, F(""), result & 0x3FFF); + stashBusy = false; +} + +void DCCEXParser::callback_Wloco(int result) +{ + if (result==1) result=stashP[0]; // pick up original requested id from command + StringFormatter::send(stashStream, F(""), result); stashBusy = false; } diff --git a/DCCEXParser.h b/DCCEXParser.h index ec0286d..c9197de 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -28,8 +28,10 @@ struct DCCEXParser DCCEXParser(); void loop(Stream & stream); void parse(Print * stream, byte * command, bool blocking); + void parse(const __FlashStringHelper * cmd); void flush(); static void setFilter(FILTER_CALLBACK filter); + static void setRMFTFilter(FILTER_CALLBACK filter); static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); static const int MAX_PARAMS=10; // Must not exceed this @@ -58,9 +60,11 @@ struct DCCEXParser static void callback_B(int result); static void callback_R(int result); static void callback_Rloco(int result); + static void callback_Wloco(int result); static void callback_Vbit(int result); static void callback_Vbyte(int result); static FILTER_CALLBACK filterCallback; + static FILTER_CALLBACK filterRMFTCallback; static AT_COMMAND_CALLBACK atCommandCallback; static void funcmap(int cab, byte value, byte fstart, byte fstop); diff --git a/DCCTimer.cpp b/DCCTimer.cpp new file mode 100644 index 0000000..94fdfba --- /dev/null +++ b/DCCTimer.cpp @@ -0,0 +1,73 @@ +/* + * © 2021, Chris Harlow & David Cutting. All rights reserved. + * + * This file is part of Asbelos DCC API + * + * This 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. + * + * It 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 CommandStation. If not, see . + */ + + +/* This timer class is used to manage the single timer required to handle the DCC waveform. + * All timer access comes through this class so that it can be compiled for + * various hardware CPU types. + * + * DCCEX works on a single timer interrupt at a regular 58uS interval. + * The DCCWaveform class generates the signals to the motor shield + * based on this timer. + */ + +#include "DCCTimer.h" + +const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle +const int DCC_SLOW_TIME=58*512; // for command diagnostics + +INTERRUPT_CALLBACK interruptHandler=0; + + +void DCCTimer::begin(INTERRUPT_CALLBACK callback, bool slow) { + interruptHandler=callback; + // Initialise timer1 to trigger every 58us (DCC_SIGNAL_TIME) + long clockCycles=((F_CPU / 1000000) * (slow? DCC_SLOW_TIME : DCC_SIGNAL_TIME)) >>1; + noInterrupts(); + +#ifdef ARDUINO_ARCH_MEGAAVR + // Arduino unoWifi Rev2 and nanoEvery architectire + TCB0.CCMP = clockCycles; + TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag + TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt + TCB0.CNT = 0; + TCB0.CTRLA |= TCB_ENABLE_bm; // start + #define ISR_NAME TCB2_INT_vect + +#else + // Arduino nano, uno, mega + TCCR1A = 0; + ICR1 = clockCycles; + TCNT1 = 0; + TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 + TIMSK1 = _BV(TOIE1); // Enable Software interrupt + #define ISR_NAME TIMER1_OVF_vect +#endif + + interrupts(); +} + +// ISR called by timer interrupt every 58uS +ISR(ISR_NAME) +{ +#ifdef ARDUINO_ARCH_MEGAAVR + TCB0.INTFLAGS = TCB_CAPT_bm; +#endif + if (interruptHandler) interruptHandler(); +} diff --git a/DCCTimer.h b/DCCTimer.h new file mode 100644 index 0000000..7f99acc --- /dev/null +++ b/DCCTimer.h @@ -0,0 +1,13 @@ +#ifndef DCCTimer_h +#define DCCTimer_h +#include "Arduino.h" + +typedef void (*INTERRUPT_CALLBACK)(); + +class DCCTimer { + public: + static void begin(INTERRUPT_CALLBACK interrupt, bool slow=false); + private: +}; + +#endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 08f3c31..90db3ee 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -20,17 +20,9 @@ #include #include "DCCWaveform.h" +#include "DCCTimer.h" #include "DIAG.h" - -#ifdef ARDUINO_ARCH_MEGAAVR -#include "EveryTimerB.h" -#define Timer1 TimerB2 // use TimerB2 as a drop in replacement for Timer1 -#else // assume architecture supported by TimerOne .... -#include "TimerOne.h" -#endif - -const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle -const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512; + DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -39,21 +31,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; -void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) { +void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; mainTrack.setPowerMode(POWERMODE::OFF); progTrack.setPowerMode(POWERMODE::OFF); - - Timer1.initialize(); - Timer1.setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle - Timer1.attachInterrupt(interruptHandler); - Timer1.start(); + DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::setDiagnosticSlowWave(bool slow) { - Timer1.setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME); - Timer1.start(); + +void DCCWaveform::setDiagnosticSlowWave(bool slow) { + DCCTimer::begin(DCCWaveform::interruptHandler, slow); DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); } @@ -62,8 +50,6 @@ void DCCWaveform::loop() { progTrack.checkPowerOverload(); } - -// static // void DCCWaveform::interruptHandler() { // call the timer edge sensitive actions for progtrack and maintrack bool mainCall2 = mainTrack.interrupt1(); @@ -109,7 +95,6 @@ POWERMODE DCCWaveform::getPowerMode() { } void DCCWaveform::setPowerMode(POWERMODE mode) { - powerMode = mode; bool ison = (mode == POWERMODE::ON); motorDriver->setPower( ison); @@ -162,10 +147,6 @@ void DCCWaveform::checkPowerOverload() { } } - - - - // process time-edge sensitive part of interrupt // return true if second level required bool DCCWaveform::interrupt1() { diff --git a/DCCWaveform.h b/DCCWaveform.h index cc66607..e3c05fd 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -21,7 +21,6 @@ #define DCCWaveform_h #include "MotorDriver.h" - // Wait times for power management. Unit: milliseconds const int POWER_SAMPLE_ON_WAIT = 100; const int POWER_SAMPLE_OFF_WAIT = 1000; @@ -46,7 +45,7 @@ const byte resetPacket[] = {0x00, 0x00, 0x00}; class DCCWaveform { public: DCCWaveform( byte preambleBits, bool isMain); - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber); + static void begin(MotorDriver * mainDriver, MotorDriver * progDriver); static void setDiagnosticSlowWave(bool slow); static void loop(); static DCCWaveform mainTrack; @@ -58,9 +57,26 @@ class DCCWaveform { void checkPowerOverload(); int getLastCurrent(); inline int get1024Current() { - if (powerMode == POWERMODE::ON) - return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); - return 0; + if (powerMode == POWERMODE::ON) + return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); + return 0; + } + inline int getCurrentmA() { + if (powerMode == POWERMODE::ON) + return motorDriver->raw2mA(lastCurrent); + return 0; + } + inline int getMaxmA() { + if (maxmA == 0) { //only calculate this for first request, it doesn't change + maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc + } + return maxmA; + } + inline int getTripmA() { + if (tripmA == 0) { //only calculate this for first request, it doesn't change + tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); + } + return tripmA; } void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; @@ -88,6 +104,7 @@ class DCCWaveform { } private: + static void interruptHandler(); bool interrupt1(); void interrupt2(); @@ -111,7 +128,8 @@ class DCCWaveform { byte pendingLength; byte pendingRepeats; int lastCurrent; - + int maxmA; + int tripmA; // current sampling POWERMODE powerMode; diff --git a/Release - Architecture Doc/Prod-Release-Notes.md b/Release - Architecture Doc/Prod-Release-Notes.md index 1a04b09..0893e1a 100644 --- a/Release - Architecture Doc/Prod-Release-Notes.md +++ b/Release - Architecture Doc/Prod-Release-Notes.md @@ -1,9 +1,18 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. **Known Bugs:** - - **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket. + - **Consisting through JMRI** - currently does not work in this release. You may use the command to do this manually. - **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode. - **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry + +**Summary of the key new features added to CommandStation-EX V3.0.3** + - ** command to write loco address and clear consist** + - ** command will allow for consist address** + - **Startup commands implemented** + +**Summary of the key new features added to CommandStation-EX V3.0.2:** +- **Create new output for current in mA for ```` command** - New current response outputs current in mA, overlimit current, and maximum board capable current +- **Simultaneously update JMRI to handle new current meter** **Summary of the key new features added to CommandStation-EX V3.0.1:** - **Add back fix for jitter** diff --git a/Turnouts.cpp b/Turnouts.cpp index 45b3c1b..03093a1 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -21,10 +21,17 @@ #include "Turnouts.h" #include "EEStore.h" #include "PWMServoDriver.h" +#include "StringFormatter.h" #ifdef EESTOREDEBUG #include "DIAG.h" #endif +// print all turnout states to stream +void Turnout::printAll(Print *stream){ + for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) + StringFormatter::send(stream, F(""), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0); +} // Turnout::printAll + bool Turnout::activate(int n,bool state){ #ifdef EESTOREDEBUG DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state); diff --git a/Turnouts.h b/Turnouts.h index 2aff97d..186149b 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -49,6 +49,7 @@ class Turnout { static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle); static Turnout *create(int id); void activate(bool state); + static void printAll(Print *); #ifdef EESTOREDEBUG void print(Turnout *tt); #endif diff --git a/version.h b/version.h index 5f219a3..5ab6a3f 100644 --- a/version.h +++ b/version.h @@ -1,6 +1,10 @@ #ifndef version_h #define version_h -#define VERSION "3.0.1" +#define VERSION "3.0.3" +// 3.0.3 Includes: +// command to write loco address and clear consist +// command will allow for consist address +// Startup commands implemented #endif