From 42075f838ecc5f52da388b3a3fda1f08906dabf8 Mon Sep 17 00:00:00 2001 From: mstevetodd Date: Mon, 4 Jan 2021 10:57:03 -0500 Subject: [PATCH 01/15] should send turnout definitions, not just states (#110) * use int, not byte for witSpeed * add turnout, sensor and output states to 's'tatus message * should send turnout definitions, not just states --- DCCEXParser.cpp | 11 ++++++----- Turnouts.cpp | 7 +++++++ Turnouts.h | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 0a78676..753f69b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -445,7 +445,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 +529,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 +591,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 +647,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) 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 From 0618a0bd72c974ecd130ad95ac3d9d6f3e45e212 Mon Sep 17 00:00:00 2001 From: Fred Date: Tue, 5 Jan 2021 13:05:17 -0500 Subject: [PATCH 02/15] RMFT Hooks (#112) These hooks do NOT require RMFT code to be present.... but they offer the hooks that RMFT will need when available. authored-by: Asbelos --- CommandStation-EX.ino | 9 +++++++++ DCCEX.h | 6 +++++- DCCEXParser.cpp | 7 +++++++ DCCEXParser.h | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 8b75fc4..18f2aaa 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -53,6 +53,11 @@ void setup() // 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 + LCD(1,F("Ready")); } @@ -75,6 +80,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/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 753f69b..c488fc1 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -221,11 +221,16 @@ 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; @@ -245,6 +250,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) diff --git a/DCCEXParser.h b/DCCEXParser.h index ec0286d..bef1199 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -30,6 +30,7 @@ struct DCCEXParser void parse(Print * stream, byte * command, bool blocking); 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 @@ -61,6 +62,7 @@ struct DCCEXParser 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); From 895b2aaaaaee935b92fef5d41c0487dfa6db9fd2 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 7 Jan 2021 20:58:23 +0000 Subject: [PATCH 03/15] Implement mySetup.h facility --- CommandStation-EX.ino | 10 ++++++++-- DCCEXParser.cpp | 8 ++++++++ DCCEXParser.h | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 18f2aaa..c0c3a1d 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -53,11 +53,17 @@ void setup() // 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")); } diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c488fc1..59d0b74 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -236,6 +236,14 @@ 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) { diff --git a/DCCEXParser.h b/DCCEXParser.h index bef1199..d2423d0 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -28,6 +28,7 @@ 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); From 418d8eb1b2e17ebc0a5623747612d5bd5fa5ccc9 Mon Sep 17 00:00:00 2001 From: mstevetodd Date: Fri, 8 Jan 2021 16:57:32 -0500 Subject: [PATCH 04/15] send milliAmps and meter setup for new JMRI Meter function (#113) * send milliAmps and meter setup for new JMRI Meter function --- DCCEXParser.cpp | 5 ++++- DCCWaveform.h | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c488fc1..fd1cea0 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -442,7 +442,10 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; case 'c': // READ CURRENT - StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.get1024Current()); + // + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getCurrentmA(), DCCWaveform::mainTrack.getMaxmA()); + // StringFormatter::send(stream, F(""), DCCWaveform::progTrack.getCurrentmA(), DCCWaveform::progTrack.getMaxmA()); + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available return; case 'Q': // SENSORS diff --git a/DCCWaveform.h b/DCCWaveform.h index b51e79f..3755979 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -58,9 +58,20 @@ 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()); + } + return maxmA; } void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; @@ -112,7 +123,7 @@ class DCCWaveform { byte pendingLength; byte pendingRepeats; int lastCurrent; - + int maxmA; // current sampling POWERMODE powerMode; From 7b4e5546b6b6e1c20060389fb2dc3e9c1b8ff9bf Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 8 Jan 2021 16:58:22 -0500 Subject: [PATCH 05/15] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index e60dd91..43f4764 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" // const char VERSION[] PROGMEM ="0.2.0"; -#define VERSION "3.0.1" +#define VERSION "3.0.2" #endif From 82a4b4880892a6a0c91d40a5a662f1b838149d41 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 8 Jan 2021 17:03:43 -0500 Subject: [PATCH 06/15] Update Prod-Release-Notes.md --- Release - Architecture Doc/Prod-Release-Notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Release - Architecture Doc/Prod-Release-Notes.md b/Release - Architecture Doc/Prod-Release-Notes.md index 1a04b09..eeaa466 100644 --- a/Release - Architecture Doc/Prod-Release-Notes.md +++ b/Release - Architecture Doc/Prod-Release-Notes.md @@ -4,6 +4,10 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production R - **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. - **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.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** From b537d7a31849f091b18c70c0b6c226b2c5271306 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 17 Jan 2021 13:22:16 +0000 Subject: [PATCH 07/15] command consist support R command will return address suitable for throttle if consist has been setup. --- DCC.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- DCC.h | 2 ++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 96868ca..421dd9b 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -331,10 +331,31 @@ const ackOp PROGMEM READ_CV_PROG[] = { const ackOp PROGMEM 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 +387,7 @@ const ackOp PROGMEM 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, @@ -668,7 +689,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 +737,11 @@ void DCC::ackManagerLoop(bool blocking) { ackManagerCv=pgm_read_byte_near(ackManagerProg); break; + case SETBYTE: + ackManagerProg++; + ackManagerByte=pgm_read_byte_near(ackManagerProg); + break; + case STASHLOCOID: ackManagerStash=ackManagerByte; // stash value from CV17 break; @@ -724,6 +758,8 @@ void DCC::ackManagerLoop(bool blocking) { while (opcode!=SKIPTARGET) { ackManagerProg++; opcode=pgm_read_byte_near(ackManagerProg); + // Jump over second byte of any 2-byte opcodes. + if (opcode==SETBIT || opcode==SETBYTE || opcode==SETCV) ackManagerProg++; } break; case SKIPTARGET: diff --git a/DCC.h b/DCC.h index 122e934..d7430c6 100644 --- a/DCC.h +++ b/DCC.h @@ -37,12 +37,14 @@ 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 STASHLOCOID, // keeps current byte value for later COMBINELOCOID, // combines current value with stashed value and returns it ITSKIP, // skip to SKIPTARGET if ack true From 7d90e4241a3e27f6348a9f7a0fa9eaa3ed711d6b Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 18 Jan 2021 10:06:46 +0000 Subject: [PATCH 08/15] Add command Automatically clears consist and manages short/long addresses --- DCC.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++- DCC.h | 5 ++++ DCCEXParser.cpp | 18 ++++++++++---- DCCEXParser.h | 1 + 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 421dd9b..f192ef5 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -399,6 +399,44 @@ const ackOp PROGMEM 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. @@ -441,6 +479,13 @@ 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); + int wordval; + 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; @@ -573,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; @@ -588,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. @@ -742,6 +795,14 @@ void DCC::ackManagerLoop(bool blocking) { ackManagerByte=pgm_read_byte_near(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 d7430c6..d8a1b6b 100644 --- a/DCC.h +++ b/DCC.h @@ -45,6 +45,8 @@ enum ackOp 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 @@ -90,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 @@ -126,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/DCCEXParser.cpp b/DCCEXParser.cpp index fd1cea0..efbba32 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -351,9 +351,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 @@ -780,6 +783,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 bef1199..8304e95 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -59,6 +59,7 @@ 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; From 611838d60c8503dfe5c11e5b2c2e8f83d35da219 Mon Sep 17 00:00:00 2001 From: mstevetodd Date: Mon, 18 Jan 2021 17:46:41 -0500 Subject: [PATCH 09/15] add warn/trip level to meter response (#120) * send milliAmps and meter setup for new JMRI Meter function * add warn/trip level to meter response provides support for separate max vs trip levels --- DCCEXParser.cpp | 8 ++++---- DCCWaveform.h | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index fd1cea0..3ecdb61 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -441,10 +441,10 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) } return; - case 'c': // READ CURRENT - // - StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getCurrentmA(), DCCWaveform::mainTrack.getMaxmA()); - // StringFormatter::send(stream, F(""), DCCWaveform::progTrack.getCurrentmA(), DCCWaveform::progTrack.getMaxmA()); + 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; diff --git a/DCCWaveform.h b/DCCWaveform.h index 3755979..7908e02 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -69,10 +69,16 @@ class DCCWaveform { } inline int getMaxmA() { if (maxmA == 0) { //only calculate this for first request, it doesn't change - maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); + 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; volatile byte sentResetsSincePacket; @@ -124,6 +130,7 @@ class DCCWaveform { byte pendingRepeats; int lastCurrent; int maxmA; + int tripmA; // current sampling POWERMODE powerMode; From 2ce4c8066e49c6bbbc5073db480c860644d00736 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 21 Jan 2021 11:10:52 +0000 Subject: [PATCH 10/15] Update version.h --- version.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 43f4764..e47b1ec 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" // const char VERSION[] PROGMEM ="0.2.0"; -#define VERSION "3.0.2" - +#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 From 7c7305ba1d3846cd32bdb6cb7e66db09cf5a3767 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 21 Jan 2021 10:08:35 -0500 Subject: [PATCH 11/15] Update Prod-Release-Notes.md --- Release - Architecture Doc/Prod-Release-Notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Release - Architecture Doc/Prod-Release-Notes.md b/Release - Architecture Doc/Prod-Release-Notes.md index eeaa466..e0c89c4 100644 --- a/Release - Architecture Doc/Prod-Release-Notes.md +++ b/Release - Architecture Doc/Prod-Release-Notes.md @@ -4,6 +4,10 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production R - **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. - **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 From a91dc981841e48bd6b5ffb846108d41b265e4ec4 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 21 Jan 2021 10:13:38 -0500 Subject: [PATCH 12/15] Update Prod-Release-Notes.md --- Release - Architecture Doc/Prod-Release-Notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Release - Architecture Doc/Prod-Release-Notes.md b/Release - Architecture Doc/Prod-Release-Notes.md index e0c89c4..35e8f4b 100644 --- a/Release - Architecture Doc/Prod-Release-Notes.md +++ b/Release - Architecture Doc/Prod-Release-Notes.md @@ -4,6 +4,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production R - **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. - **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** From f646f12c655801e208abbae70c7dd9c3454d87f2 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 21 Jan 2021 10:19:34 -0500 Subject: [PATCH 13/15] Update Prod-Release-Notes.md --- Release - Architecture Doc/Prod-Release-Notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Release - Architecture Doc/Prod-Release-Notes.md b/Release - Architecture Doc/Prod-Release-Notes.md index 35e8f4b..0893e1a 100644 --- a/Release - Architecture Doc/Prod-Release-Notes.md +++ b/Release - Architecture Doc/Prod-Release-Notes.md @@ -1,7 +1,7 @@ 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 From 8a9feaef22e6d161c20c7ea57a1a11804502a47f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 25 Jan 2021 15:26:39 +0000 Subject: [PATCH 14/15] Clean simple Timer interface Removes overkill files, puts all timer in a single small file. (DCCTimer) --- ATMEGA2560/Timer.h | 194 --------------------------------------- ATMEGA328/Timer.h | 208 ------------------------------------------ ArduinoTimers.h | 18 ---- CommandStation-EX.ino | 4 +- DCC.cpp | 5 +- DCC.h | 2 +- DCCTimer.cpp | 54 +++++++++++ DCCTimer.h | 13 +++ DCCWaveform.cpp | 37 ++------ DCCWaveform.h | 5 +- Timer.cpp | 52 ----------- VirtualTimer.h | 21 ----- 12 files changed, 79 insertions(+), 534 deletions(-) delete mode 100644 ATMEGA2560/Timer.h delete mode 100644 ATMEGA328/Timer.h delete mode 100644 ArduinoTimers.h create mode 100644 DCCTimer.cpp create mode 100644 DCCTimer.h delete mode 100644 Timer.cpp delete mode 100644 VirtualTimer.h diff --git a/ATMEGA2560/Timer.h b/ATMEGA2560/Timer.h deleted file mode 100644 index 2568c9e..0000000 --- a/ATMEGA2560/Timer.h +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef ATMEGA2560Timer_h -#define ATMEGA2560Timer_h - -#include "../VirtualTimer.h" -#include - -class Timer : public VirtualTimer { -private: - int pwmPeriod; - unsigned long timer_resolution; - unsigned char clockSelectBits; - int timer_num; - unsigned long lastMicroseconds; -public: -void (*isrCallback)(); - Timer(int timer_num) { - switch (timer_num) - { - case 1: - case 3: - case 4: - case 5: - timer_resolution = 65536; - break; - } - this->timer_num = timer_num; - lastMicroseconds = 0; - } - - void initialize() { - switch (timer_num) - { - case 1: - TCCR1B = _BV(WGM13) | _BV(WGM12); - TCCR1A = _BV(WGM11); - break; - case 3: - TCCR3B = _BV(WGM33) | _BV(WGM32); - TCCR3A = _BV(WGM31); - break; - case 4: - TCCR4B = _BV(WGM43) | _BV(WGM42); - TCCR4A = _BV(WGM41); - break; - case 5: - TCCR5B = _BV(WGM53) | _BV(WGM52); - TCCR5A = _BV(WGM51); - break; - } - } - - void setPeriod(unsigned long microseconds) { - if(microseconds == lastMicroseconds) - return; - lastMicroseconds = microseconds; - const unsigned long cycles = (F_CPU / 1000000) * microseconds; - if (cycles < timer_resolution) { - clockSelectBits = 1 << 0; - pwmPeriod = cycles; - } else - if (cycles < timer_resolution * 8) { - clockSelectBits = 1 << 1; - pwmPeriod = cycles / 8; - } else - if (cycles < timer_resolution * 64) { - clockSelectBits = (1 << 0) | (1 << 1); - pwmPeriod = cycles / 64; - } else - if (cycles < timer_resolution * 256) { - clockSelectBits = 1 << 2; - pwmPeriod = cycles / 256; - } else - if (cycles < timer_resolution * 1024) { - clockSelectBits = (1 << 2) | (1 << 0); - pwmPeriod = cycles / 1024; - } else { - clockSelectBits = (1 << 2) | (1 << 0); - pwmPeriod = timer_resolution - 1; - } - - switch (timer_num) - { - case 1: - ICR1 = pwmPeriod; - TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits; - break; - case 3: - ICR3 = pwmPeriod; - TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits; - break; - case 4: - ICR4 = pwmPeriod; - TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits; - break; - case 5: - ICR5 = pwmPeriod; - TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits; - break; - } - - } - void start() { - switch (timer_num) - { - case 1: - TCCR1B = 0; - TCNT1 = 0; // TODO: does this cause an undesired interrupt? - TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits; - break; - case 3: - TCCR3B = 0; - TCNT3 = 0; // TODO: does this cause an undesired interrupt? - TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits; - break; - case 4: - TCCR4B = 0; - TCNT4 = 0; // TODO: does this cause an undesired interrupt? - TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits; - break; - case 5: - TCCR5B = 0; - TCNT5 = 0; // TODO: does this cause an undesired interrupt? - TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits; - break; - } - - - } - void stop() { - switch (timer_num) - { - case 1: - TCCR1B = _BV(WGM13) | _BV(WGM12); - break; - case 3: - TCCR3B = _BV(WGM33) | _BV(WGM32); - break; - case 4: - TCCR4B = _BV(WGM43) | _BV(WGM42); - break; - case 5: - TCCR5B = _BV(WGM53) | _BV(WGM52); - break; - } - } - - void attachInterrupt(void (*isr)()) { - isrCallback = isr; - - switch (timer_num) - { - case 1: - TIMSK1 = _BV(TOIE1); - break; - case 3: - TIMSK3 = _BV(TOIE3); - break; - case 4: - TIMSK4 = _BV(TOIE4); - break; - case 5: - TIMSK5 = _BV(TOIE5); - break; - } - } - - void detachInterrupt() { - switch (timer_num) - { - case 1: - TIMSK1 = 0; - break; - case 3: - TIMSK3 = 0; - break; - case 4: - TIMSK4 = 0; - break; - case 5: - TIMSK5 = 0; - break; - } - } - -}; - -extern Timer TimerA; -extern Timer TimerB; -extern Timer TimerC; -extern Timer TimerD; - - - -#endif \ No newline at end of file diff --git a/ATMEGA328/Timer.h b/ATMEGA328/Timer.h deleted file mode 100644 index 6204953..0000000 --- a/ATMEGA328/Timer.h +++ /dev/null @@ -1,208 +0,0 @@ -#ifndef ATMEGA328Timer_h -#define ATMEGA328Timer_h - -#include "../VirtualTimer.h" -#include - -class Timer : public VirtualTimer { -private: - int pwmPeriod; - unsigned long timer_resolution; - unsigned char clockSelectBits; - int timer_num; - unsigned long lastMicroseconds; -public: -void (*isrCallback)(); - Timer(int timer_num) { - switch (timer_num) - { - //case 0: - case 2: - timer_resolution = 256; - break; - case 1: - timer_resolution = 65536; - break; - } - this->timer_num = timer_num; - lastMicroseconds = 0; - } - - void initialize() { - switch (timer_num) - { - // case 0: - // TCCR0B = _BV(WGM02); - // TCCR0A = _BV(WGM00) | _BV(WGM01); - // break; - case 1: - TCCR1B = _BV(WGM13) | _BV(WGM12); - TCCR1A = _BV(WGM11); - break; - case 2: - TCCR2B = _BV(WGM22); - TCCR2A = _BV(WGM20) | _BV(WGM21); - break; - } - } - - void setPeriod(unsigned long microseconds) { - if(microseconds == lastMicroseconds) - return; - lastMicroseconds = microseconds; - const unsigned long cycles = (F_CPU / 1000000) * microseconds; - - switch(timer_num) { - case 2: - if (cycles < timer_resolution) { - clockSelectBits = 1 << 0; - pwmPeriod = cycles; - } else - if (cycles < timer_resolution * 8) { - clockSelectBits = 1 << 1; - pwmPeriod = cycles / 8; - } else - if (cycles < timer_resolution * 32) { - clockSelectBits = 1 << 0 | 1 << 1; - pwmPeriod = cycles / 32; - } else - if (cycles < timer_resolution * 64) { - clockSelectBits = 1 << 2; - pwmPeriod = cycles / 64; - } else - if (cycles < timer_resolution * 128) { - clockSelectBits = 1 << 2 | 1 << 0; - pwmPeriod = cycles / 128; - } else - if (cycles < timer_resolution * 256) { - clockSelectBits = 1 << 2 | 1 << 1; - pwmPeriod = cycles / 256; - } else - if (cycles < timer_resolution * 1024) { - clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0; - pwmPeriod = cycles / 1024; - } else { - clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0; - pwmPeriod = timer_resolution - 1; - } - break; - //case 0: - case 1: - if (cycles < timer_resolution) { - clockSelectBits = 1 << 0; - pwmPeriod = cycles; - } else - if (cycles < timer_resolution * 8) { - clockSelectBits = 1 << 1; - pwmPeriod = cycles / 8; - } else - if (cycles < timer_resolution * 64) { - clockSelectBits = (1 << 0) | (1 << 1); - pwmPeriod = cycles / 64; - } else - if (cycles < timer_resolution * 256) { - clockSelectBits = 1 << 2; - pwmPeriod = cycles / 256; - } else - if (cycles < timer_resolution * 1024) { - clockSelectBits = (1 << 2) | (1 << 0); - pwmPeriod = cycles / 1024; - } else { - clockSelectBits = (1 << 2) | (1 << 0); - pwmPeriod = timer_resolution - 1; - } - break; - } - - switch (timer_num) - { - // case 0: - // OCR0A = pwmPeriod; - // TCCR0B = _BV(WGM02) | clockSelectBits; - // break; - case 1: - ICR1 = pwmPeriod; - TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits; - break; - case 2: - OCR2A = pwmPeriod; - TCCR2B = _BV(WGM22) | clockSelectBits; - break; - } - - } - void start() { - switch (timer_num) - { - // case 0: - // TCCR0B = 0; - // TCNT0 = 0; // TODO: does this cause an undesired interrupt? - // TCCR0B = _BV(WGM02) | clockSelectBits; - // break; - case 1: - TCCR1B = 0; - TCNT1 = 0; // TODO: does this cause an undesired interrupt? - TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits; - break; - case 2: - TCCR2B = 0; - TCNT2 = 0; // TODO: does this cause an undesired interrupt? - TCCR2B = _BV(WGM22) | clockSelectBits; - break; - } - - - } - void stop() { - switch (timer_num) - { - // case 0: - // TCCR0B = _BV(WGM02); - // break; - case 1: - TCCR1B = _BV(WGM13) | _BV(WGM12); - break; - case 2: - TCCR2B = _BV(WGM22); - break; - } - } - - void attachInterrupt(void (*isr)()) { - isrCallback = isr; - - switch (timer_num) - { - // case 0: - // TIMSK0 = _BV(TOIE0); - // break; - case 1: - TIMSK1 = _BV(TOIE1); - break; - case 2: - TIMSK2 = _BV(TOIE2); - break; - } - } - - void detachInterrupt() { - switch (timer_num) - { - // case 0: - // TIMSK0 = 0; - // break; - case 1: - TIMSK1 = 0; - break; - case 2: - TIMSK2 = 0; - break; - } - } - -}; - -extern Timer TimerA; -extern Timer TimerB; - -#endif \ No newline at end of file diff --git a/ArduinoTimers.h b/ArduinoTimers.h deleted file mode 100644 index c0acd30..0000000 --- a/ArduinoTimers.h +++ /dev/null @@ -1,18 +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" -#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 c0c3a1d..4906498 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -49,9 +49,7 @@ 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) diff --git a/DCC.cpp b/DCC.cpp index f192ef5..f426bb0 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -45,7 +45,7 @@ const byte FN_GROUP_5=0x10; __FlashStringHelper* DCC::shieldName=NULL; -void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { +void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { shieldName=(__FlashStringHelper*)motorShieldName; DIAG(F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); @@ -53,7 +53,7 @@ void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDr (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) { @@ -481,7 +481,6 @@ void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) { void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) { if (id<=0 || id>9999) callback(-1); - int wordval; if (id<=127) ackManagerSetup(id,SHORT_LOCO_ID_PROG, callback, blocking); else ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking); } diff --git a/DCC.h b/DCC.h index d8a1b6b..162cbfe 100644 --- a/DCC.h +++ b/DCC.h @@ -64,7 +64,7 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1); + static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); static void loop(); // Public DCC API functions diff --git a/DCCTimer.cpp b/DCCTimer.cpp new file mode 100644 index 0000000..818937a --- /dev/null +++ b/DCCTimer.cpp @@ -0,0 +1,54 @@ +/* + * © 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) + noInterrupts(); + TCCR1A = 0; + ICR1 = ((F_CPU / 1000000) * (slow? DCC_SLOW_TIME : DCC_SIGNAL_TIME)) >>1; + TCNT1 = 0; + TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 + TIMSK1 = _BV(TOIE1); // Enable Software interrupt + interrupts(); +} + +// Timer interrupt every 58uS +ISR(TIMER1_OVF_vect) +{ + 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 9eb7022..90db3ee 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -20,10 +20,9 @@ #include #include "DCCWaveform.h" +#include "DCCTimer.h" #include "DIAG.h" -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); @@ -31,32 +30,18 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; -VirtualTimer * DCCWaveform::interruptTimer=NULL; -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); - switch (timerNumber) { - case 1: interruptTimer= &TimerA; break; - case 2: interruptTimer= &TimerB; break; -#ifndef ARDUINO_AVR_UNO - case 3: interruptTimer= &TimerC; break; -#endif - default: - DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber); - return; - } - interruptTimer->initialize(); - interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle - interruptTimer->attachInterrupt(interruptHandler); - interruptTimer->start(); + DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::setDiagnosticSlowWave(bool slow) { - interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME); - interruptTimer->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")); } @@ -65,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(); @@ -112,10 +95,6 @@ POWERMODE DCCWaveform::getPowerMode() { } void DCCWaveform::setPowerMode(POWERMODE mode) { - - // Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away. - if (!interruptTimer) return; - powerMode = mode; bool ison = (mode == POWERMODE::ON); motorDriver->setPower( ison); @@ -168,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 7908e02..e3c05fd 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -20,7 +20,6 @@ #ifndef DCCWaveform_h #define DCCWaveform_h #include "MotorDriver.h" -#include "ArduinoTimers.h" // Wait times for power management. Unit: milliseconds const int POWER_SAMPLE_ON_WAIT = 100; @@ -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; @@ -105,7 +104,7 @@ class DCCWaveform { } private: - static VirtualTimer * interruptTimer; + static void interruptHandler(); bool interrupt1(); void interrupt2(); diff --git a/Timer.cpp b/Timer.cpp deleted file mode 100644 index 10673ec..0000000 --- a/Timer.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit to David Cutting - -#include - -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - -#include "ATMEGA2560/Timer.h" - -Timer TimerA(1); -Timer TimerB(3); -Timer TimerC(4); -Timer TimerD(5); - -ISR(TIMER1_OVF_vect) -{ - TimerA.isrCallback(); -} - -ISR(TIMER3_OVF_vect) -{ - TimerB.isrCallback(); -} - -ISR(TIMER4_OVF_vect) -{ - TimerC.isrCallback(); -} - -ISR(TIMER5_OVF_vect) -{ - TimerD.isrCallback(); -} - -#elif defined(ARDUINO_AVR_UNO) // Todo: add other 328 boards for compatibility - -#include "ATMEGA328/Timer.h" - -Timer TimerA(1); -Timer TimerB(2); - -ISR(TIMER1_OVF_vect) -{ - TimerA.isrCallback(); -} - -ISR(TIMER2_OVF_vect) -{ - TimerB.isrCallback(); -} - -#endif diff --git a/VirtualTimer.h b/VirtualTimer.h deleted file mode 100644 index 5e3832f..0000000 --- a/VirtualTimer.h +++ /dev/null @@ -1,21 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit to David Cutting - -#ifndef VirtualTimer_h -#define VirtualTimer_h - -class VirtualTimer -{ -public: - virtual void initialize() = 0; - virtual void setPeriod(unsigned long microseconds) = 0; - virtual void start() = 0; - virtual void stop() = 0; - - virtual void attachInterrupt(void (*isr)()) = 0; - virtual void detachInterrupt() = 0; -private: - -}; - -#endif From cbb039c02f84f25f5b751a8e841f393badb83867 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 25 Jan 2021 20:20:41 +0000 Subject: [PATCH 15/15] Timer port --- DCCTimer.cpp | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 818937a..94fdfba 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -38,17 +38,36 @@ 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(); - TCCR1A = 0; - ICR1 = ((F_CPU / 1000000) * (slow? DCC_SLOW_TIME : DCC_SIGNAL_TIME)) >>1; - TCNT1 = 0; - TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 - TIMSK1 = _BV(TOIE1); // Enable Software interrupt + +#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(); } -// Timer interrupt every 58uS -ISR(TIMER1_OVF_vect) +// ISR called by timer interrupt every 58uS +ISR(ISR_NAME) { +#ifdef ARDUINO_ARCH_MEGAAVR + TCB0.INTFLAGS = TCB_CAPT_bm; +#endif if (interruptHandler) interruptHandler(); }