1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-25 13:41:23 +01:00

Merge pull request #185 from DCC-EX/EX-RAIL-neil-RCN213

Ex rail neil rcn213
This commit is contained in:
Neil McKechnie 2021-08-25 00:42:16 +01:00 committed by GitHub
commit bad9e866f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 205 additions and 45 deletions

View File

@ -700,22 +700,17 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
{ {
bool state = false; bool state = false;
switch (p[1]) { switch (p[1]) {
// By default turnout command uses 0=throw, 1=close, // Turnout messages use 1=throw, 0=close.
// but legacy DCC++ behaviour is 1=throw, 0=close.
case 0: case 0:
state = Turnout::useLegacyTurnoutBehaviour;
break;
case 1:
state = !Turnout::useLegacyTurnoutBehaviour;
break;
case HASH_KEYWORD_C: case HASH_KEYWORD_C:
state = true; state = true;
break; break;
case 1:
case HASH_KEYWORD_T: case HASH_KEYWORD_T:
state= false; state= false;
break; break;
default: default:
return false; return false; // Invalid parameter
} }
if (!Turnout::setClosed(p[0], state)) return false; if (!Turnout::setClosed(p[0], state)) return false;

View File

@ -164,8 +164,6 @@ void I2CManagerClass::loop() {
#if !defined(I2C_USE_INTERRUPTS) #if !defined(I2C_USE_INTERRUPTS)
handleInterrupt(); handleInterrupt();
#endif #endif
// If free, initiate next transaction
startTransaction();
checkForTimeout(); checkForTimeout();
} }

View File

@ -292,7 +292,7 @@ ArduinoPins::ArduinoPins(VPIN firstVpin, int nPins) {
_pinPullups = (uint8_t *)calloc(2, arrayLen); _pinPullups = (uint8_t *)calloc(2, arrayLen);
_pinModes = (&_pinPullups[0]) + arrayLen; _pinModes = (&_pinPullups[0]) + arrayLen;
for (int i=0; i<arrayLen; i++) { for (int i=0; i<arrayLen; i++) {
_pinPullups[i] = 0; _pinPullups[i] = 0xff; // default to pullup on, for inputs
_pinModes[i] = 0; _pinModes[i] = 0;
} }
} }

View File

@ -59,6 +59,8 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols,
backlight(); backlight();
lcdDisplay = this; lcdDisplay = this;
} }
// Initialise request block for comms.
requestBlock.setWriteParams(lcd_Addr, outputBuffer, sizeof(outputBuffer));
} }
void LiquidCrystal_I2C::begin() { void LiquidCrystal_I2C::begin() {
@ -190,13 +192,15 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
mode |= _backlightval; mode |= _backlightval;
uint8_t highnib = (((value >> 4) & 0x0f) << BACKPACK_DATA_BITS) | mode; uint8_t highnib = (((value >> 4) & 0x0f) << BACKPACK_DATA_BITS) | mode;
uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode; uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode;
// Wait for previous request to complete before writing to outputbuffer.
requestBlock.wait();
// Send both nibbles // Send both nibbles
uint8_t len = 0; uint8_t len = 0;
outputBuffer[len++] = highnib|En; outputBuffer[len++] = highnib|En;
outputBuffer[len++] = highnib; outputBuffer[len++] = highnib;
outputBuffer[len++] = lownib|En; outputBuffer[len++] = lownib|En;
outputBuffer[len++] = lownib; outputBuffer[len++] = lownib;
I2CManager.write(_Addr, outputBuffer, len, &requestBlock); I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously
} }
// write 4 data bits to the HD44780 LCD controller. // write 4 data bits to the HD44780 LCD controller.
@ -210,7 +214,7 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) {
uint8_t len = 0; uint8_t len = 0;
outputBuffer[len++] = _data|En; outputBuffer[len++] = _data|En;
outputBuffer[len++] = _data; outputBuffer[len++] = _data;
I2CManager.write(_Addr, outputBuffer, len, &requestBlock); I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously
} }
// write a byte to the PCF8574 I2C interface. We don't need to set // write a byte to the PCF8574 I2C interface. We don't need to set
@ -219,5 +223,5 @@ void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
// Wait for previous request to complete before writing to outputbuffer. // Wait for previous request to complete before writing to outputbuffer.
requestBlock.wait(); requestBlock.wait();
outputBuffer[0] = value | _backlightval; outputBuffer[0] = value | _backlightval;
I2CManager.write(_Addr, outputBuffer, 1, &requestBlock); I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously
} }

View File

@ -90,7 +90,8 @@ private:
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[4]; uint8_t outputBuffer[4];
bool isBusy() { return requestBlock.isBusy(); } // I/O is synchronous, so if this is called we're not busy!
bool isBusy() { return false; }
}; };
#endif #endif

View File

@ -20,13 +20,8 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
// Set the following definition to true for <T id 0> = throw and <T id 1> = close
// or to false for <T id 0> = close and <T id 1> = throw (the original way).
#ifndef USE_LEGACY_TURNOUT_BEHAVIOUR
#define USE_LEGACY_TURNOUT_BEHAVIOUR false
#endif
#include "defines.h" #include "defines.h" // includes config.h
#include "EEStore.h" #include "EEStore.h"
#include "StringFormatter.h" #include "StringFormatter.h"
#include "RMFT2.h" #include "RMFT2.h"
@ -47,7 +42,6 @@
* Public static data * Public static data
*/ */
int Turnout::turnoutlistHash = 0; int Turnout::turnoutlistHash = 0;
bool Turnout::useLegacyTurnoutBehaviour = USE_LEGACY_TURNOUT_BEHAVIOUR;
/* /*
* Protected static functions * Protected static functions
@ -74,9 +68,10 @@
turnoutlistHash++; turnoutlistHash++;
} }
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
void Turnout::printState(Print *stream) { void Turnout::printState(Print *stream) {
StringFormatter::send(stream, F("<H %d %d>\n"), StringFormatter::send(stream, F("<H %d %d>\n"),
_turnoutData.id, _turnoutData.closed ^ useLegacyTurnoutBehaviour); _turnoutData.id, !_turnoutData.closed);
} }
// Remove nominated turnout from turnout linked list and delete the object. // Remove nominated turnout from turnout linked list and delete the object.
@ -137,7 +132,7 @@
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress // Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts. // is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0) if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
EEPROM.put(tt->_eepromAddress, *((uint8_t *) &tt->_turnoutData)); EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#if defined(RMFT_ACTIVE) #if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag); RMFT2::turnoutEvent(id, closeFlag);
@ -172,7 +167,7 @@
Turnout *tt = 0; Turnout *tt = 0;
// Read turnout type from EEPROM // Read turnout type from EEPROM
struct TurnoutData turnoutData; struct TurnoutData turnoutData;
int eepromAddress = EEStore::pointer(); // Address of byte containing the closed flag. int eepromAddress = EEStore::pointer() + offsetof(struct TurnoutData, flags); // Address of byte containing the closed flag.
EEPROM.get(EEStore::pointer(), turnoutData); EEPROM.get(EEStore::pointer(), turnoutData);
EEStore::advance(sizeof(turnoutData)); EEStore::advance(sizeof(turnoutData));
@ -196,7 +191,7 @@
} }
if (tt) { if (tt) {
// Save EEPROM address in object. Note that LCN turnouts always have eepromAddress of zero. // Save EEPROM address in object. Note that LCN turnouts always have eepromAddress of zero.
tt->_eepromAddress = eepromAddress; tt->_eepromAddress = eepromAddress + offsetof(struct TurnoutData, flags);
} }
#ifdef EESTOREDEBUG #ifdef EESTOREDEBUG
@ -205,7 +200,7 @@
return tt; return tt;
} }
// Display, on the specified stream, the current state of the turnout (1 or 0). // Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
void Turnout::printState(uint16_t id, Print *stream) { void Turnout::printState(uint16_t id, Print *stream) {
Turnout *tt = get(id); Turnout *tt = get(id);
if (!tt) tt->printState(stream); if (!tt) tt->printState(stream);
@ -279,10 +274,11 @@
return tt; return tt;
} }
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
void ServoTurnout::print(Print *stream) { void ServoTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d SERVO %d %d %d %d %d>\n"), _turnoutData.id, _servoTurnoutData.vpin, StringFormatter::send(stream, F("<H %d SERVO %d %d %d %d %d>\n"), _turnoutData.id, _servoTurnoutData.vpin,
_servoTurnoutData.thrownPosition, _servoTurnoutData.closedPosition, _servoTurnoutData.profile, _servoTurnoutData.thrownPosition, _servoTurnoutData.closedPosition, _servoTurnoutData.profile,
_turnoutData.closed ^ useLegacyTurnoutBehaviour); !_turnoutData.closed);
} }
// ServoTurnout-specific code for throwing or closing a servo turnout. // ServoTurnout-specific code for throwing or closing a servo turnout.
@ -312,6 +308,12 @@
* *
*************************************************************************************/ *************************************************************************************/
#if defined(DCC_TURNOUTS_RCN_213)
const bool DCCTurnout::rcn213Compliant = true;
#else
const bool DCCTurnout::rcn213Compliant = false;
#endif
// DCCTurnoutData contains data specific to this subclass that is // DCCTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved. // written to EEPROM when the turnout is saved.
struct DCCTurnoutData { struct DCCTurnoutData {
@ -363,19 +365,19 @@
void DCCTurnout::print(Print *stream) { void DCCTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), _turnoutData.id, StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), _turnoutData.id,
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3), (((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3),
_turnoutData.closed ^ useLegacyTurnoutBehaviour); !_turnoutData.closed);
// Also report using classic DCC++ syntax for DCC accessory turnouts // Also report using classic DCC++ syntax for DCC accessory turnouts, since JMRI expects this.
StringFormatter::send(stream, F("<H %d %d %d %d>\n"), _turnoutData.id, StringFormatter::send(stream, F("<H %d %d %d %d>\n"), _turnoutData.id,
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3), (((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3),
_turnoutData.closed ^ useLegacyTurnoutBehaviour); !_turnoutData.closed);
} }
bool DCCTurnout::setClosedInternal(bool close) { bool DCCTurnout::setClosedInternal(bool close) {
// DCC++ Classic behaviour is that Throw writes a 1 in the packet, // DCC++ Classic behaviour is that Throw writes a 1 in the packet,
// and Close writes a 0. // and Close writes a 0.
// RCN-214 specifies that Throw is 0 and Close is 1. // RCN-213 specifies that Throw is 0 and Close is 1.
DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1), DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1),
((_dccTurnoutData.address-1) & 3), close ^ useLegacyTurnoutBehaviour); ((_dccTurnoutData.address-1) & 3), close ^ !rcn213Compliant);
_turnoutData.closed = close; _turnoutData.closed = close;
return true; return true;
} }
@ -437,9 +439,10 @@
return tt; return tt;
} }
// Report 1 for thrown, 0 for closed.
void VpinTurnout::print(Print *stream) { void VpinTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), _turnoutData.id, _vpinTurnoutData.vpin, StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), _turnoutData.id, _vpinTurnoutData.vpin,
_turnoutData.closed ^ useLegacyTurnoutBehaviour); !_turnoutData.closed);
} }
bool VpinTurnout::setClosedInternal(bool close) { bool VpinTurnout::setClosedInternal(bool close) {
@ -501,8 +504,9 @@
//void save() override { } //void save() override { }
//static Turnout *load(struct TurnoutData *turnoutData) { //static Turnout *load(struct TurnoutData *turnoutData) {
// Report 1 for thrown, 0 for closed.
void LCNTurnout::print(Print *stream) { void LCNTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id, StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id,
_turnoutData.closed ^ useLegacyTurnoutBehaviour); !_turnoutData.closed);
} }

View File

@ -53,9 +53,14 @@ protected:
// vice versa. If the turnout has been saved, then this byte is rewritten // vice versa. If the turnout has been saved, then this byte is rewritten
// when changed in RAM. The 'closed' flag must be located in the first byte. // when changed in RAM. The 'closed' flag must be located in the first byte.
struct TurnoutData { struct TurnoutData {
union {
struct {
bool closed : 1; bool closed : 1;
bool _rfu: 2; bool _rfu: 2;
uint8_t turnoutType : 5; uint8_t turnoutType : 5;
};
uint8_t flags;
};
uint16_t id; uint16_t id;
} _turnoutData; // 3 bytes } _turnoutData; // 3 bytes
@ -104,7 +109,7 @@ public:
* Static data * Static data
*/ */
static int turnoutlistHash; static int turnoutlistHash;
static bool useLegacyTurnoutBehaviour; static const bool useClassicTurnoutCommands;
/* /*
* Public base class functions * Public base class functions
@ -182,11 +187,11 @@ private:
} _servoTurnoutData; // 6 bytes } _servoTurnoutData; // 6 bytes
// Constructor // Constructor
ServoTurnout(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed = true); ServoTurnout(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed);
public: public:
// Create function // Create function
static Turnout *create(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed = true); static Turnout *create(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed=true);
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point. // Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData); static Turnout *load(struct TurnoutData *turnoutData);
@ -222,6 +227,8 @@ public:
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point. // Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData); static Turnout *load(struct TurnoutData *turnoutData);
void print(Print *stream) override; void print(Print *stream) override;
// Flag whether DCC Accessory packets are to contain 1=close/0=throw(RCN-213) or 1=throw/0-close (DCC++ Classic)
static const bool rcn213Compliant;
protected: protected:
bool setClosedInternal(bool close) override; bool setClosedInternal(bool close) override;
@ -243,7 +250,7 @@ private:
} _vpinTurnoutData; // 2 bytes } _vpinTurnoutData; // 2 bytes
// Constructor // Constructor
VpinTurnout(uint16_t id, VPIN vpin, bool closed=true); VpinTurnout(uint16_t id, VPIN vpin, bool closed);
public: public:
// Create function // Create function
@ -271,7 +278,7 @@ private:
// } _lcnTurnoutData; // 0 bytes // } _lcnTurnoutData; // 0 bytes
// Constructor // Constructor
LCNTurnout(uint16_t id, bool closed=true); LCNTurnout(uint16_t id, bool closed);
public: public:
// Create function // Create function

View File

@ -129,4 +129,16 @@ The configuration file for DCC-EX Command Station
#define SCROLLMODE 1 #define SCROLLMODE 1
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
//
// According to norm RCN-213 a DCC packet with a 1 is closed/straight
// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous
// versions of DCC++EX, a throw command was implemented in the packet as
// '1' and a close command as '0'. The #define below makes the states
// match with the norm. But we don't want to cause havoc on existent layouts,
// so we define this only for new installations. If you don't want this,
// don't add it to your config.h.
#define DCC_TURNOUTS_RCN_213
/////////////////////////////////////////////////////////////////////////////////////

139
mySetup.cpp_example.txt Normal file
View File

@ -0,0 +1,139 @@
// Sample mySetup.cpp file.
//
// To use this file, copy it to mySetup.cpp and uncomment the directives and/or
// edit them to satisfy your requirements.
// Note that if the file has a .cpp extension it WILL be compiled into the build
// and the mySetup() function WILL be invoked.
//
// To prevent this, temporarily rename it to mySetup.txt or similar.
//
#include "IODevice.h"
#include "Turnouts.h"
#include "Sensors.h"
#include "IO_HCSR04.h"
// The #if directive prevent compile errors for Uno and Nano by excluding the
// HAL directives from the build.
#if !defined(IO_NO_HAL)
// Examples of statically defined HAL directives (alternative to the create() call).
// These have to be outside of the mySetup() function.
// The following directive defines a PCA9685 PWM Servo driver module.
// The parameters are:
// First Vpin=100
// Number of VPINs=16 (numbered 100-115)
// I2C address of module=0x40
//PCA9685 pwmModule1(100, 16, 0x40);
// The following directive defines an MCP23017 16-port I2C GPIO Extender module.
// The parameters are:
// First Vpin=164
// Number of VPINs=16 (numbered 164-179)
// I2C address of module=0x20
//MCP23017 gpioModule2(164, 16, 0x20);
// Alternative form, which allows the INT pin of the module to request a scan
// by pulling Arduino pin 40 to ground. Means that the I2C isn't being polled
// all the time, only when a change takes place. Multiple modules' INT pins
// may be connected to the same Arduino pin.
//MCP23017 gpioModule2(164, 16, 0x20, 40);
// The following directive defines an MCP23008 8-port I2C GPIO Extender module.
// The parameters are:
// First Vpin=300
// Number of VPINs=8 (numbered 300-307)
// I2C address of module=0x22
//MCP23017 gpioModule3(300, 8, 0x22);
// The following directive defines a PCF8574 8-port I2C GPIO Extender module.
// The parameters are:
// First Vpin=200
// Number of VPINs=8 (numbered 200-207)
// I2C address of module=0x23
//PCF8574 gpioModule4(200, 8, 0x23);
// Alternative form using INT pin (see above)
//PCF8574 gpioModule4(200, 8, 0x23, 40);
// The following directive defines an HCSR04 ultrasonic module.
// The parameters are:
// Vpin=2000 (only one VPIN per directive)
// Number of VPINs=1
// Arduino pin connected to TRIG=30
// Arduino pin connected to ECHO=31
// Minimum trigger range=20cm (VPIN goes to 1 when <20cm)
// Maximum trigger range=25cm (VPIN goes to 0 when >25cm)
// Note: Multiple devices can be configured by using a different ECHO pin
// for each one. The TRIG pin can be shared between multiple devices.
// Be aware that the 'ping' of one device may be received by another
// device and position them accordingly!
//HCSR04 sonarModule1(2000, 30, 31, 20, 25);
//HCSR04 sonarModule2(2001, 30, 32, 20, 25);
// The function mySetup() is invoked from CS if it exists within the build.
// It is called just before mysetup.h is executed, so things set up within here can be
// referenced by commands in mySetup.h.
void mySetup() {
// Alternative way of creating MCP23017, which has to be within the mySetup() function
// The other devices can also be created in this way. The parameter lists for the
// create() function are identical to the parameter lists for the declarations.
//MCP23017::create(180, 16, 0x21);
// Creating a Turnout
// Parameters: same as <T> command for Servo turnouts
// ID and VPIN are 100, sonar moves between positions 102 and 490 with slow profile.
// Profile may be Instant, Fast, Medium, Slow or Bounce.
//ServoTurnout::create(100, 100, 490, 102, PCA9685::Slow);
// DCC Accessory turnout
// Parameters: same as <T> command for DCC Accessory turnouts
// ID=3000
// Decoder address=23
// Decoder subaddress = 1
//DCCTurnout::create(3000, 23, 1);
// Creating a Sensor
// Parameters: As for the <S> command,
// id = 164,
// Vpin = 164 (configured above as pin 0 of an MCP23017)
// Pullup enable = 1 (enabled)
//Sensor::create(164, 164, 1);
// Way of creating lots of identical sensors in a range
//for (int i=165; i<180; i++)
// Sensor::create(i, i, 1);
}
#endif