diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e72e057..f44f9dc 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -91,6 +91,7 @@ LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; +LookList * RMFT2::onChangeLookup=NULL; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -173,6 +174,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onRedLookup=LookListLoader(OPCODE_ONRED); onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); + onChangeLookup=LookListLoader(OPCODE_ONCHANGE); // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -745,6 +747,10 @@ void RMFT2::loop2() { 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; @@ -968,6 +974,7 @@ void RMFT2::loop2() { case OPCODE_ONRED: case OPCODE_ONAMBER: case OPCODE_ONGREEN: + case OPCODE_ONCHANGE: break; @@ -1094,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); diff --git a/EXRAIL2.h b/EXRAIL2.h index 6e6d0ca..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,7 +65,8 @@ 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 { @@ -113,6 +115,7 @@ 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; @@ -169,6 +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 63fc6bd..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 @@ -185,6 +187,7 @@ #define IFTHROWN(turnout_id) #define IFRESERVE(block) #define IFTIMEOUT +#define IFRE(sensor_id,value) #define INVERT_DIRECTION #define JOIN #define KILLALL @@ -201,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) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 69ffed2..b5e78d9 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -285,6 +285,7 @@ const HIGHFLASH 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, @@ -301,6 +302,7 @@ const HIGHFLASH 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), 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/myHal.cpp_example.txt b/myHal.cpp_example.txt index c95aaed..9752a8b 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -194,6 +194,19 @@ void halSetup() { //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