diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 8c79652..9f2baa3 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream } void CommandDistributor::forget(byte clientId) { - // keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); + if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); clients[clientId]=NONE_TYPE; } #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8e7cd33..cbb152e 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -41,6 +41,14 @@ #include "DCCTimer.h" #include "EXRAIL2.h" +// This macro can't be created easily as a portable function because the +// flashlist requires a far pointer for high flash access. +#define SENDFLASHLIST(stream,flashList) \ + for (int16_t i=0;;i+=sizeof(flashList[0])) { \ + int16_t value=GETHIGHFLASHW(flashList,i); \ + if (value==0) break; \ + StringFormatter::send(stream,F(" %d"),value); \ + } // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. @@ -569,8 +577,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F(" #ifdef EXRAIL_ACTIVE - sendFlashList(stream,RMFT2::routeIdList); - sendFlashList(stream,RMFT2::automationIdList); + SENDFLASHLIST(stream,RMFT2::routeIdList) + SENDFLASHLIST(stream,RMFT2::automationIdList) #endif } else { // @@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F("\n")); } -void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) { - for (int16_t i=0;;i++) { - int16_t value=GETFLASHW(flashList+i); - if (value==0) return; - StringFormatter::send(stream,F(" %d"),value); - } -} - bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) { diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index b26aa4d..0976bcd 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -31,14 +31,18 @@ #include "DCCTimer.h" #if defined(ARDUINO_NUCLEO_F411RE) -// STM32F411RE doesn't have Serial1 defined by default +// Nucleo-64 boards don't have Serial1 defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console -// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable -// for other DCC-EX uses like WiFi, DFPlayer, etc. -// Let's define Serial6 as an additional serial port (the only other option for the F411RE) +// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE -#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_NUCLEO_F446RE) +// Nucleo-64 boards don't have Serial1 defined by default +HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 33 - F446RE +// Serial2 is defined to use USART2 by default, but is in fact used as the diag console +// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #else diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 96cb447..f44f9dc 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -41,6 +41,7 @@ */ #include +#include "defines.h" #include "EXRAIL2.h" #include "DCC.h" #include "DCCWaveform.h" @@ -90,15 +91,26 @@ LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; -#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter) -#ifdef ARDUINO_ARCH_AVR -#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3)) -#else -#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3)) -#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A)) -#endif +LookList * RMFT2::onChangeLookup=NULL; + +#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 +// getOperand instance version, uses progCounter from instance. +uint16_t RMFT2::getOperand(byte n) { + return getOperand(progCounter,n); +} + +// getOperand static version, must be provided prog counter from loop etc. +uint16_t RMFT2::getOperand(int progCounter,byte n) { + int offset=progCounter+1+(n*3); + if (offset&1) { + byte lsb=GETHIGHFLASH(RouteCode,offset); + byte msb=GETHIGHFLASH(RouteCode,offset+1); + return msb<<8|lsb; + } + return GETHIGHFLASHW(RouteCode,offset); +} LookList::LookList(int16_t size) { m_size=size; @@ -139,12 +151,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { for (progCounter=0;; SKIPOP) { byte opcode=GET_OPCODE; if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter); + if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter); } return list; } /* static */ void RMFT2::begin() { + + DIAG(F("EXRAIL RoutCode at =%P"),RouteCode); + + bool saved_diag=diag; + diag=true; DCCEXParser::setRMFTFilter(RMFT2::ComandFilter); for (int f=0;fsetHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01); } char RMFT2::getRouteType(int16_t id) { - for (int16_t i=0;;i++) { - int16_t rid= GETFLASHW(routeIdList+i); + for (int16_t i=0;;i+=2) { + int16_t rid= GETHIGHFLASHW(routeIdList,i); if (rid==id) return 'R'; if (rid==0) break; } - for (int16_t i=0;;i++) { - int16_t rid= GETFLASHW(automationIdList+i); + for (int16_t i=0;;i+=2) { + int16_t rid= GETHIGHFLASHW(automationIdList,i); if (rid==id) return 'A'; if (rid==0) break; } @@ -308,7 +330,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { // do the signals // flags[n] represents the state of the nth signal in the table for (int sigslot=0;;sigslot++) { - VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4); + VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8); if (sigid==0) break; // end of signal list byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id StringFormatter::send(stream,F("\n%S[%d]"), @@ -555,7 +577,7 @@ void RMFT2::loop2() { if (delayTime!=0 && millis()-delayStart < delayTime) return; byte opcode = GET_OPCODE; - int16_t operand = GET_OPERAND(0); + int16_t operand = getOperand(0); // skipIf will get set to indicate a failing IF condition bool skipIf=false; @@ -621,13 +643,13 @@ void RMFT2::loop2() { case OPCODE_ATGTE: // wait for analog sensor>= value timeoutFlag=false; - if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break; + if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break; delayMe(50); return; case OPCODE_ATLT: // wait for analog sensor < value timeoutFlag=false; - if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break; + if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break; delayMe(50); return; @@ -638,7 +660,7 @@ void RMFT2::loop2() { case OPCODE_ATTIMEOUT2: if (readSensor(operand)) break; // success without timeout - if (millis()-timeoutStart > 100*GET_OPERAND(1)) { + if (millis()-timeoutStart > 100*getOperand(1)) { timeoutFlag=true; break; // and drop through } @@ -681,7 +703,7 @@ void RMFT2::loop2() { break; case OPCODE_POM: - if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); + if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1)); break; case OPCODE_POWEROFF: @@ -715,16 +737,20 @@ void RMFT2::loop2() { break; case OPCODE_IFGTE: // do next operand if sensor>= value - skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1)); + skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1)); break; case OPCODE_IFLT: // do next operand if sensor< value - skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1)); + skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1)); break; case OPCODE_IFNOT: // do next operand if sensor not set skipIf=readSensor(operand); break; + + case OPCODE_IFRE: // do next operand if rotary encoder != position + skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1)); + break; case OPCODE_IFRANDOM: // do block on random percentage skipIf=(uint8_t)micros() >= operand * 255/100; @@ -802,11 +828,11 @@ void RMFT2::loop2() { } case OPCODE_XFON: - DCC::setFn(operand,GET_OPERAND(1),true); + DCC::setFn(operand,getOperand(1),true); break; case OPCODE_XFOFF: - DCC::setFn(operand,GET_OPERAND(1),false); + DCC::setFn(operand,getOperand(1),false); break; case OPCODE_DCCACTIVATE: { @@ -898,7 +924,7 @@ void RMFT2::loop2() { case OPCODE_SENDLOCO: // cab, route { - int newPc=sequenceLookup->find(GET_OPERAND(1)); + int newPc=sequenceLookup->find(getOperand(1)); if (newPc<0) break; RMFT2* newtask=new RMFT2(newPc); // create new task newtask->loco=operand; @@ -916,7 +942,7 @@ void RMFT2::loop2() { case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) - IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); + IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3)); break; case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) @@ -948,6 +974,7 @@ void RMFT2::loop2() { case OPCODE_ONRED: case OPCODE_ONAMBER: case OPCODE_ONGREEN: + case OPCODE_ONCHANGE: break; @@ -965,7 +992,7 @@ void RMFT2::delayMe(long delay) { delayStart=millis(); } -boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { +bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { if (FLAGOVERFLOW(id)) return false; // Outside range limit byte f=flags[id]; f &= ~offMask; @@ -986,8 +1013,8 @@ void RMFT2::kill(const FSH * reason, int operand) { } int16_t RMFT2::getSignalSlot(int16_t id) { - for (int sigpos=0;;sigpos+=4) { - int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); + for (int sigslot=0;;sigslot++) { + int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8); if (sigid==0) { // end of signal list DIAG(F("EXRAIL Signal %d not defined"), id); return -1; @@ -997,9 +1024,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) { // but for a servo signal it will also have SERVO_SIGNAL_FLAG set. if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking - return sigpos/4; // relative slot in signals table + return sigslot; // relative slot in signals table } } + /* static */ void RMFT2::doSignal(int16_t id,char rag) { if (diag) DIAG(F(" doSignal %d %x"),id,rag); @@ -1016,11 +1044,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) { setFlag(sigslot,rag,SIGNAL_MASK); // Correct signal definition found, get the rag values - int16_t sigpos=sigslot*4; - VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); - VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); - VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); - VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); + int16_t sigpos=sigslot*8; + VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos); + VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2); + VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4); + VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6); if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin); VPIN sigtype=sigid & ~SIGNAL_ID_MASK; @@ -1073,6 +1101,11 @@ void RMFT2::activateEvent(int16_t addr, bool activate) { if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); } + +void RMFT2::changeEvent(int16_t vpin, bool change) { + // Hunt for an ONCHANGE for this sensor + if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); +} void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); @@ -1096,3 +1129,96 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { void RMFT2::printMessage2(const FSH * msg) { DIAG(F("EXRAIL(%d) %S"),loco,msg); } +static StringBuffer * buffer=NULL; +/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial +and handle the oddities like LCD, BROADCAST and PARSE */ +void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { + //DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id); + Print * stream=NULL; + // Find out where the string is going + switch (mode) { + case thrunge_print: + StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco); + stream=&Serial; + break; + + case thrunge_serial: stream=&Serial; break; + case thrunge_serial1: + #ifdef SERIAL1_COMMANDS + stream=&Serial1; + #endif + break; + case thrunge_serial2: + #ifdef SERIAL2_COMMANDS + stream=&Serial2; + #endif + break; + case thrunge_serial3: + #ifdef SERIAL3_COMMANDS + stream=&Serial3; + #endif + break; + case thrunge_serial4: + #ifdef SERIAL4_COMMANDS + stream=&Serial4; + #endif + break; + case thrunge_serial5: + #ifdef SERIAL5_COMMANDS + stream=&Serial5; + #endif + break; + case thrunge_serial6: + #ifdef SERIAL6_COMMANDS + stream=&Serial6; + #endif + break; + // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; + case thrunge_lcn: + #if defined(LCN_SERIAL) + stream=&LCN_SERIAL; + #endif + break; + case thrunge_parse: + case thrunge_broadcast: + case thrunge_lcd: + if (!buffer) buffer=new StringBuffer(); + buffer->flush(); + stream=buffer; + break; + } + if (!stream) return; + + #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + // if mega stream it out + for (;;strfar++) { + char c=pgm_read_byte_far(strfar); + if (c=='\0') break; + stream->write(c); + } + #else + // UNO/NANO CPUs dont have high memory + // 32 bit cpus dont care anyway + stream->print((FSH *)strfar); + #endif + + // and decide what to do next + switch (mode) { + case thrunge_print: + StringFormatter::send(&Serial,F(" *>\n")); + break; + // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; + case thrunge_parse: + DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL); + break; + case thrunge_broadcast: + // TODO CommandDistributor::broadcastText(buffer->getString()); + break; + case thrunge_lcd: + LCD(id,F("%s"),buffer->getString()); + break; + + default: break; + } +} + \ No newline at end of file diff --git a/EXRAIL2.h b/EXRAIL2.h index 323ae57..2ea2ba1 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -54,6 +54,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ENDTASK,OPCODE_ENDEXRAIL, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, + OPCODE_ONCHANGE, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -64,9 +65,16 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFTIMEOUT, OPCODE_IF,OPCODE_IFNOT, OPCODE_IFRANDOM,OPCODE_IFRESERVE, - OPCODE_IFCLOSED,OPCODE_IFTHROWN + OPCODE_IFCLOSED,OPCODE_IFTHROWN, + OPCODE_IFRE, }; +enum thrunger: byte { + thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, + thrunge_serial1, thrunge_serial2, thrunge_serial3, + thrunge_serial4, thrunge_serial5, thrunge_serial6, + thrunge_lcd, thrunge_lcn}; + // Flag bits for status of hardware and TPL @@ -107,16 +115,16 @@ class LookList { static void createNewTask(int route, uint16_t cab); static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); + static void changeEvent(int16_t id, bool change); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; static const int16_t SIGNAL_ID_MASK=0x0FFF; - // Throttle Info Access functions built by exrail macros static const byte rosterNameCount; - static const int16_t FLASH routeIdList[]; - static const int16_t FLASH automationIdList[]; - static const int16_t FLASH rosterIdList[]; + static const int16_t HIGHFLASH routeIdList[]; + static const int16_t HIGHFLASH automationIdList[]; + static const int16_t HIGHFLASH rosterIdList[]; static const FSH * getRouteDescription(int16_t id); static char getRouteType(int16_t id); static const FSH * getTurnoutDescription(int16_t id); @@ -137,6 +145,7 @@ private: static LookList* LookListLoader(OPCODE op1, OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); + static uint16_t getOperand(int progCounter,byte n); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); @@ -148,10 +157,12 @@ private: void kill(const FSH * reason=NULL,int operand=0); void printMessage(uint16_t id); // Built by RMFTMacros.h void printMessage2(const FSH * msg); + void thrungeString(uint32_t strfar, thrunger mode, byte id=0); + uint16_t getOperand(byte n); static bool diag; - static const FLASH byte RouteCode[]; - static const FLASH int16_t SignalDefinitions[]; + static const HIGHFLASH byte RouteCode[]; + static const HIGHFLASH int16_t SignalDefinitions[]; static byte flags[MAX_FLAGS]; static LookList * sequenceLookup; static LookList * onThrowLookup; @@ -161,7 +172,7 @@ private: static LookList * onRedLookup; static LookList * onAmberLookup; static LookList * onGreenLookup; - + static LookList * onChangeLookup; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 0211e22..32e28a2 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -72,6 +72,7 @@ #undef IFRESERVE #undef IFTHROWN #undef IFTIMEOUT +#undef IFRE #undef INVERT_DIRECTION #undef JOIN #undef KILLALL @@ -88,6 +89,7 @@ #undef ONGREEN #undef ONRED #undef ONTHROW +#undef ONCHANGE #undef PARSE #undef PAUSE #undef PIN_TURNOUT @@ -110,6 +112,9 @@ #undef SERIAL1 #undef SERIAL2 #undef SERIAL3 +#undef SERIAL4 +#undef SERIAL5 +#undef SERIAL6 #undef SERVO #undef SERVO2 #undef SERVO_TURNOUT @@ -182,6 +187,7 @@ #define IFTHROWN(turnout_id) #define IFRESERVE(block) #define IFTIMEOUT +#define IFRE(sensor_id,value) #define INVERT_DIRECTION #define JOIN #define KILLALL @@ -198,6 +204,7 @@ #define ONGREEN(signal_id) #define ONRED(signal_id) #define ONTHROW(turnout_id) +#define ONCHANGE(sensor_id) #define PAUSE #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) @@ -220,6 +227,9 @@ #define SERIAL1(msg) #define SERIAL2(msg) #define SERIAL3(msg) +#define SERIAL4(msg) +#define SERIAL5(msg) +#define SERIAL6(msg) #define SERVO(id,position,profile) #define SERVO2(id,position,duration) #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index d5424c6..b5e78d9 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -73,14 +73,14 @@ void exrailHalSetup() { #include "EXRAIL2MacroReset.h" #undef ROUTE #define ROUTE(id, description) id, -const int16_t FLASH RMFT2::routeIdList[]= { +const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" 0}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, -const int16_t FLASH RMFT2::automationIdList[]= { +const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" 0}; @@ -100,30 +100,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) { // Pass 4... Create Text sending functions #include "EXRAIL2MacroReset.h" const int StringMacroTracker1=__COUNTER__; +#define THRUNGE(msg,mode) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=mode;\ + break;\ + } #undef BROADCAST -#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break; +#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast) #undef PARSE -#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break; +#define PARSE(msg) THRUNGE(msg,thrunge_parse) #undef PRINT -#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break; +#define PRINT(msg) THRUNGE(msg,thrunge_print) #undef LCN -#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break; +#define LCN(msg) THRUNGE(msg,thrunge_lcn) #undef SERIAL -#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break; +#define SERIAL(msg) THRUNGE(msg,thrunge_serial) #undef SERIAL1 -#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break; +#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1) #undef SERIAL2 -#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break; +#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2) #undef SERIAL3 -#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break; +#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3) +#undef SERIAL4 +#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4) +#undef SERIAL5 +#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5) +#undef SERIAL6 +#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6) #undef LCD -#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break; +#define LCD(id,msg) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=thrunge_lcd; \ + lcdid=id;\ + break;\ + } void RMFT2::printMessage(uint16_t id) { + thrunger tmode; + uint32_t strfar=0; + byte lcdid=0; switch(id) { #include "myAutomation.h" default: break ; } + if (strfar) thrungeString(strfar,tmode,lcdid); } @@ -158,7 +182,7 @@ const byte RMFT2::rosterNameCount=0 #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) cabid, -const int16_t FLASH RMFT2::rosterIdList[]={ +const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" 0}; @@ -198,7 +222,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #undef VIRTUAL_SIGNAL #define VIRTUAL_SIGNAL(id) id,0,0,0, -const FLASH int16_t RMFT2::SignalDefinitions[] = { +const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -261,6 +285,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define IFRESERVE(block) OPCODE_IFRESERVE,V(block), #define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id), #define IFTIMEOUT OPCODE_IFTIMEOUT,0,0, +#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value), #define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0, #define JOIN OPCODE_JOIN,0,0, #define KILLALL OPCODE_KILLALL,0,0, @@ -277,6 +302,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), #define ONRED(signal_id) OPCODE_ONRED,V(signal_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), +#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), @@ -299,6 +325,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define SERIAL1(msg) PRINT(msg) #define SERIAL2(msg) PRINT(msg) #define SERIAL3(msg) PRINT(msg) +#define SERIAL4(msg) PRINT(msg) +#define SERIAL5(msg) PRINT(msg) +#define SERIAL6(msg) PRINT(msg) #define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0), #define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L), #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) @@ -323,7 +352,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { // Build RouteCode const int StringMacroTracker2=__COUNTER__; -const FLASH byte RMFT2::RouteCode[] = { +const HIGHFLASH byte RMFT2::RouteCode[] = { #include "myAutomation.h" OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 8dd4d31..7c5d768 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -60,14 +60,14 @@ EthernetInterface::EthernetInterface() connected=false; #ifdef IP_ADDRESS - if (Ethernet.begin(mac, IP_ADDRESS) == 0) + Ethernet.begin(mac, IP_ADDRESS); #else if (Ethernet.begin(mac) == 0) - #endif { DIAG(F("Ethernet.begin FAILED")); return; } + #endif if (Ethernet.hardwareStatus() == EthernetNoHardware) { DIAG(F("Ethernet shield not found or W5100")); } @@ -135,7 +135,10 @@ bool EthernetInterface::checkLink() { if(!connected) { DIAG(F("Ethernet cable connected")); connected=true; - IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address + #ifdef IP_ADDRESS + setLocalIP(IP_ADDRESS); // for static IP, set it again + #endif + IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static) server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT server->begin(); LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); diff --git a/FSH.h b/FSH.h index 38cc052..f4bf47e 100644 --- a/FSH.h +++ b/FSH.h @@ -34,42 +34,51 @@ * PROGMEM use FLASH instead * pgm_read_byte_near use GETFLASH instead. * pgm_read_word_near use GETFLASHW instead. + * + * Also: + * HIGHFLASH - PROGMEM forced to end of link so needs far pointers. + * GETHIGHFLASH,GETHIGHFLASHW to access them * */ #include +#ifdef ARDUINO_ARCH_AVR +// AVR devices have flash memory mapped differently +// progmem can be accessed by _near functions or _far +typedef __FlashStringHelper FSH; +#define FLASH PROGMEM +#define GETFLASH(addr) pgm_read_byte_near(addr) -#if defined(ARDUINO_ARCH_MEGAAVR) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) +// AVR_MEGA memory deliberately placed at end of link may need _far functions +#define HIGHFLASH __attribute__((section(".fini2"))) +#define GETFARPTR(data) pgm_get_far_address(data) +#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset) +#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset) +#else +// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent +// as there is no progmem above 32kb anyway. +#define HIGHFLASH PROGMEM +#define GETFARPTR(data) ((uint32_t)(data)) +#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset)) +#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset)) +#endif + +#else +// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access #ifdef F #undef F #endif -#define F(str) (str) -typedef char FSH; -#define GETFLASH(addr) (*(const unsigned char *)(addr)) -#define GETFLASHW(addr) (*(const unsigned short *)(addr)) -#define FLASH -#define strlen_P strlen -#define strcpy_P strcpy - -#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) - -typedef __FlashStringHelper FSH; -#define GETFLASH(addr) pgm_read_byte(addr) -// pgm_read_word is buggy if addr is odd but here -// we do only read well aligned addrs, the others are -// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp. -#define GETFLASHW(addr) pgm_read_word(addr) #ifdef FLASH #undef FLASH #endif -#define FLASH PROGMEM - -#else // AVR and AVR compat here - -typedef __FlashStringHelper FSH; -#define GETFLASH(addr) pgm_read_byte_near(addr) -#define GETFLASHW(addr) pgm_read_word_near(addr) -#define FLASH PROGMEM - -#endif // flash stuff -#endif // FSH +#define F(str) (str) +typedef char FSH; +#define FLASH +#define HIGHFLASH +#define GETFARPTR(data) ((uint32_t)(data)) +#define GETFLASH(addr) (*(const byte *)(addr)) +#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) +#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset)) +#endif +#endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 12884d8..d6c9738 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211181919Z" +#define GITHUB_SHA "devel-202212051450Z" diff --git a/IODevice.h b/IODevice.h index 3b583ba..77b440d 100644 --- a/IODevice.h +++ b/IODevice.h @@ -161,6 +161,8 @@ public: // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); + // Method to check if pins will overlap before creating new device. + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); protected: @@ -234,9 +236,6 @@ protected: // pin low if an input changes state. int16_t _gpioInterruptPin = -1; - // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -407,5 +406,8 @@ private: #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_duinoNodes.h" +#include "IO_EXIOExpander.h" + #endif // iodevice_h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h new file mode 100644 index 0000000..1e20fac --- /dev/null +++ b/IO_EXIOExpander.h @@ -0,0 +1,197 @@ +/* + * © 2021, Peter Cole. All rights reserved. + * + * This file is part of EX-CommandStation + * + * 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 . +*/ + +/* +* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices. +* This device driver will configure the device on startup, along with +* interacting with the device for all input/output duties. +* +* To create EX-IOExpander devices, these are defined in myHal.cpp: +* (Note the device driver is included by default) +* +* void halSetup() { +* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); +* EXIOExpander::create(800, 18, 0x65, 12, 8); +* } +* +* Note when defining the number of digital and analogue pins, there is no way to sanity check +* this from the device driver, and it is up to the user to define the correct values here. +* +* All pins available on the EX-IOExpander device must be accounted for. +* +* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will +* populate the first part of the specified vpin range, with the analogue pins populating the +* last part of the vpin range. +* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). +*/ + +#ifndef IO_EX_IOEXPANDER_H +#define IO_EX_IOEXPANDER_H + +#include "I2CManager.h" +#include "DIAG.h" +#include "FSH.h" + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for EX-IOExpander. + */ +class EXIOExpander : public IODevice { +public: + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + } + +private: + // Constructor + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + _firstVpin = firstVpin; + _nPins = nPins; + _i2cAddress = i2cAddress; + _numDigitalPins = numDigitalPins; + _numAnaloguePins = numAnaloguePins; + addDevice(this); + } + + void _begin() { + // Initialise EX-IOExander device + I2CManager.begin(); + if (I2CManager.exists(_i2cAddress)) { + _digitalOutBuffer[0] = EXIOINIT; + _digitalOutBuffer[1] = _numDigitalPins; + _digitalOutBuffer[2] = _numAnaloguePins; + // Send config, if EXIORDY returned, we're good, otherwise go offline + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); + if (_digitalInBuffer[0] != EXIORDY) { + DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + return; + } + // Attempt to get version, if we don't get it, we don't care, don't go offline + // Using digital in buffer in reverse to save RAM + _digitalInBuffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1); + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), + _i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); +#ifdef DIAG_IO + _display(); +#endif + } else { + DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + } + } + + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { + if (configType != CONFIGURE_INPUT) return false; + if (paramCount != 1) return false; + if (vpin >= _firstVpin + _numDigitalPins) { + DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); + return false; + } + bool pullup = params[0]; + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } + + // We only use this to detect incorrect use of analogue pins + int _configureAnalogIn(VPIN vpin) override { + if (vpin < _firstVpin + _numDigitalPins) { + DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); + } + return false; + } + + int _readAnalogue(VPIN vpin) override { + if (vpin < _firstVpin + _numDigitalPins) return false; + int pin = vpin - _firstVpin; + _analogueOutBuffer[0] = EXIORDAN; + _analogueOutBuffer[1] = pin; + I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); + return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; + } + + int _read(VPIN vpin) override { + if (vpin >= _firstVpin + _numDigitalPins) return false; + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIORDD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); + return _digitalInBuffer[0]; + } + + void _write(VPIN vpin, int value) override { + if (vpin >= _firstVpin + _numDigitalPins) return; + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + } + + void _display() override { + int _firstAnalogue, _lastAnalogue; + if (_numAnaloguePins == 0) { + _firstAnalogue = 0; + _lastAnalogue = 0; + } else { + _firstAnalogue = _firstVpin + _numDigitalPins; + _lastAnalogue = _firstVpin + _nPins - 1; + } + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + _i2cAddress, _majorVer, _minorVer, _patchVer, + _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, + _numAnaloguePins, _firstAnalogue, _lastAnalogue, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + uint8_t _i2cAddress; + uint8_t _numDigitalPins; + uint8_t _numAnaloguePins; + int _digitalPinBytes; + int _analoguePinBytes; + byte _analogueInBuffer[2]; + byte _analogueOutBuffer[2]; + byte _digitalOutBuffer[3]; + byte _digitalInBuffer[1]; + uint8_t _versionBuffer[3]; + uint8_t _majorVer = 0; + uint8_t _minorVer = 0; + uint8_t _patchVer = 0; + + enum { + EXIOINIT = 0xE0, // Flag to initialise setup procedure + EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup + EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration + EXIOVER = 0xE3, // Flag to get version + EXIORDAN = 0xE4, // Flag to read an analogue input + EXIOWRD = 0xE5, // Flag for digital write + EXIORDD = 0xE6, // Flag to read digital input + }; +}; + +#endif \ No newline at end of file diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index ea3dcb0..2dc9e6b 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) { addDevice(this); } -// Initialisation of TurntableEX +// Initialisation of EXTurntable void EXTurntable::_begin() { I2CManager.begin(); I2CManager.setClock(1000000); @@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ uint8_t stepsMSB = value >> 8; uint8_t stepsLSB = value & 0xFF; #ifdef DIAG_IO - DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), + DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), _I2CAddress, stepsMSB, stepsLSB, activity); @@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h new file mode 100644 index 0000000..b4d538c --- /dev/null +++ b/IO_RotaryEncoder.h @@ -0,0 +1,127 @@ +/* + * © 2022, Peter Cole. All rights reserved. + * + * This file is part of EX-CommandStation + * + * 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 . +*/ + +/* +* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C. +* +* There is separate code required for the Arduino the rotary encoder is connected to, which is located here: +* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder +* +* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions +* can be tested in EX-RAIL with: +* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position +* IFRE(vpin, position) - test to see if specified rotary encoder position has been received +* +* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. +* +* Refer to the documentation for further information including the valid activities and examples. +*/ + +#ifndef IO_ROTARYENCODER_H +#define IO_ROTARYENCODER_H + +#include "EXRAIL2.h" +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +class RotaryEncoder : public IODevice { +public: + // Constructor + RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){ + _firstVpin = firstVpin; + _nPins = nPins; + _I2CAddress = I2CAddress; + addDevice(this); + } + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress); + } + +private: + // Initiate the device + void _begin() { + I2CManager.begin(); + if (I2CManager.exists(_I2CAddress)) { + byte _getVersion[1] = {RE_VER}; + I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + _buffer[0] = RE_OP; + I2CManager.write(_I2CAddress, _buffer, 1); +#ifdef DIAG_IO + _display(); +#endif + } else { + _deviceState = DEVSTATE_FAILED; + } + } + + void _loop(unsigned long currentMicros) override { + I2CManager.read(_I2CAddress, _buffer, 1); + _position = _buffer[0]; + // This here needs to have a change check, ie. position is a different value. + #if defined(EXRAIL_ACTIVE) + if (_position != _previousPosition) { + _previousPosition = _position; + RMFT2::changeEvent(_firstVpin,1); + } else { + RMFT2::changeEvent(_firstVpin,0); + } + #endif + delayUntil(currentMicros + 100000); + } + + // Device specific read function + int _readAnalogue(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + return _position; + } + + void _write(VPIN vpin, int value) override { + if (vpin == _firstVpin + 1) { + byte _feedbackBuffer[2] = {RE_OP, value}; + I2CManager.write(_I2CAddress, _feedbackBuffer, 2); + } + } + + void _display() override { + DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer, + (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } + + uint8_t _I2CAddress; + int8_t _position; + int8_t _previousPosition = 0; + uint8_t _versionBuffer[3]; + uint8_t _buffer[1]; + uint8_t _majorVer = 0; + uint8_t _minorVer = 0; + uint8_t _patchVer = 0; + + enum { + RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device + RE_OP = 0xA1, // Flag for normal operation + }; + +}; + +#endif diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h new file mode 100644 index 0000000..73de1a1 --- /dev/null +++ b/IO_duinoNodes.h @@ -0,0 +1,172 @@ +/* + * © 2022, Chris Harlow. All rights reserved. + * Based on original by: Robin Simonds, Beagle Bay Inc + * + * This file is part of DCC-EX 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 . + */ +#ifndef IO_duinoNodes_h + #define IO_duinoNodes_h +#include +#include "defines.h" +#include "IODevice.h" + +#define DN_PIN_MASK(bit) (0x80>>(bit%8)) +#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) ) +#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x)) +#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x)) + + + +class IO_duinoNodes : public IODevice { + +public: + IO_duinoNodes(VPIN firstVpin, int nPins, + byte clockPin, byte latchPin, byte dataPin, + const byte* pinmap) : + IODevice(firstVpin, nPins) { + + _latchPin=latchPin; + _clockPin=clockPin; + _dataPin=dataPin; + _pinMap=pinmap; + _nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits + _pinValues=(byte*) calloc(_nShiftBytes,1); + // Connect to HAL so my _write, _read and _loop will be called as required. + IODevice::addDevice(this); + } + +// Called by HAL to start handling this device + void _begin() override { + _deviceState = DEVSTATE_NORMAL; + pinMode(_latchPin,OUTPUT); + pinMode(_clockPin,OUTPUT); + pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT); + _display(); + } + +// loop called by HAL supervisor +void _loop(unsigned long currentMicros) override { + if (_pinMap) _loopInput(currentMicros); + else if (_xmitPending) _loopOutput(); +} + +void _loopInput(unsigned long currentMicros) { + + if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do + _prevMicros=currentMicros; + + //set latch to HIGH to freeze & store parallel data + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + delayMicroseconds(1); + //set latch to LOW to enable the data to be transmitted serially + ArduinoPins::fastWriteDigital(_latchPin, LOW); + + // stream in the bitmap using mapping order provided at constructor + for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) { + byte newByte=0; + for (int xmitBit=0;xmitBit<8; xmitBit++) { + ArduinoPins::fastWriteDigital(_clockPin, LOW); + delayMicroseconds(1); + bool data = ArduinoPins::fastReadDigital(_dataPin); + byte map=_pinMap[xmitBit]; + if (data) newByte |= map; + else newByte &= ~map; + ArduinoPins::fastWriteDigital(_clockPin, HIGH); + delayMicroseconds(1); + } + _pinValues[xmitByte]=newByte; + // DIAG(F("DIN %x=%x"),xmitByte, newByte); + } + } + +void _loopOutput() { + // stream out the bitmap (highest pin first) + _xmitPending=false; + ArduinoPins::fastWriteDigital(_latchPin, LOW); + for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) { + ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit)); + ArduinoPins::fastWriteDigital(_clockPin,HIGH); + ArduinoPins::fastWriteDigital(_clockPin,LOW); + } + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + } + + int _read(VPIN vpin) override { + int pin=vpin - _firstVpin; + bool b=DN_GET_BIT(pin); + return b?1:0; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + bool oldval=DN_GET_BIT(pin); + bool newval=value!=0; + if (newval==oldval) return; // no change + if (newval) DN_SET_BIT(pin); + else DN_CLR_BIT(pin); + _xmitPending=true; // shift register will be sent on next _loop() + } + + void _display() override { + DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + _pinMap?F("IN"):F("OUT"), + (int)_firstVpin, + (int)_firstVpin+_nPins-1, _nShiftBytes*8); + } + +private: + static const unsigned long POLL_MICROS=100000; // 10 / S + unsigned long _prevMicros; + int _nShiftBytes=0; + VPIN _latchPin,_clockPin,_dataPin; + byte* _pinValues; + bool _xmitPending; // Only relevant in output mode + const byte* _pinMap; // NULL in output mode +}; + +class IO_DNIN8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0,7,6,5,1,2,3,4 + static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } + +}; + +class IO_DNIN8K { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7 + static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } +}; + +class IO_DNOU8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL); + } + +}; +#endif \ No newline at end of file diff --git a/MotorDrivers.h b/MotorDrivers.h index 328d5e6..c066052 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -182,7 +182,7 @@ #define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) + new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN) // #endif diff --git a/Release_Notes/duinoNodes.md b/Release_Notes/duinoNodes.md new file mode 100644 index 0000000..64a2323 --- /dev/null +++ b/Release_Notes/duinoNodes.md @@ -0,0 +1,39 @@ +Using Lew's Duino Gear boards: + +1. DNIN8 Input + This is a shift-register implementation of a digital input collector. + Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software + configuratuion correctly represents the number of boards connected otherwise the results will be meaningless. + + Use in myAnimation.h + + HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 400, 16, 40, 42, 44) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + + + + This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence. + Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second. + + Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes. + + This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable. + + The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards. + + + DNOU8 works the same way, + Use in myAnimation.h + + HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 450, 16, 45, 47, 49) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + +This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins. diff --git a/RingStream.cpp b/RingStream.cpp index 7f10728..9377a0a 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -65,6 +65,13 @@ int RingStream::availableForWrite() { } size_t RingStream::printFlash(const FSH * flashBuffer) { + // This function does not work on a 32 bit processor where the runtime + // sometimes misrepresents the pointer size in uintptr_t. + // In any case its not really necessary in a 32 bit processor because + // we have adequate ram. + if (sizeof(void*)>2) return print(flashBuffer); + + // We are about to add a PROGMEM string to the buffer. // To save RAM we can insert a marker and the // progmem address into the buffer instead. @@ -107,8 +114,11 @@ int RingStream::read() { if ((_pos_read==_pos_write) && !_overflow) return -1; // empty byte b=readRawByte(); if (b!=FLASH_INSERT_MARKER) return b; -#ifndef ARDUINO_ARCH_ESP32 // Detected a flash insert + if (sizeof(void*)>2) { + DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read); + return '?'; + } // read address bytes LSB first (size depends on CPU) uintptr_t iFlash=0; for (byte f=0; f( iFlash); // and try again... so will read the first byte of the insert. return read(); -#else - DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read); - return '\0'; -#endif } byte RingStream::readRawByte() { @@ -189,12 +195,6 @@ bool RingStream::commit() { _mark++; if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); - // Enable this for debugging only, it requires A LOT of RAM - //{ char s[_count+2]; - // strncpy(s, (const char*)&(_buffer[_mark+1]), _count); - // s[_count]=0; - // DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); - //} _ringClient = NO_CLIENT; return true; // commit worked } diff --git a/RingStream.h b/RingStream.h index 4f51a65..b477b77 100644 --- a/RingStream.h +++ b/RingStream.h @@ -27,7 +27,7 @@ class RingStream : public Print { public: RingStream( const uint16_t len); - static const int THIS_IS_A_RINGSTREAM=77; + static const int THIS_IS_A_RINGSTREAM=777; virtual size_t write(uint8_t b); // This availableForWrite function is subverted from its original intention so that a caller diff --git a/StringBuffer.h b/StringBuffer.h index fe227fb..ac42964 100644 --- a/StringBuffer.h +++ b/StringBuffer.h @@ -32,7 +32,7 @@ class StringBuffer : public Print { private: static const int buffer_max=64; // enough for long text msgs to throttles int16_t _pos_write; - char _buffer[buffer_max+1]; + char _buffer[buffer_max+2]; }; #endif \ No newline at end of file diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 87c22e5..cc78714 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { { const FSH* flash= (const FSH*)va_arg(args, char*); -#ifndef ARDUINO_ARCH_ESP32 - // On ESP32 the reading flashstring from rinstream code - // crashes, so don't use the flashstream hack on ESP32 #if WIFI_ON | ETHERNET_ON // RingStream has special logic to handle flash strings // but is not implemented unless wifi or ethernet are enabled. @@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) ((RingStream *)stream)->printFlash(flash); else -#endif #endif stream->print(flash); break; } + case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break; case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; @@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) { case '\r': stream->print(F("\\r")); break; case '\0': stream->print(F("\\0")); return; case '\t': stream->print(F("\\t")); break; - case '\\': stream->print(F("\\")); break; - default: stream->print(c); + case '\\': stream->print(F("\\\\")); break; + default: stream->write(c); } } diff --git a/WiThrottle.cpp b/WiThrottle.cpp index cfc4a74..920ebd6 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -63,69 +63,6 @@ WiThrottle * WiThrottle::firstThrottle=NULL; -static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) { - if (n == 0) - return 0; - do { - if (*s1 != *s2++) - return 1; - if (*s1++ == 0) - break; - } while (--n != 0); - return 0; -} - -void WiThrottle::findUniqThrottle(int id, char *u) { - WiThrottle *wtmyid = NULL; - WiThrottle *wtmyuniq = NULL; - - // search 1, look for clientid match - for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - if (wt->clientid == id) { - if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case - return; - wtmyid = wt; - break; - } - } - // search 2, look for string match - for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - if (xstrncmp(u, wt->uniq, 16) == 0) { - wtmyuniq = wt; - break; - } - } - - // analyse result of the two for loops: - if (wtmyid == NULL) { // should not happen - DIAG(F("Did not find my own wiThrottle handle")); - return; - } - // wtmyuniq == wtmyid has already returned in for loop 1 - if (wtmyuniq == NULL) { // register uniq in the found id - strncpy(wtmyid->uniq, u, 16); - wtmyid->uniq[16] = '\0'; - if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq); - return; - } - // if we get here wtmyid and wtmyuniq point on objects but differnet ones - // so we need to do the copy (all other options covered above) - for(int n=0; n < MAX_MY_LOCO; n++) - wtmyid->myLocos[n] = wtmyuniq->myLocos[n]; - wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable; - wtmyid->heartBeat = wtmyuniq->heartBeat; - wtmyid->initSent = wtmyuniq->initSent; - wtmyid->exRailSent = wtmyuniq->exRailSent; - wtmyid->mostRecentCab = wtmyuniq->mostRecentCab; - wtmyid->turnoutListHash = wtmyuniq->turnoutListHash; - wtmyid->lastPowerState = wtmyuniq->lastPowerState; - strncpy(wtmyid->uniq, u, 16); - wtmyid->uniq[16] = '\0'; - if (Diag::WITHROTTLE) - DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq); - forget(wtmyuniq->clientid); // do not use wtmyid after this -} - WiThrottle* WiThrottle::getThrottle( int wifiClient) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->clientid==wifiClient) return wt; @@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) { void WiThrottle::forget( byte clientId) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->clientid==clientId) { + DIAG(F("Withrottle client %d dropped"),clientId); delete wt; break; } @@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) { nextThrottle=firstThrottle; firstThrottle= this; clientid=wificlientid; - initSent=false; // prevent sending heartbeats before connection completed heartBeatEnable=false; // until client turns it on - turnoutListHash = -1; // make sure turnout list is sent once - exRailSent=false; mostRecentCab=0; for (int loco=0;loconext()){ - if (tt->isHidden()) continue; - int id=tt->getId(); - const FSH * tdesc=NULL; - #ifdef EXRAIL_ACTIVE - tdesc=RMFT2::getTurnoutDescription(id); - #endif - char tchar=Turnout::isClosed(id)?'2':'4'; - if (tdesc==NULL) // turnout with no description - StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar); - else - StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar); - } - StringFormatter::send(stream,F("\n")); - turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison - } - - else if (!exRailSent) { - // Send EX-RAIL routes list if not already sent (but not at same time as turnouts above) - exRailSent=true; -#ifdef EXRAIL_ACTIVE - StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL")); - for (byte pass=0;pass<2;pass++) { - // first pass automations, second pass routes. - for (int ix=0;;ix++) { - int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix); - if (id==0) break; - const FSH * desc=RMFT2::getRouteDescription(id); - StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"), - pass?'A':'R',id,desc, pass?'4':'2'); - } - } - StringFormatter::send(stream,F("\n")); -#endif - // allow heartbeat to slow down once all metadata sent - StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); } } @@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { } break; case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value + StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value break; case 'M': // multithrottle multithrottle(stream, cmd); break; case 'H': // send initial connection info after receiving "HU" message - if (cmd[1] == 'U') { - WiThrottle::findUniqThrottle(clientid, (char *)cmd+2); - StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); - StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); - StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); - StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON); -#ifdef EXRAIL_ACTIVE - StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); - for (int16_t r=0;rwrite('\n'); // end roster -#endif - - - // set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!) - StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2); - initSent = true; + if (cmd[1] == 'U') { + sendIntro(stream); } break; case 'Q': // @@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); } } - if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid); + if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid); delete this; break; } @@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ } //use first empty "slot" on this client's list, will be added to DCC registration list for (int loco=0;loco\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco - int fkeys=29; - myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle - -#ifdef EXRAIL_ACTIVE - const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); - if (!functionNames) { - // no roster, use presets as above - } - else if (GETFLASH(functionNames)=='\0') { - // "" = Roster but no functions given - fkeys=0; - } - else { - // we have function names... - // scan names list emitting names, counting functions and - // flagging non-toggling things like horn. - myLocos[loco].functionToggles =0; - StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid); - fkeys=0; - bool firstchar=true; - for (int fx=0;;fx++) { - char c=GETFLASH(functionNames+fx); - if (c=='\0') { - fkeys++; - break; - } - if (c=='/') { - fkeys++; - StringFormatter::send(stream,F("]\\[")); - firstchar=true; - } - else if (firstchar && c=='*') { - myLocos[loco].functionToggles |= 1UL<write(c); - } - } - StringFormatter::send(stream,F("\n")); - } - -#endif - - for(int fKey=0; fKey=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey); - } - //speed and direction will be published at next broadcast cycle - StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 - return; + if (myLocos[loco].throttle=='\0') { + myLocos[loco].throttle=throttleChar; + myLocos[loco].cab=locoid; + myLocos[loco].functionMap=DCC::getFunctionMap(locoid); + myLocos[loco].broadcastPending=true; // means speed/dir will be sent later + mostRecentCab=locoid; + StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco + sendFunctions(stream,loco); + //speed and direction will be published at next broadcast cycle + StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 + return; } } StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); @@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) { // for each WiThrottle, check the heartbeat and broadcast needed for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) wt->checkHeartbeat(stream); - - } void WiThrottle::checkHeartbeat(RingStream * stream) { @@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) { heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop. } } - //haba no, not necessary the only throttle and it may come back - //delete this; + // if it does come back, the throttle should re-acquire + delete this; return; } @@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) { DIAG(F("LocoCallback commit success")); stashStream->commit(); CommandDistributor::broadcastPower(); - } + +void WiThrottle::sendIntro(Print* stream) { + introSent=true; + StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); + StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); + StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); + StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON); + // set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!) + StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD); +} + +void WiThrottle::sendTurnouts(Print* stream) { + turnoutsSent=true; + StringFormatter::send(stream,F("PTL")); + for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){ + if (tt->isHidden()) continue; + int id=tt->getId(); + const FSH * tdesc=NULL; + #ifdef EXRAIL_ACTIVE + tdesc=RMFT2::getTurnoutDescription(id); + #endif + char tchar=Turnout::isClosed(id)?'2':'4'; + if (tdesc==NULL) // turnout with no description + StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar); + else + StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar); + } + StringFormatter::send(stream,F("\n")); +} +void WiThrottle::sendRoster(Print* stream) { + rosterSent=true; + #ifdef EXRAIL_ACTIVE + StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); + for (int16_t r=0;r]\\["), myLocos[loco].throttle,LorS(locoid),locoid); + fkeys=0; + bool firstchar=true; + for (int fx=0;;fx++) { + char c=GETFLASH(functionNames+fx); + if (c=='\0') { + fkeys++; + break; + } + if (c=='/') { + fkeys++; + StringFormatter::send(stream,F("]\\[")); + firstchar=true; + } + else if (firstchar && c=='*') { + myLocos[loco].functionToggles |= 1UL<write(c); + } + } + StringFormatter::send(stream,F("\n")); + } + +#endif + + for(int fKey=0; fKey=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey); + } +} \ No newline at end of file diff --git a/WiThrottle.h b/WiThrottle.h index 3fa973a..6756943 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -45,7 +45,8 @@ class WiThrottle { ~WiThrottle(); static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client - static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport + static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport + static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs static WiThrottle* firstThrottle; static int getInt(byte * cmd); @@ -61,10 +62,12 @@ class WiThrottle { MYLOCO myLocos[MAX_MY_LOCO]; bool heartBeatEnable; unsigned long heartBeat; - bool initSent; // valid connection established - bool exRailSent; // valid connection established + bool introSent=false; + bool turnoutsSent=false; + bool rosterSent=false; + bool routesSent=false; + bool heartrateSent=false; uint16_t mostRecentCab; - int turnoutListHash; // used to check for changes to turnout list bool lastPowerState; // last power state sent to this client int DCCToWiTSpeed(int DCCSpeed); @@ -74,6 +77,11 @@ class WiThrottle { void accessory(RingStream *, byte* cmd); void checkHeartbeat(RingStream * stream); void markForBroadcast2(int cab); + void sendIntro(Print * stream); + void sendTurnouts(Print * stream); + void sendRoster(Print * stream); + void sendRoutes(Print * stream); + void sendFunctions(Print* stream, byte loco); // callback stuff to support prog track acquire static RingStream * stashStream; static WiThrottle * stashInstance; diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 2a8ec28..b570527 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() { } - if (pendingCipsend) { + if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) { if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); pendingCipsend=false; @@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { if (ch=='S') { // SEND OK probably loopState=SKIPTOEND; + lastCIPSEND=0; // no need to wait next time break; } if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND pendingCipsend=(clientPendingCIPSEND>=0); + if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND loopState=SKIPTOEND; break; } diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index d3410cd..08f6789 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -68,7 +68,9 @@ class WifiInboundHandler { Stream * wifiStream; static const int INBOUND_RING = 512; - static const int OUTBOUND_RING = 2048; + static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192; + + static const int CIPSENDgap=100; // millis() between retries of cipsend. RingStream * inboundRing; RingStream * outboundRing; @@ -79,5 +81,7 @@ class WifiInboundHandler { int clientPendingCIPSEND=-1; int currentReplySize; bool pendingCipsend; + uint32_t lastCIPSEND=0; // millis() of previous cipsend + }; #endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 4d93eda..bdc8dad 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) { while (wifiStream->available()) stream->write(wifiStream->read()); if (stream->available()) { int cx=stream->read(); - // A newline followed by !!! is an exit + // A newline followed by ! is an exit if (cx=='\n' || cx=='\r') startOfLine=true; else if (startOfLine && cx=='!') break; else startOfLine=false; - stream->write(cx); wifiStream->write(cx); } } @@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, char *locator = (char *)waitfor; DIAG(F("Wifi Check: [%E]"), waitfor); while ( millis() - startTime < timeout) { - while (wifiStream->available()) { - int ch = wifiStream->read(); + int nextchar; + while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) { + char ch = (char)nextchar; if (echo) { if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE - else USB_SERIAL.print((char)ch); + else USB_SERIAL.print(ch); } if (ch != GETFLASH(locator)) locator = (char *)waitfor; if (ch == GETFLASH(locator)) { diff --git a/defines.h b/defines.h index c4d38a3..9ad5851 100644 --- a/defines.h +++ b/defines.h @@ -149,6 +149,12 @@ #define CPU_TYPE_ERROR #endif +// replace board type if provided by compiler +#ifdef BOARD_NAME + #undef ARDUINO_TYPE + #define ARDUINO_TYPE BOARD_NAME +#endif + //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 32aa12e..5470f76 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -20,6 +20,7 @@ #include "IO_HCSR04.h" // Ultrasonic range sensor #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player +//#include "IO_EXTurntable.h" // Turntable-EX turntable controller //========================================================================== @@ -160,6 +161,52 @@ void halSetup() { // DFPlayer::create(10000, 10, Serial1); + //======================================================================= + // The following directive defines an EX-Turntable turntable instance. + //======================================================================= + // EXTurntable::create(VPIN, Number of VPINs, I2C Address) + // + // The parameters are: + // VPIN=600 + // Number of VPINs=1 (Note there is no reason to change this) + // I2C address=0x60 + // + // Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default. + + //EXTurntable::create(600, 1, 0x60); + + + //======================================================================= + // The following directive defines an EX-IOExpander instance. + //======================================================================= + // EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count) + // + // The parameters are: + // VPIN=an available Vpin + // Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation) + // I2C address=an available I2C address (default 0x65) + // + // Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default. + // The first example is for an Arduino Nano with the default pin allocations. + // The second example is for an Arduino Uno using all pins as digital only. + + //EXIOExpander::create(800, 18, 0x65, 12, 6); + //EXIOExpander::create(820, 16, 0x66, 16, 0); + + + //======================================================================= + // The following directive defines a rotary encoder instance. + //======================================================================= + // The parameters are: + // firstVpin = First available Vpin to allocate + // numPins= Number of Vpins to allocate, can be either 1 or 2 + // i2cAddress = Available I2C address (default 0x70) + + //RotaryEncoder::create(firstVpin, numPins, i2cAddress); + //RotaryEncoder::create(700, 1, 0x70); + //RotaryEncoder::create(701, 2, 0x71); + + } #endif diff --git a/platformio.ini b/platformio.ini index bc1ef0e..82167f2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ default_envs = samd21-zero-usb ESP32 Nucleo-F411RE + Nucleo-F446RE Teensy3.2 Teensy3.5 Teensy3.6 @@ -50,19 +51,6 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 -; Firebox disabled for now -; [env:samc21-firebox] -; platform = atmelsam -; board = firebox -; framework = arduino -; upload_protocol = atmel-ice -; lib_deps = -; ${env.lib_deps} -; SparkFun External EEPROM Arduino Library -;monitor_speed = 115200 -;monitor_echo = yes -;build_flags = -std=c++17 - [env:mega2560-debug] platform = atmelavr board = megaatmega2560 @@ -109,9 +97,6 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -; Example, but v12 does generate bigger binaries -; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux -; Should make binaries smaller build_flags = -mcall-prologues [env:mega328] @@ -160,7 +145,6 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -; Should make binaries smaller build_flags = -mcall-prologues [env:nano] @@ -184,7 +168,16 @@ platform = ststm32 board = nucleo_f411re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 -Wunused-variable +monitor_speed = 115200 +monitor_echo = yes + +[env:Nucleo-F446RE] +platform = ststm32 +board = nucleo_f446re +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 -Os -g2 -Wunused-variable monitor_speed = 115200 monitor_echo = yes @@ -192,7 +185,7 @@ monitor_echo = yes platform = teensy board = teensy31 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -200,7 +193,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy35 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -208,7 +201,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy36 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -216,7 +209,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy40 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -224,6 +217,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy41 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} -lib_ignore = \ No newline at end of file +lib_ignore = + diff --git a/version.h b/version.h index 0244840..a17babd 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,13 @@ #include "StringFormatter.h" -#define VERSION "4.2.6" +#define VERSION "4.2.9pre1" +// 4.2.9 duinoNodes support +// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) +// Withrottle connect/disconnect improvements +// Report BOARD_TYPE if provided by compiler +// 4.2.7 FIX: Static IP addr +// FIX: Reuse WiThrottle list entries // 4.2.6 FIX: Remove RAM thief // FIX: ADC port 8-15 fix // 4.2.5 Make GETFLASHW code more universal