From d7b2cf3d76a8b2e5f994406af0df978b0f15aeb1 Mon Sep 17 00:00:00 2001 From: Fred Date: Tue, 23 Mar 2021 10:37:05 -0400 Subject: [PATCH] Assorted bits (#138) * LCN * Prevent deprecated compiler warning * Implement huge function numbers * new commands forget locos. <9> ESTOP ALL. reboot arduino * Waveform accuracy msg * Drop post-write verify * UNUSED_PIN current measure and callback -2 for cv actions. * Correct diags * ESTOP a forget loco * ESTOP loco on forget * Avoid compiler warning * current sensor offset * Restore <1 JOIN> after prog track operation * ESTOP <-> FORGET * Auto current offset detection * manage current offset and diagnostics * neater msg at startup * Add startup message to LCN master * DCC::setJoinRelayPin Co-authored-by: Asbelos --- CommandStation-EX.ino | 9 ++++++ DCC.cpp | 59 +++++++++++++++++++++++++++------- DCC.h | 5 +-- DCCEX.h | 1 + DCCEXParser.cpp | 27 +++++++++++++++- DCCWaveform.cpp | 4 +-- DCCWaveform.h | 3 ++ LCN.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++ LCN.h | 16 ++++++++++ LiquidCrystal_I2C.h | 6 ++-- MotorDriver.cpp | 32 +++++++++++++++---- MotorDriver.h | 5 ++- MotorDrivers.h | 2 +- StringFormatter.cpp | 1 + StringFormatter.h | 1 + Turnouts.cpp | 5 +++ Turnouts.h | 3 +- 17 files changed, 225 insertions(+), 28 deletions(-) create mode 100644 LCN.cpp create mode 100644 LCN.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 6694244..5355376 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -95,6 +95,11 @@ void setup() #undef SETUP #endif + #if defined(LCN_SERIAL) + LCN_SERIAL.begin(115200); + LCN::init(LCN_SERIAL); + #endif + LCD(1,F("Ready")); } @@ -121,6 +126,10 @@ void loop() RMFT::loop(); #endif + #if defined(LCN_SERIAL) + LCN::loop(); + #endif + LCDDisplay::loop(); // ignored if LCD not in use // Report any decrease in memory (will automatically trigger on first call) diff --git a/DCC.cpp b/DCC.cpp index e6b3c64..890adfe 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -47,16 +47,10 @@ const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; byte DCC::joinRelay=UNUSED_PIN; -void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, - byte joinRelayPin) { +void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { shieldName=(FSH *)motorShieldName; DIAG(F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); - joinRelay=joinRelayPin; - if (joinRelay!=UNUSED_PIN) { - pinMode(joinRelay,OUTPUT); - digitalWrite(joinRelay,LOW); // high is relay disengaged - } // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); @@ -64,6 +58,14 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv DCCWaveform::begin(mainDriver,progDriver); } +void DCC::setJoinRelayPin(byte joinRelayPin) { + joinRelay=joinRelayPin; + if (joinRelay!=UNUSED_PIN) { + pinMode(joinRelay,OUTPUT); + digitalWrite(joinRelay,LOW); // LOW is relay disengaged + } +} + void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { byte speedCode = (tSpeed & 0x7F) + tDirection * 128; setThrottle2(cab, speedCode); @@ -114,7 +116,28 @@ bool DCC::getThrottleDirection(int cab) { // Set function to value on or off void DCC::setFn( int cab, byte functionNumber, bool on) { - if (cab<=0 || functionNumber>28) return; + if (cab<=0 ) return; + + if (functionNumber>28) { + //non reminding advanced binary bit set + byte b[5]; + byte nB = 0; + if (cab > 127) + b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address + b[nB++] = lowByte(cab); + if (functionNumber <= 127) { + b[nB++] = 0b11011101; // Binary State Control Instruction short form + b[nB++] = functionNumber | (on ? 0x80 : 0); + } + else { + b[nB++] = 0b11000000; // Binary State Control Instruction long form + b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag + b[nB++] = functionNumber >>8 ; // high order bits + } + DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + return; + } + int reg = lookupSpeedTable(cab); if (reg<0) return; @@ -293,7 +316,8 @@ const ackOp FLASH READ_BIT_PROG[] = { const ackOp FLASH WRITE_BYTE_PROG[] = { BASELINE, WB,WACK, // Write - VB,WACK, // validate byte + // VB,WACK, // validate byte, unnecessary after write gave ACK. + // Also, in some cases, like decoder reset, the value read back is not the same as written. ITC1, // if ok callback (1) FAIL // callback (-1) }; @@ -498,12 +522,15 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) { ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback); } -void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco +void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco + setThrottle2(cab,1); // ESTOP this loco if still on track int reg=lookupSpeedTable(cab); if (reg>=0) speedTable[reg].loco=0; + setThrottle2(cab,1); // ESTOP if this loco still on track } void DCC::forgetAllLocos() { // removes all speed reminders - for (int i=0;i to state before BASELINE + setProgTrackSyncMain(ackManagerRejoin); + if (Diag::ACK) DIAG(F("\nCallback(%d)\n"),value); (ackManagerCallback)( value); } diff --git a/DCC.h b/DCC.h index 4a9c5e0..aa4451f 100644 --- a/DCC.h +++ b/DCC.h @@ -65,8 +65,8 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, - byte joinRelayPin=UNUSED_PIN); + static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); + static void setJoinRelayPin(byte joinRelayPin); static void loop(); // Public DCC API functions @@ -135,6 +135,7 @@ private: static int ackManagerWord; static byte ackManagerStash; static bool ackReceived; + static bool ackManagerRejoin; static ACK_CALLBACK ackManagerCallback; static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback); static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback); diff --git a/DCCEX.h b/DCCEX.h index 2d1d183..244be19 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -14,6 +14,7 @@ #include "EthernetInterface.h" #endif #include "LCD_Implementation.h" +#include "LCN.h" #include "freeMemory.h" #if __has_include ( "myAutomation.h") diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2556cc2..e902e99 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -30,6 +30,7 @@ #include "EEStore.h" #include "DIAG.h" +#include // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command @@ -51,6 +52,9 @@ const int HASH_KEYWORD_LIMIT = 27413; const int HASH_KEYWORD_ETHERNET = -30767; const int HASH_KEYWORD_MAX = 16244; const int HASH_KEYWORD_MIN = 15978; +const int HASH_KEYWORD_LCN = 15137; +const int HASH_KEYWORD_RESET = 26133; + int DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; @@ -477,6 +481,10 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) } return; + case '!': // ESTOP ALL + DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. + return; + case 'c': // SEND METER RESPONSES // StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getCurrentmA(), @@ -520,6 +528,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS); return; + case '-': // Forget Loco <- [cab]> + if (params > 1 || p[0]<0) break; + if (p[0]==0) DCC::forgetAllLocos(); + else DCC::forgetLoco(p[0]); + return; + case 'F': // New command to call the new Loco Function API if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); @@ -756,11 +770,22 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) case HASH_KEYWORD_WIT: // Diag::WITHROTTLE = onOff; return true; + + case HASH_KEYWORD_LCN: // + Diag::LCN = onOff; + return true; case HASH_KEYWORD_PROGBOOST: DCC::setProgTrackBoost(true); - return true; + return true; + case HASH_KEYWORD_RESET: + { + wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms + delay(50); // wait for the prescaller time to expire + break; // and if we didnt restart + } + case HASH_KEYWORD_EEPROM: // if (params >= 2) EEStore::dump(p[1]); diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index eb23152..f392a59 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -46,9 +46,9 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); if (MotorDriver::usePWM) - DIAG(F("\nWaveform using PWM pins for accuracy.")); + DIAG(F("\nSignal pin config: high accuracy waveform")); else - DIAG(F("\nWaveform accuracy limited by signal pin configuration.")); + DIAG(F("\nSignal pin config: normal accuracy waveform")); DCCTimer::begin(DCCWaveform::interruptHandler); } diff --git a/DCCWaveform.h b/DCCWaveform.h index db6c7f7..f3f26c7 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -94,6 +94,9 @@ class DCCWaveform { autoPowerOff=false; } }; + inline bool canMeasureCurrent() { + return motorDriver->canMeasureCurrent(); + }; inline void setAckLimit(int mA) { ackLimitmA = mA; } diff --git a/LCN.cpp b/LCN.cpp new file mode 100644 index 0000000..90c8ec5 --- /dev/null +++ b/LCN.cpp @@ -0,0 +1,74 @@ +/* + * © 2021, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX CommandStation-EX + * + * 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 . + */ + +#include "LCN.h" +#include "DIAG.h" +#include "Turnouts.h" +#include "Sensors.h" + +int LCN::id = 0; +Stream * LCN::stream=NULL; +bool LCN::firstLoop=true; + +void LCN::init(Stream & lcnstream) { + stream=&lcnstream; + DIAG(F("\nLCN connection setup\n")); +} + + +// Inbound LCN traffic is postfix notation... nnnX where nnn is an id, X is the opcode +void LCN::loop() { + if (!stream) return; + if (firstLoop) { + firstLoop=false; + stream->println('X'); + return; + } + + while (stream->available()) { + int ch = stream->read(); + if (ch >= 0 && ch <= '9') { // accumulate id value + id = 10 * id + ch - '0'; + } + else if (ch == 't' || ch == 'T') { // Turnout opcodes + if (Diag::LCN) DIAG(F("\nLCN IN %d%c\n"),id,(char)ch); + Turnout * tt = Turnout::get(id); + if (!tt) Turnout::create(id, LCN_TURNOUT_ADDRESS, 0); + if (ch == 't') tt->data.tStatus |= STATUS_ACTIVE; + else tt->data.tStatus &= ~STATUS_ACTIVE; + Turnout::turnoutlistHash++; // signals ED update of turnout data + id = 0; + } + else if (ch == 'S' || ch == 's') { + if (Diag::LCN) DIAG(F("\nLCN IN %d%c\n"),id,(char)ch); + Sensor * ss = Sensor::get(id); + if (!ss) ss = Sensor::create(id, 255,0); // impossible pin + ss->active = ch == 'S'; + id = 0; + } + else id = 0; // ignore any other garbage from LCN + } +} + +void LCN::send(char opcode, int id, bool state) { + if (stream) { + StringFormatter::send(stream,F("%c/%d/%d"), opcode, id , state); + if (Diag::LCN) DIAG(F("\nLCN OUT %c/%d/%d\n"), opcode, id , state); + } +} diff --git a/LCN.h b/LCN.h new file mode 100644 index 0000000..a49745b --- /dev/null +++ b/LCN.h @@ -0,0 +1,16 @@ +#ifndef LCN_h +#define LCN_h +#include + +class LCN { + public: + static void init(Stream & lcnstream); + static void loop(); + static void send(char opcode, int id, bool state); + private : + static bool firstLoop; + static Stream * stream; + static int id; +}; + +#endif diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 4f69bf5..2d6919e 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -67,9 +67,9 @@ #define LCD_BACKLIGHT 0x08 #define LCD_NOBACKLIGHT 0x00 -#define En B00000100 // Enable bit -#define Rw B00000010 // Read/Write bit -#define Rs B00000001 // Register select bit +#define En 0b00000100 // Enable bit +#define Rw 0b00000010 // Read/Write bit +#define Rs 0b00000001 // Register select bit class LiquidCrystal_I2C : public Print { public: diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c9681d6..08518c4 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -58,7 +58,10 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 else brakePin=UNUSED_PIN; currentPin=current_pin; - pinMode(currentPin, INPUT); + if (currentPin!=UNUSED_PIN) { + pinMode(currentPin, INPUT); + senseOffset=analogRead(currentPin); // value of sensor at zero current + } faultPin=fault_pin; if (faultPin != UNUSED_PIN) { @@ -69,6 +72,12 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 senseFactor=sense_factor; tripMilliamps=trip_milliamps; rawCurrentTripValue=(int)(trip_milliamps / sense_factor); + + if (currentPin==UNUSED_PIN) + DIAG(F("\nMotorDriver ** WARNING ** No current or short detection\n")); + else + DIAG(F("\nMotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d\n"), + currentPin-A0, senseOffset,rawCurrentTripValue); } bool MotorDriver::isPWMCapable() { @@ -117,14 +126,24 @@ void MotorDriver::setSignal( bool high) { } } +bool MotorDriver::canMeasureCurrent() { + return currentPin!=UNUSED_PIN; +} /* * Return the current reading as pin reading 0 to 1023. If the fault * pin is activated return a negative current to show active fault pin. - * As there is no -0, ceat a little and return -1 in that case. + * As there is no -0, create a little and return -1 in that case. + * + * senseOffset handles the case where a shield returns values above or below + * a central value depending on direction. */ int MotorDriver::getCurrentRaw() { - int current = analogRead(currentPin); - if (faultPin != UNUSED_PIN && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) + if (currentPin==UNUSED_PIN) return 0; + + int current = analogRead(currentPin)-senseOffset; + if (current<0) current=0-current; + + if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) return (current == 0 ? -1 : -current); return current; // IMPORTANT: This function can be called in Interrupt() time within the 56uS timer @@ -140,7 +159,8 @@ int MotorDriver::mA2raw( unsigned int mA) { } void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { - DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin); + // DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin); + (void) type; // avoid compiler warning if diag not used above. uint8_t port = digitalPinToPort(pin); if (input) result.inout = portInputRegister(port); @@ -148,5 +168,5 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res result.inout = portOutputRegister(port); result.maskHIGH = digitalPinToBitMask(pin); result.maskLOW = ~result.maskHIGH; - DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x\n"),port, result.inout,input,result.maskHIGH); + // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x\n"),port, result.inout,input,result.maskHIGH); } diff --git a/MotorDriver.h b/MotorDriver.h index b47ae95..2af1b58 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -34,7 +34,8 @@ struct FASTPIN { class MotorDriver { public: - MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); + MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, + byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( bool on); virtual void setSignal( bool high); virtual void setBrake( bool on); @@ -45,6 +46,7 @@ class MotorDriver { return rawCurrentTripValue; } bool isPWMCapable(); + bool canMeasureCurrent(); static bool usePWM; static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs inline byte getFaultPin() { @@ -61,6 +63,7 @@ class MotorDriver { bool dualSignal; // true to use signalPin2 bool invertBrake; // brake pin passed as negative means pin is inverted float senseFactor; + int senseOffset; unsigned int tripMilliamps; int rawCurrentTripValue; }; diff --git a/MotorDrivers.h b/MotorDrivers.h index 54b5cc0..e011b0f 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -21,7 +21,7 @@ // If the brakePin is negative that means the sense // of the brake pin on the motor bridge is inverted // (HIGH == release brake) - +// // Arduino standard Motor Shield #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 204e0df..0533d62 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -34,6 +34,7 @@ bool Diag::CMD=false; bool Diag::WIFI=false; bool Diag::WITHROTTLE=false; bool Diag::ETHERNET=false; +bool Diag::LCN=false; void StringFormatter::diag( const FSH* input...) { diff --git a/StringFormatter.h b/StringFormatter.h index a3ba7fd..50ce0f7 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -33,6 +33,7 @@ class Diag { static bool WIFI; static bool WITHROTTLE; static bool ETHERNET; + static bool LCN; }; diff --git a/Turnouts.cpp b/Turnouts.cpp index 03093a1..885eb26 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -55,6 +55,11 @@ void Turnout::activate(bool state) { #ifdef EESTOREDEBUG DIAG(F("\nTurnout::activate(%d)\n"),state); #endif + if (data.address==LCN_TURNOUT_ADDRESS) { + // A LCN turnout is transmitted to the LCN master. + LCN::send('T',data.id,state); + return; // The tStatus will be updated by a message from the LCN master, later. + } if (state) data.tStatus|=STATUS_ACTIVE; else diff --git a/Turnouts.h b/Turnouts.h index 186149b..d04c664 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -21,11 +21,12 @@ #include #include "DCC.h" +#include "LCN.h" const byte STATUS_ACTIVE=0x80; // Flag as activated const byte STATUS_PWM=0x40; // Flag as a PWM turnout const byte STATUS_PWMPIN=0x3F; // PWM pin 0-63 - +const int LCN_TURNOUT_ADDRESS=-1; // spoof dcc address -1 indicates a LCN turnout struct TurnoutData { int id; uint8_t tStatus; // has STATUS_ACTIVE, STATUS_PWM, STATUS_PWMPIN