mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 23:56:13 +01:00
Merge branch 'master' into EX-RAIL-DC
This commit is contained in:
commit
a6eaa3660c
32
DCC.cpp
32
DCC.cpp
|
@ -155,7 +155,7 @@ uint8_t DCC::getThrottleSpeed(int cab) {
|
|||
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return false ;
|
||||
if (reg<0) return true;
|
||||
return (speedTable[reg].speedCode & 0x80) !=0;
|
||||
}
|
||||
|
||||
|
@ -371,8 +371,9 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
|
|||
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
BASELINE,
|
||||
BIV, // ackManagerByte initial value
|
||||
VB,WACK, // validate byte
|
||||
ITCB, // if ok callback value
|
||||
ITCB, // if ok callback value
|
||||
STARTMERGE, //clear bit and byte values ready for merge pass
|
||||
// each bit is validated against 0 and the result inverted in MERGE
|
||||
// this is because there tend to be more zeros in cv values than ones.
|
||||
|
@ -390,7 +391,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
|||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||
FAIL };
|
||||
|
||||
|
||||
|
@ -702,11 +703,13 @@ int DCC::nextLoco = 0;
|
|||
ackOp const * DCC::ackManagerProg;
|
||||
ackOp const * DCC::ackManagerProgStart;
|
||||
byte DCC::ackManagerByte;
|
||||
byte DCC::ackManagerByteVerify;
|
||||
byte DCC::ackManagerStash;
|
||||
int DCC::ackManagerWord;
|
||||
byte DCC::ackManagerRetry;
|
||||
byte DCC::ackRetry = 2;
|
||||
int16_t DCC::ackRetrySum;
|
||||
int16_t DCC::ackRetryPSum;
|
||||
int DCC::ackManagerCv;
|
||||
byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
|
@ -742,6 +745,7 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
|
|||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
@ -767,6 +771,7 @@ void DCC::ackManagerLoop() {
|
|||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
callbackState=READY;
|
||||
|
@ -840,7 +845,18 @@ void DCC::ackManagerLoop() {
|
|||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
|
@ -858,7 +874,11 @@ void DCC::ackManagerLoop() {
|
|||
case FAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
|
@ -927,7 +947,7 @@ void DCC::callback(int value) {
|
|||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("RETRY %d %d %d %d"), ackManagerCv, ackManagerRetry, ackRetry, ackRetrySum);
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
|
|
8
DCC.h
8
DCC.h
|
@ -38,9 +38,11 @@ enum ackOp : byte
|
|||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
|
@ -115,9 +117,11 @@ public:
|
|||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
};
|
||||
static inline void setAckRetry(byte retry) {
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -149,11 +153,13 @@ private:
|
|||
static ackOp const *ackManagerProg;
|
||||
static ackOp const *ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
|
|
|
@ -71,6 +71,8 @@ const int16_t HASH_KEYWORD_T=84;
|
|||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||
|
@ -736,15 +738,17 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
|||
if (!VpinTurnout::create(p[0], p[2])) return false;
|
||||
} else
|
||||
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
|
||||
if (params==4 && p[2]>0 && p[2]<=512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
||||
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
|
||||
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
||||
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
|
||||
} else if (params==3 && p[2]>0 && p[2]<=512*4) { // <T id DCC nn>, 1<=nn<=2048
|
||||
// Linearaddress 1 maps onto decoder address 1/0 (not 0/0!).
|
||||
if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false;
|
||||
} else
|
||||
return false;
|
||||
} else
|
||||
if (params==3) { // legacy <T id n n> for DCC accessory
|
||||
if (p[1]>0 && p[1]<=512 && p[2]>=0 && p[2]<4) {
|
||||
if (params==3) { // legacy <T id addr subadd> for DCC accessory
|
||||
if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) {
|
||||
if (!DCCTurnout::create(p[0], p[1], p[2])) return false;
|
||||
} else
|
||||
return false;
|
||||
|
@ -820,8 +824,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||
LCD(0, F("Ack Max=%dus"), p[2]); // <D ACK MAX 9000>
|
||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||
if (p[2] >255) p[2]=3;
|
||||
DCC::setAckRetry(p[2]);
|
||||
LCD(0, F("Ack Retry=%d"), p[2]); // <D ACK RETRY 2>
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
|
@ -878,9 +881,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||
return true;
|
||||
|
||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
||||
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
||||
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||
break;
|
||||
|
||||
#if !defined(IO_MINIMAL_HAL)
|
||||
case HASH_KEYWORD_HAL:
|
||||
if (p[1] == HASH_KEYWORD_SHOW)
|
||||
|
|
|
@ -116,6 +116,7 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
|
|||
powerMode = mode;
|
||||
bool ison = (mode == POWERMODE::ON);
|
||||
motorDriver->setPower( ison);
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
#define GITHUB_SHA "50fcbc0"
|
||||
#define GITHUB_SHA "37904b5"
|
||||
|
|
|
@ -179,7 +179,7 @@ I2CManagerClass I2CManager = I2CManagerClass();
|
|||
/***************************************************************************
|
||||
* Block waiting for request block to complete, and return completion status.
|
||||
* Since such a loop could potentially last for ever if the RB status doesn't
|
||||
* change, we set a high limit (0.1sec, 100ms) on the wait time and, if it
|
||||
* change, we set a high limit (1sec, 1000ms) on the wait time and, if it
|
||||
* hasn't changed by that time we assume it's not going to, and just return
|
||||
* a timeout status. This means that CS will not lock up.
|
||||
***************************************************************************/
|
||||
|
@ -187,8 +187,8 @@ uint8_t I2CRB::wait() {
|
|||
unsigned long waitStart = millis();
|
||||
do {
|
||||
I2CManager.loop();
|
||||
// Rather than looping indefinitely, let's set a very high timeout (100ms).
|
||||
if ((millis() - waitStart) > 100UL) {
|
||||
// Rather than looping indefinitely, let's set a very high timeout (1s).
|
||||
if ((millis() - waitStart) > 1000UL) {
|
||||
DIAG(F("I2C TIMEOUT I2C:x%x I2CRB:x%x"), i2cAddress, this);
|
||||
status = I2C_STATUS_TIMEOUT;
|
||||
// Note that, although the timeout is posted, the request may yet complete.
|
||||
|
|
|
@ -62,7 +62,18 @@
|
|||
* Set I2C clock speed register.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) {
|
||||
TWBR = ((F_CPU / i2cClockSpeed) - 16) / 2;
|
||||
unsigned long temp = ((F_CPU / i2cClockSpeed) - 16) / 2;
|
||||
for (uint8_t preScaler = 0; preScaler<=3; preScaler++) {
|
||||
if (temp <= 255) {
|
||||
TWBR = temp;
|
||||
TWSR = (TWSR & 0xfc) | preScaler;
|
||||
return;
|
||||
} else
|
||||
temp /= 4;
|
||||
}
|
||||
// Set slowest speed ~= 500 bits/sec
|
||||
TWBR = 255;
|
||||
TWSR |= 0x03;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
|
|
166
IODevice.cpp
166
IODevice.cpp
|
@ -67,34 +67,54 @@ void IODevice::begin() {
|
|||
// doesn't need to invoke it.
|
||||
void IODevice::loop() {
|
||||
unsigned long currentMicros = micros();
|
||||
// Call every device's loop function in turn, one per entry.
|
||||
if (!_nextLoopDevice) _nextLoopDevice = _firstDevice;
|
||||
if (_nextLoopDevice) {
|
||||
_nextLoopDevice->_loop(currentMicros);
|
||||
_nextLoopDevice = _nextLoopDevice->_nextDevice;
|
||||
}
|
||||
|
||||
IODevice *lastLoopDevice = _nextLoopDevice; // So we know when to stop...
|
||||
// Loop through devices until we find one ready to be serviced.
|
||||
do {
|
||||
if (!_nextLoopDevice) _nextLoopDevice = _firstDevice;
|
||||
if (_nextLoopDevice) {
|
||||
if (_nextLoopDevice->_deviceState != DEVSTATE_FAILED
|
||||
&& ((long)(currentMicros - _nextLoopDevice->_nextEntryTime)) >= 0) {
|
||||
// Found one ready to run, so invoke its _loop method.
|
||||
_nextLoopDevice->_nextEntryTime = currentMicros;
|
||||
_nextLoopDevice->_loop(currentMicros);
|
||||
_nextLoopDevice = _nextLoopDevice->_nextDevice;
|
||||
break;
|
||||
}
|
||||
// Not this one, move to next one
|
||||
_nextLoopDevice = _nextLoopDevice->_nextDevice;
|
||||
}
|
||||
} while (_nextLoopDevice != lastLoopDevice); // Stop looking when we've done all.
|
||||
|
||||
// Report loop time if diags enabled
|
||||
#if defined(DIAG_LOOPTIMES)
|
||||
static unsigned long lastMicros = 0;
|
||||
static unsigned long maxElapsed = 0;
|
||||
// Measure time since loop() method started.
|
||||
unsigned long halElapsed = micros() - currentMicros;
|
||||
// Measure time between loop() method entries.
|
||||
unsigned long elapsed = currentMicros - lastMicros;
|
||||
static unsigned long maxElapsed = 0, maxHalElapsed = 0;
|
||||
static unsigned long lastOutputTime = 0;
|
||||
static unsigned long halTotal = 0, total = 0;
|
||||
static unsigned long count = 0;
|
||||
const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec
|
||||
unsigned long elapsed = currentMicros - lastMicros;
|
||||
|
||||
// Ignore long loop counts while message is still outputting
|
||||
if (currentMicros - lastOutputTime > 3000UL) {
|
||||
if (elapsed > maxElapsed) maxElapsed = elapsed;
|
||||
if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed;
|
||||
halTotal += halElapsed;
|
||||
total += elapsed;
|
||||
count++;
|
||||
}
|
||||
count++;
|
||||
if (currentMicros - lastOutputTime > interval) {
|
||||
if (lastOutputTime > 0)
|
||||
LCD(1,F("Loop=%lus,%lus max"), interval/count, maxElapsed);
|
||||
maxElapsed = 0;
|
||||
count = 0;
|
||||
DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"),
|
||||
total/count, maxElapsed, halTotal/count, maxHalElapsed);
|
||||
maxElapsed = maxHalElapsed = total = halTotal = count = 0;
|
||||
lastOutputTime = currentMicros;
|
||||
}
|
||||
lastMicros = micros();
|
||||
lastMicros = currentMicros;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -114,12 +134,13 @@ bool IODevice::exists(VPIN vpin) {
|
|||
bool IODevice::hasCallback(VPIN vpin) {
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (!dev) return false;
|
||||
return dev->_hasCallback(vpin);
|
||||
return dev->_hasCallback;
|
||||
}
|
||||
|
||||
// Display (to diagnostics) details of the device.
|
||||
void IODevice::_display() {
|
||||
DIAG(F("Unknown device Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
|
||||
DIAG(F("Unknown device Vpins:%d-%d %S"),
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
// Find device associated with nominated Vpin and pass configuration values on to it.
|
||||
|
@ -139,30 +160,36 @@ void IODevice::write(VPIN vpin, int value) {
|
|||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write analogue value to virtual pin(s). If multiple devices are allocated the same pin
|
||||
// then only the first one found will be used. Duration is the time that the
|
||||
// operation is to be performed over (e.g. as an animation) in deciseconds (0-3276 sec)
|
||||
void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
// Write analogue value to virtual pin(s). If multiple devices are allocated
|
||||
// the same pin then only the first one found will be used.
|
||||
//
|
||||
// The significance of param1 and param2 may vary from device to device.
|
||||
// For servo controllers, param1 is the profile of the transition and param2
|
||||
// the duration, i.e. the time that the operation is to be animated over
|
||||
// in deciseconds (0-3276 sec)
|
||||
//
|
||||
void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) {
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (dev) {
|
||||
dev->_writeAnalogue(vpin, value, profile, duration);
|
||||
dev->_writeAnalogue(vpin, value, param1, param2);
|
||||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
|
||||
#endif
|
||||
}
|
||||
|
||||
// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing
|
||||
// the output over a period of time.
|
||||
// isBusy, when called for a device pin is always a digital output or analogue output,
|
||||
// returns input feedback state of the pin, i.e. whether the pin is busy performing
|
||||
// an animation or fade over a period of time.
|
||||
bool IODevice::isBusy(VPIN vpin) {
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (dev)
|
||||
return dev->_isBusy(vpin);
|
||||
return dev->_read(vpin);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
@ -194,10 +221,12 @@ void IODevice::addDevice(IODevice *newDevice) {
|
|||
newDevice->_begin();
|
||||
}
|
||||
|
||||
// Private helper function to locate a device by VPIN. Returns NULL if not found
|
||||
// Private helper function to locate a device by VPIN. Returns NULL if not found.
|
||||
// This is performance-critical, so minimises the calculation and function calls necessary.
|
||||
IODevice *IODevice::findDevice(VPIN vpin) {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
if (dev->owns(vpin))
|
||||
VPIN firstVpin = dev->_firstVpin;
|
||||
if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins)
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
|
@ -236,7 +265,19 @@ int IODevice::read(VPIN vpin) {
|
|||
return dev->_read(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read analogue value from virtual pin.
|
||||
int IODevice::readAnalogue(VPIN vpin) {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
if (dev->owns(vpin))
|
||||
return dev->_readAnalogue(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
@ -247,7 +288,17 @@ int IODevice::read(VPIN vpin) {
|
|||
// Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more.
|
||||
|
||||
void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
|
||||
bool IODevice::configure(VPIN, ConfigTypeEnum, int, int []) { return true; }
|
||||
bool IODevice::configure(VPIN pin, ConfigTypeEnum, int, int p[]) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
|
||||
#endif
|
||||
if (p[0]) {
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
} else {
|
||||
pinMode(pin, INPUT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void IODevice::write(VPIN vpin, int value) {
|
||||
digitalWrite(vpin, value);
|
||||
pinMode(vpin, OUTPUT);
|
||||
|
@ -256,9 +307,15 @@ void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {}
|
|||
bool IODevice::isBusy(VPIN) { return false; }
|
||||
bool IODevice::hasCallback(VPIN) { return false; }
|
||||
int IODevice::read(VPIN vpin) {
|
||||
pinMode(vpin, INPUT_PULLUP);
|
||||
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
|
||||
}
|
||||
int IODevice::readAnalogue(VPIN vpin) {
|
||||
pinMode(vpin, INPUT);
|
||||
noInterrupts();
|
||||
int value = analogRead(vpin);
|
||||
interrupts();
|
||||
return value;
|
||||
}
|
||||
void IODevice::loop() {}
|
||||
void IODevice::DumpAll() {
|
||||
DIAG(F("NO HAL CONFIGURED!"));
|
||||
|
@ -279,12 +336,14 @@ IONotifyCallback *IONotifyCallback::first = 0;
|
|||
ArduinoPins::ArduinoPins(VPIN firstVpin, int nPins) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
uint8_t arrayLen = (_nPins+7)/8;
|
||||
_pinPullups = (uint8_t *)calloc(2, arrayLen);
|
||||
int arrayLen = (_nPins+7)/8;
|
||||
_pinPullups = (uint8_t *)calloc(3, arrayLen);
|
||||
_pinModes = (&_pinPullups[0]) + arrayLen;
|
||||
_pinInUse = (&_pinPullups[0]) + 2*arrayLen;
|
||||
for (int i=0; i<arrayLen; i++) {
|
||||
_pinPullups[i] = 0xff; // default to pullup on, for inputs
|
||||
_pinModes[i] = 0;
|
||||
_pinInUse[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,6 +368,7 @@ bool ArduinoPins::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun
|
|||
_pinPullups[index] &= ~mask;
|
||||
pinMode(pin, INPUT);
|
||||
}
|
||||
_pinInUse[index] |= mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -327,11 +387,35 @@ void ArduinoPins::_write(VPIN vpin, int value) {
|
|||
_pinModes[index] |= mask;
|
||||
// Since mode changes should be infrequent, use standard pinMode function
|
||||
pinMode(pin, OUTPUT);
|
||||
_pinInUse[index] |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Device-specific read function.
|
||||
// Device-specific read function (digital input).
|
||||
int ArduinoPins::_read(VPIN vpin) {
|
||||
int pin = vpin;
|
||||
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
|
||||
uint8_t index = (pin-_firstVpin) / 8;
|
||||
if ((_pinModes[index] | ~_pinInUse[index]) & mask) {
|
||||
// Currently in write mode or not initialised, change to read mode
|
||||
_pinModes[index] &= ~mask;
|
||||
// Since mode changes should be infrequent, use standard pinMode function
|
||||
if (_pinPullups[index] & mask)
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
else
|
||||
pinMode(pin, INPUT);
|
||||
_pinInUse[index] |= mask;
|
||||
}
|
||||
int value = !fastReadDigital(pin); // Invert (5v=0, 0v=1)
|
||||
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
||||
// Device-specific readAnalogue function (analogue input)
|
||||
int ArduinoPins::_readAnalogue(VPIN vpin) {
|
||||
int pin = vpin;
|
||||
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
|
||||
uint8_t index = (pin-_firstVpin) / 8;
|
||||
|
@ -344,10 +428,22 @@ int ArduinoPins::_read(VPIN vpin) {
|
|||
else
|
||||
pinMode(pin, INPUT);
|
||||
}
|
||||
int value = !fastReadDigital(pin); // Invert (5v=0, 0v=1)
|
||||
|
||||
// Since AnalogRead is also called from interrupt code, disable interrupts
|
||||
// while we're using it. There's only one ADC shared by all analogue inputs
|
||||
// on the Arduino, so we don't want interruptions.
|
||||
//******************************************************************************
|
||||
// NOTE: If the HAL is running on a computer without the DCC signal generator,
|
||||
// then interrupts needn't be disabled. Also, the DCC signal generator puts
|
||||
// the ADC into fast mode, so if it isn't present, analogueRead calls will be much
|
||||
// slower!!
|
||||
//******************************************************************************
|
||||
noInterrupts();
|
||||
int value = analogRead(pin);
|
||||
interrupts();
|
||||
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
|
60
IODevice.h
60
IODevice.h
|
@ -120,7 +120,7 @@ public:
|
|||
}
|
||||
|
||||
// User-friendly function for configuring a servo pin.
|
||||
inline static bool configureServo(VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile, uint16_t duration, uint8_t initialState=0) {
|
||||
inline static bool configureServo(VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile=0, uint16_t duration=0, uint8_t initialState=0) {
|
||||
int params[] = {(int)activePosition, (int)inactivePosition, profile, (int)duration, initialState};
|
||||
return IODevice::configure(vpin, CONFIGURE_SERVO, 5, params);
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ public:
|
|||
static void write(VPIN vpin, int value);
|
||||
|
||||
// write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs)
|
||||
static void writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration=0);
|
||||
static void writeAnalogue(VPIN vpin, int value, uint8_t profile=0, uint16_t duration=0);
|
||||
|
||||
// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing
|
||||
// the output over a period of time.
|
||||
|
@ -141,6 +141,9 @@ public:
|
|||
// read invokes the IODevice instance's _read method.
|
||||
static int read(VPIN vpin);
|
||||
|
||||
// read invokes the IODevice instance's _readAnalogue method.
|
||||
static int readAnalogue(VPIN vpin);
|
||||
|
||||
// loop invokes the IODevice instance's _loop method.
|
||||
static void loop();
|
||||
|
||||
|
@ -160,7 +163,14 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
// Constructor
|
||||
IODevice(VPIN firstVpin=0, int nPins=0) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
}
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
virtual void _begin() {}
|
||||
|
||||
// Method to configure device (optionally implemented within device class)
|
||||
|
@ -175,35 +185,25 @@ protected:
|
|||
};
|
||||
|
||||
// Method to write an 'analogue' value (optionally implemented within device class)
|
||||
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
(void)vpin; (void)value; (void) profile; (void)duration;
|
||||
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) {
|
||||
(void)vpin; (void)value; (void) param1; (void)param2;
|
||||
};
|
||||
|
||||
// Function called to check whether callback notification is supported by this pin.
|
||||
// Defaults to no, if not overridden by the device.
|
||||
// The same value should be returned by all pins on the device, so only one need
|
||||
// be checked.
|
||||
virtual bool _hasCallback(VPIN vpin) {
|
||||
(void) vpin;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method to read pin state (optionally implemented within device class)
|
||||
// Method to read digital pin state (optionally implemented within device class)
|
||||
virtual int _read(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// _isBusy returns true if the device is currently in an animation of some sort, e.g. is changing
|
||||
// the output over a period of time. Returns false unless overridden in sub class.
|
||||
virtual bool _isBusy(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return false;
|
||||
}
|
||||
// Method to read analogue pin state (optionally implemented within device class)
|
||||
virtual int _readAnalogue(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Method to perform updates on an ongoing basis (optionally implemented within device class)
|
||||
virtual void _loop(unsigned long currentMicros) {
|
||||
(void)currentMicros; // Suppress compiler warning.
|
||||
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
|
||||
};
|
||||
|
||||
// Method for displaying info on DIAG output (optionally implemented within device class)
|
||||
|
@ -211,11 +211,19 @@ protected:
|
|||
|
||||
// Destructor
|
||||
virtual ~IODevice() {};
|
||||
|
||||
// Non-virtual function
|
||||
void delayUntil(unsigned long futureMicrosCount) {
|
||||
_nextEntryTime = futureMicrosCount;
|
||||
}
|
||||
|
||||
// Common object fields.
|
||||
VPIN _firstVpin;
|
||||
int _nPins;
|
||||
|
||||
// Flag whether the device supports callbacks.
|
||||
bool _hasCallback = false;
|
||||
|
||||
// Pin number of interrupt pin for GPIO extender devices. The extender module will pull this
|
||||
// pin low if an input changes state.
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
@ -233,6 +241,7 @@ private:
|
|||
static IODevice *findDevice(VPIN vpin);
|
||||
|
||||
IODevice *_nextDevice = 0;
|
||||
unsigned long _nextEntryTime;
|
||||
static IODevice *_firstDevice;
|
||||
|
||||
static IODevice *_nextLoopDevice;
|
||||
|
@ -267,7 +276,7 @@ private:
|
|||
// Device-specific write functions.
|
||||
void _write(VPIN vpin, int value) override;
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override;
|
||||
bool _isBusy(VPIN vpin) override;
|
||||
int _read(VPIN vpin) override; // returns the digital state or busy status of the device
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
void updatePosition(uint8_t pin);
|
||||
void writeDevice(uint8_t pin, int value);
|
||||
|
@ -294,7 +303,6 @@ private:
|
|||
static const byte FLASH _bounceProfile[30];
|
||||
|
||||
const unsigned int refreshInterval = 50; // refresh every 50ms
|
||||
unsigned long _lastRefreshTime; // last seen value of micros() count
|
||||
|
||||
// structures for setting up non-blocking writes to servo controller
|
||||
I2CRB requestBlock;
|
||||
|
@ -343,13 +351,15 @@ private:
|
|||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
// Device-specific write function.
|
||||
void _write(VPIN vpin, int value) override;
|
||||
// Device-specific read function.
|
||||
// Device-specific read functions.
|
||||
int _read(VPIN vpin) override;
|
||||
int _readAnalogue(VPIN vpin) override;
|
||||
void _display() override;
|
||||
|
||||
|
||||
uint8_t *_pinPullups;
|
||||
uint8_t *_pinModes; // each bit is 1 for output, 0 for input
|
||||
uint8_t *_pinInUse;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
165
IO_AnalogueInputs.h
Normal file
165
IO_AnalogueInputs.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef io_analogueinputs_h
|
||||
#define io_analogueinputs_h
|
||||
|
||||
// Uncomment following line to slow the scan cycle down to 1second ADC samples, with
|
||||
// diagnostic output of scanned values.
|
||||
//#define IO_ANALOGUE_SLOW
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/**********************************************************************************************
|
||||
* ADS111x class for I2C-connected analogue input modules ADS1113, ADS1114 and ADS1115.
|
||||
*
|
||||
* ADS1113 and ADS1114 are restricted to 1 input. ADS1115 has a multiplexer which allows
|
||||
* any of four input pins to be read by its ADC.
|
||||
*
|
||||
* The driver polls the device in accordance with the constant 'scanInterval' below. On first loop
|
||||
* entry, the multiplexer is set to pin A0 and the ADC is triggered. On second and subsequent
|
||||
* entries, the analogue value is read from the conversion register and then the multiplexer and
|
||||
* ADC are set up to read the next pin.
|
||||
*
|
||||
* The ADS111x is set up as follows:
|
||||
* Single-shot scan
|
||||
* Data rate 128 samples/sec (7.8ms/sample, but scanned every 10ms)
|
||||
* Comparator off
|
||||
* Gain FSR=6.144V
|
||||
* The gain means that the maximum input voltage of 5V (when Vss=5V) gives a reading
|
||||
* of 32767*(5.0/6.144) = 26666.
|
||||
*
|
||||
* A device is configured by the following:
|
||||
* ADS111x::create(firstVpin, nPins, i2cAddress);
|
||||
* for example
|
||||
* ADS111x::create(300, 1, 0x48); // single-input ADS1113
|
||||
* ADS111x::create(300, 4, 0x48); // four-input ADS1115
|
||||
*
|
||||
* Note: The device is simple and does not need initial configuration, so it should recover from
|
||||
* temporary loss of communications or power.
|
||||
**********************************************************************************************/
|
||||
class ADS111x: public IODevice {
|
||||
public:
|
||||
ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins,4);
|
||||
_i2cAddress = i2cAddress;
|
||||
_currentPin = 0;
|
||||
for (int8_t i=0; i<_nPins; i++)
|
||||
_value[i] = -1;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
void _begin() {
|
||||
// Initialise ADS device
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
_nextState = STATE_STARTSCAN;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
|
||||
// Check that previous non-blocking write has completed, if not then wait
|
||||
uint8_t status = _i2crb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything.
|
||||
if (status == I2C_STATUS_OK) {
|
||||
switch (_nextState) {
|
||||
case STATE_STARTSCAN:
|
||||
// Configure ADC and multiplexer for next scan. See ADS111x datasheet for details
|
||||
// of configuration register settings.
|
||||
_outBuffer[0] = 0x01; // Config register address
|
||||
_outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n
|
||||
_outBuffer[2] = 0xA3; // 250 samples/sec, comparator off
|
||||
// Write command, without waiting for completion.
|
||||
I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb);
|
||||
|
||||
delayUntil(currentMicros + scanInterval);
|
||||
_nextState = STATE_STARTREAD;
|
||||
break;
|
||||
|
||||
case STATE_STARTREAD:
|
||||
// Reading the pin value
|
||||
_outBuffer[0] = 0x00; // Conversion register address
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register
|
||||
_nextState = STATE_GETVALUE;
|
||||
break;
|
||||
|
||||
case STATE_GETVALUE:
|
||||
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
|
||||
#ifdef IO_ANALOGUE_SLOW
|
||||
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
|
||||
#endif
|
||||
|
||||
// Move to next pin
|
||||
if (++_currentPin >= _nPins) _currentPin = 0;
|
||||
_nextState = STATE_STARTSCAN;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else { // error status
|
||||
DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
return _value[pin];
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
// ADC conversion rate is 250SPS, or 4ms per conversion. Set the period between updates to 10ms.
|
||||
// This is enough to allow the conversion to reliably complete in time.
|
||||
#ifndef IO_ANALOGUE_SLOW
|
||||
const unsigned long scanInterval = 10000UL; // Period between successive ADC scans in microseconds.
|
||||
#else
|
||||
const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds.
|
||||
#endif
|
||||
enum : uint8_t {
|
||||
STATE_STARTSCAN,
|
||||
STATE_STARTREAD,
|
||||
STATE_GETVALUE,
|
||||
};
|
||||
uint16_t _value[4];
|
||||
uint8_t _i2cAddress;
|
||||
uint8_t _outBuffer[3];
|
||||
uint8_t _inBuffer[2];
|
||||
uint8_t _currentPin; // ADC pin currently being scanned
|
||||
I2CRB _i2crb;
|
||||
uint8_t _nextState;
|
||||
};
|
||||
|
||||
#endif // io_analogueinputs_h
|
|
@ -22,17 +22,9 @@
|
|||
#include "DIAG.h"
|
||||
#include "defines.h"
|
||||
|
||||
// Note: For DCC Accessory Decoders, a particular output can be specified by
|
||||
// a linear address, or by an address/subaddress pair, where the subaddress is
|
||||
// in the range 0 to 3 and specifies an output within a group of 4.
|
||||
// NMRA and DCC++EX accepts addresses in the range 0-511. Linear addresses
|
||||
// are not specified by the NMRA and so different manufacturers may calculate them
|
||||
// in different ways. DCC++EX uses a range of 1-2044 which excludes decoder address 0.
|
||||
// Linear address 1 corresponds to address 1 subaddress 0.
|
||||
|
||||
#define LINEARADDRESS(addr, subaddr) (((addr-1) << 2) + subaddr + 1)
|
||||
#define ADDRESS(linearaddr) (((linearaddr-1) >> 2) + 1)
|
||||
#define SUBADDRESS(linearaddr) ((linearaddr-1) % 4)
|
||||
#define PACKEDADDRESS(addr, subaddr) (((addr) << 2) + (subaddr))
|
||||
#define ADDRESS(packedaddr) ((packedaddr) >> 2)
|
||||
#define SUBADDRESS(packedaddr) ((packedaddr) % 4)
|
||||
|
||||
void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
new DCCAccessoryDecoder(vpin, nPins, DCCAddress, DCCSubaddress);
|
||||
|
@ -42,7 +34,7 @@ void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSu
|
|||
DCCAccessoryDecoder::DCCAccessoryDecoder(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
_firstVpin = vpin;
|
||||
_nPins = nPins;
|
||||
_packedAddress = LINEARADDRESS(DCCAddress, DCCSubaddress);
|
||||
_packedAddress = PACKEDADDRESS(DCCAddress, DCCSubaddress);
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
|
@ -66,8 +58,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
|||
|
||||
void DCCAccessoryDecoder::_display() {
|
||||
int endAddress = _packedAddress + _nPins - 1;
|
||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Linear Address:%d-%d (%d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||
_packedAddress, _packedAddress+_nPins-1,
|
||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
||||
}
|
||||
|
||||
|
|
255
IO_DFPlayer.h
Normal file
255
IO_DFPlayer.h
Normal file
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||
* amplifier, so it only needs a power supply and a speaker.
|
||||
*
|
||||
* This driver allows the device to be controlled through IODevice::write() and
|
||||
* IODevice::writeAnalogue() calls.
|
||||
*
|
||||
* The driver is configured as follows:
|
||||
*
|
||||
* DFPlayer::create(firstVpin, nPins, Serialn);
|
||||
*
|
||||
* Where firstVpin is the first vpin reserved for reading the device,
|
||||
* nPins is the number of pins to be allocated (max 5)
|
||||
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* DFPlayer::create(3500, 5, Serial1);
|
||||
*
|
||||
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card;
|
||||
* Writing an analogue value 0-30 to the second pin will set the volume of the output;
|
||||
* Writing a digital value to the first pin will play or stop the file;
|
||||
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
|
||||
*
|
||||
* From EX-RAIL, the following commands may be used:
|
||||
* SET(3500) -- starts playing the first file on the SD card
|
||||
* SET(3501) -- starts playing the second file on the SD card
|
||||
* etc.
|
||||
* RESET(3500) -- stops all playing on the player
|
||||
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
|
||||
* SERVO(3500,23,0) -- plays file 23 at current volume
|
||||
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum)
|
||||
* SERVO(3501,20,0) -- Sets the volume to 20
|
||||
*
|
||||
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
|
||||
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
|
||||
* A 1k resistor in series with the module's RX terminal will alleviate this.
|
||||
*/
|
||||
|
||||
#ifndef IO_DFPlayer_h
|
||||
#define IO_DFPlayer_h
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
class DFPlayer : public IODevice {
|
||||
private:
|
||||
HardwareSerial *_serial;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) :
|
||||
IODevice(firstVpin, nPins),
|
||||
_serial(&serial)
|
||||
{
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
new DFPlayer(firstVpin, nPins, serial);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
_serial->begin(9600);
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
|
||||
// Send a query to the device to see if it responds
|
||||
sendPacket(0x42);
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
||||
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
if (c == 0x7E)
|
||||
_inputIndex = 1;
|
||||
else if ((c==0xFF && _inputIndex==1)
|
||||
|| (c==0x3D && _inputIndex==3)
|
||||
|| (_inputIndex >=4 && _inputIndex <= 8))
|
||||
_inputIndex++;
|
||||
else if (c==0x06 && _inputIndex==2) {
|
||||
// Valid message prefix, so consider the device online
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_inputIndex++;
|
||||
} else if (c==0xEF && _inputIndex==9) {
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
_inputIndex = 0;
|
||||
} else
|
||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
||||
}
|
||||
// Check if the initial prompt to device has timed out. Allow 1 second
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
|
||||
DIAG(F("DFPlayer device not responding on serial port"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
// Write with value 1 starts playing a song. The relative pin number is the file number.
|
||||
// Write with value 0 stops playing.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) {
|
||||
// Value 1, start playing
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Play %d"), pin+1);
|
||||
#endif
|
||||
sendPacket(0x03, pin+1);
|
||||
_playing = true;
|
||||
} else {
|
||||
// Value 0, stop playing
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Stop"));
|
||||
#endif
|
||||
sendPacket(0x16);
|
||||
_playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
// Validate parameter.
|
||||
volume = min(30,volume);
|
||||
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
if (value > 0) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Play %d"), value);
|
||||
#endif
|
||||
sendPacket(0x03, value); // Play track
|
||||
_playing = true;
|
||||
if (volume > 0) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Volume %d"), volume);
|
||||
#endif
|
||||
sendPacket(0x06, volume); // Set volume
|
||||
}
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Stop"));
|
||||
#endif
|
||||
sendPacket(0x16); // Stop play
|
||||
_playing = false;
|
||||
}
|
||||
} else if (pin == 1) {
|
||||
// Set volume (0-30)
|
||||
if (value > 30) value = 30;
|
||||
else if (value < 0) value = 0;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Volume %d"), value);
|
||||
#endif
|
||||
sendPacket(0x06, value);
|
||||
}
|
||||
}
|
||||
|
||||
// A read on any pin indicates whether the player is still playing.
|
||||
int _read(VPIN) override {
|
||||
return _playing;
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||
// 0 -> 7E is start code
|
||||
// 1 -> FF is version
|
||||
// 2 -> 06 is length
|
||||
// 3 -> 0F is command
|
||||
// 4 -> 00 is no receive
|
||||
// 5~6 -> 01 01 is argument
|
||||
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||
// 9 -> EF is end code
|
||||
|
||||
void sendPacket(uint8_t command, uint16_t arg = 0)
|
||||
{
|
||||
uint8_t out[] = { 0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
command,
|
||||
00,
|
||||
static_cast<uint8_t>(arg >> 8),
|
||||
static_cast<uint8_t>(arg & 0x00ff),
|
||||
00,
|
||||
00,
|
||||
0xEF };
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
_serial->write(out, sizeof(out));
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
sum += packet[i];
|
||||
}
|
||||
return -sum;
|
||||
}
|
||||
|
||||
void setChecksum(uint8_t* out)
|
||||
{
|
||||
uint16_t sum = calcChecksum(out);
|
||||
|
||||
out[7] = (sum >> 8);
|
||||
out[8] = (sum & 0xff);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // IO_DFPlayer_h
|
|
@ -45,10 +45,6 @@ protected:
|
|||
int _read(VPIN vpin) override;
|
||||
void _display() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
bool _hasCallback(VPIN vpin) {
|
||||
(void)vpin; // suppress compiler warning
|
||||
return true; // Enable callback if caller wants to use it.
|
||||
}
|
||||
|
||||
// Data fields
|
||||
uint8_t _I2CAddress;
|
||||
|
@ -57,9 +53,9 @@ protected:
|
|||
T _portOutputState;
|
||||
T _portMode;
|
||||
T _portPullup;
|
||||
T _portInUse;
|
||||
// Interval between refreshes of each input port
|
||||
static const int _portTickTime = 4000;
|
||||
unsigned long _lastLoopEntry = 0;
|
||||
|
||||
// Virtual functions for interfacing with I2C GPIO Device
|
||||
virtual void _writeGpioPort() = 0;
|
||||
|
@ -80,12 +76,13 @@ protected:
|
|||
|
||||
// Constructor
|
||||
template <class T>
|
||||
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) {
|
||||
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) :
|
||||
IODevice(firstVpin, nPins)
|
||||
{
|
||||
_deviceName = deviceName;
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
_gpioInterruptPin = interruptPin;
|
||||
_hasCallback = true;
|
||||
// Add device to list of devices.
|
||||
addDevice(this);
|
||||
}
|
||||
|
@ -104,11 +101,14 @@ void GPIOBase<T>::_begin() {
|
|||
#endif
|
||||
_portMode = 0; // default to input mode
|
||||
_portPullup = -1; // default to pullup enabled
|
||||
_portInputState = -1;
|
||||
_portInputState = -1;
|
||||
_portInUse = 0;
|
||||
_setupDevice();
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
} else {
|
||||
DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
_setupDevice();
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
_lastLoopEntry = micros();
|
||||
}
|
||||
|
||||
// Configuration parameters for inputs:
|
||||
|
@ -128,11 +128,15 @@ bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun
|
|||
_portPullup |= mask;
|
||||
else
|
||||
_portPullup &= ~mask;
|
||||
// Mark that port has been accessed
|
||||
_portInUse |= mask;
|
||||
// Set input mode
|
||||
_portMode &= ~mask;
|
||||
|
||||
// Call subclass's virtual function to write to device
|
||||
_writePortModes();
|
||||
_writePullups();
|
||||
// Re-read port following change
|
||||
_readGpioPort();
|
||||
// Port change will be notified on next loop entry.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -151,6 +155,8 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
|||
I2CManager.getErrorMessage(status));
|
||||
}
|
||||
_processCompletion(status);
|
||||
// Set unused pin and write mode pin value to 1
|
||||
_portInputState |= ~_portInUse | _portMode;
|
||||
|
||||
// Scan for changes in input states and invoke callback (if present)
|
||||
T differences = lastPortStates ^ _portInputState;
|
||||
|
@ -172,27 +178,25 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
|||
#endif
|
||||
}
|
||||
|
||||
// Check if interrupt configured. If so, and pin is not pulled down, finish.
|
||||
if (_gpioInterruptPin >= 0) {
|
||||
if (digitalRead(_gpioInterruptPin)) return;
|
||||
} else
|
||||
// No interrupt pin. Check if tick has elapsed. If not, finish.
|
||||
if (currentMicros - _lastLoopEntry < (unsigned long)_portTickTime) return;
|
||||
// Check if interrupt configured. If not, or if it is active (pulled down), then
|
||||
// initiate a scan.
|
||||
if (_gpioInterruptPin < 0 || !digitalRead(_gpioInterruptPin)) {
|
||||
// TODO: Could suppress reads if there are no pins configured as inputs!
|
||||
|
||||
// TODO: Could suppress reads if there are no pins configured as inputs!
|
||||
|
||||
// Read input
|
||||
_lastLoopEntry = currentMicros;
|
||||
if (_deviceState == DEVSTATE_NORMAL) {
|
||||
_readGpioPort(false); // Initiate non-blocking read
|
||||
_deviceState= DEVSTATE_SCANNING;
|
||||
// Read input
|
||||
if (_deviceState == DEVSTATE_NORMAL) {
|
||||
_readGpioPort(false); // Initiate non-blocking read
|
||||
_deviceState= DEVSTATE_SCANNING;
|
||||
}
|
||||
}
|
||||
// Delay next entry until tick elapsed.
|
||||
delayUntil(currentMicros + _portTickTime);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GPIOBase<T>::_display() {
|
||||
DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d"), _deviceName, _I2CAddress,
|
||||
_firstVpin, _firstVpin+_nPins-1);
|
||||
DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress,
|
||||
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -203,8 +207,9 @@ void GPIOBase<T>::_write(VPIN vpin, int value) {
|
|||
DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value);
|
||||
#endif
|
||||
|
||||
// Set port mode output
|
||||
// Set port mode output if currently not output mode
|
||||
if (!(_portMode & mask)) {
|
||||
_portInUse |= mask;
|
||||
_portMode |= mask;
|
||||
_writePortModes();
|
||||
}
|
||||
|
@ -224,12 +229,16 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
|||
int pin = vpin - _firstVpin;
|
||||
T mask = 1 << pin;
|
||||
|
||||
// Set port mode to input
|
||||
if (_portMode & mask) {
|
||||
// Set port mode to input if currently output or first use
|
||||
if ((_portMode | ~_portInUse) & mask) {
|
||||
_portMode &= ~mask;
|
||||
_portInUse |= mask;
|
||||
_writePullups();
|
||||
_writePortModes();
|
||||
// Port won't have been read yet, so read it now.
|
||||
_readGpioPort();
|
||||
// Set unused pin and write mode pin value to 1
|
||||
_portInputState |= ~_portInUse | _portMode;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState);
|
||||
#endif
|
||||
|
|
107
IO_HCSR04.h
107
IO_HCSR04.h
|
@ -17,31 +17,37 @@
|
|||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* The HC-SR04 module has an ultrasonic transmitter (40kHz) and a receiver.
|
||||
* It is operated through two signal pins. When the transmit pin is set to 1 for
|
||||
* 10us, on the falling edge the transmitter sends a short transmission of
|
||||
* It is operated through two signal pins. When the transmit pin is set to 1
|
||||
* for 10us, on the falling edge the transmitter sends a short transmission of
|
||||
* 8 pulses (like a sonar 'ping'). This is reflected off objects and received
|
||||
* by the receiver. A pulse is sent on the receive pin whose length is equal
|
||||
* to the delay between the transmission of the pulse and the detection of
|
||||
* its echo. The distance of the reflecting object is calculated by halving
|
||||
* the time (to allow for the out and back distance), then multiplying by the
|
||||
* speed of sound (assumed to be constant).
|
||||
*
|
||||
*
|
||||
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
|
||||
* the length of the received pulse. If the calculated distance is less than the
|
||||
* threshold, the output changes to 1. If it is greater than the threshold plus
|
||||
* a hysteresis margin, the output changes to 0.
|
||||
*
|
||||
* The measurement would be more reliable if interrupts were disabled while the
|
||||
* pulse is being timed. However, this would affect other functions in the CS
|
||||
* so the measurement is being performed with interrupts enabled. Also, we could
|
||||
* use an interrupt pin in the Arduino for the timing, but the same consideration
|
||||
* applies.
|
||||
*
|
||||
* Note: The timing accuracy required by this means that the pins have to be
|
||||
* direct Arduino pins; GPIO pins on an IO Extender cannot provide the required
|
||||
* accuracy.
|
||||
* the length of the received pulse. If the calculated distance is less than
|
||||
* the threshold, the output state returned by a read() call changes to 1. If
|
||||
* the distance is greater than the threshold plus a hysteresis margin, the
|
||||
* output changes to 0. The device also supports readAnalogue(), which returns
|
||||
* the measured distance in cm, or 32767 if the distance exceeds the
|
||||
* offThreshold.
|
||||
*
|
||||
* It might be thought that the measurement would be more reliable if interrupts
|
||||
* were disabled while the pulse is being timed. However, this would affect
|
||||
* other functions in the CS so the measurement is being performed with
|
||||
* interrupts enabled. Also, we could use an interrupt pin in the Arduino for
|
||||
* the timing, but the same consideration applies. In any case, the DCC
|
||||
* interrupt occurs once every 58us, so any IRC code is much faster than that.
|
||||
* And 58us corresponds to 1cm in the calculation, so the effect of
|
||||
* interrupts is negligible.
|
||||
*
|
||||
* Note: The timing accuracy required for measuring the pulse length means that
|
||||
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
|
||||
* provide the required accuracy.
|
||||
*/
|
||||
|
||||
#ifndef IO_HCSR04_H
|
||||
|
@ -53,43 +59,42 @@ class HCSR04 : public IODevice {
|
|||
|
||||
private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _transmitPin = -1;
|
||||
int _receivePin = -1;
|
||||
int _trigPin = -1;
|
||||
int _echoPin = -1;
|
||||
// Thresholds for setting active state in cm.
|
||||
uint8_t _onThreshold; // cm
|
||||
uint8_t _offThreshold; // cm
|
||||
// Last measured distance in cm.
|
||||
uint16_t _distance;
|
||||
// Active=1/inactive=0 state
|
||||
uint8_t _value = 0;
|
||||
// Time of last loop execution
|
||||
unsigned long _lastExecutionTime;
|
||||
// Factor for calculating the distance (cm) from echo time (ms).
|
||||
// Based on a speed of sound of 345 metres/second.
|
||||
const uint16_t factor = 58; // ms/cm
|
||||
|
||||
public:
|
||||
// Constructor perfroms static initialisation of the device object
|
||||
HCSR04 (VPIN vpin, int transmitPin, int receivePin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
_firstVpin = vpin;
|
||||
_nPins = 1;
|
||||
_transmitPin = transmitPin;
|
||||
_receivePin = receivePin;
|
||||
_trigPin = trigPin;
|
||||
_echoPin = echoPin;
|
||||
_onThreshold = onThreshold;
|
||||
_offThreshold = offThreshold;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int transmitPin, int receivePin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
new HCSR04(vpin, transmitPin, receivePin, onThreshold, offThreshold);
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
}
|
||||
|
||||
protected:
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
pinMode(_transmitPin, OUTPUT);
|
||||
pinMode(_receivePin, INPUT);
|
||||
ArduinoPins::fastWriteDigital(_transmitPin, 0);
|
||||
_lastExecutionTime = micros();
|
||||
pinMode(_trigPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
|
@ -101,18 +106,21 @@ protected:
|
|||
return _value;
|
||||
}
|
||||
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
(void)vpin; // avoid compiler warning
|
||||
return _distance;
|
||||
}
|
||||
|
||||
// _loop function - read HC-SR04 once every 50 milliseconds.
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (currentMicros - _lastExecutionTime > 50000UL) {
|
||||
_lastExecutionTime = currentMicros;
|
||||
|
||||
_value = read_HCSR04device();
|
||||
}
|
||||
read_HCSR04device();
|
||||
// Delay next loop entry until 50ms have elapsed.
|
||||
delayUntil(currentMicros + 50000UL);
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
||||
_firstVpin, _transmitPin, _receivePin, _onThreshold, _offThreshold);
|
||||
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -127,51 +135,52 @@ private:
|
|||
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
|
||||
// greater than the offThreshold.
|
||||
//
|
||||
uint8_t read_HCSR04device() {
|
||||
void read_HCSR04device() {
|
||||
// uint16 enough to time up to 65ms
|
||||
uint16_t startTime, waitTime, currentTime, maxTime;
|
||||
|
||||
// If receive pin is still set on from previous call, abort the read.
|
||||
if (ArduinoPins::fastReadDigital(_receivePin)) return _value;
|
||||
if (ArduinoPins::fastReadDigital(_echoPin))
|
||||
return;
|
||||
|
||||
// Send 10us pulse to trigger transmitter
|
||||
ArduinoPins::fastWriteDigital(_transmitPin, 1);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 1);
|
||||
delayMicroseconds(10);
|
||||
ArduinoPins::fastWriteDigital(_transmitPin, 0);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||
|
||||
// Wait for receive pin to be set
|
||||
startTime = currentTime = micros();
|
||||
maxTime = factor * _offThreshold * 2;
|
||||
while (!ArduinoPins::fastReadDigital(_receivePin)) {
|
||||
while (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
// lastTime = currentTime;
|
||||
currentTime = micros();
|
||||
waitTime = currentTime - startTime;
|
||||
if (waitTime > maxTime) {
|
||||
// Timeout waiting for pulse start, abort the read
|
||||
return _value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for receive pin to reset, and measure length of pulse
|
||||
startTime = currentTime = micros();
|
||||
maxTime = factor * _offThreshold;
|
||||
while (ArduinoPins::fastReadDigital(_receivePin)) {
|
||||
while (ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
currentTime = micros();
|
||||
waitTime = currentTime - startTime;
|
||||
// If pulse is too long then set return value to zero,
|
||||
// and finish without waiting for end of pulse.
|
||||
if (waitTime > maxTime) {
|
||||
// Pulse length longer than maxTime, reset value.
|
||||
return 0;
|
||||
_value = 0;
|
||||
_distance = 32767;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Check if pulse length is below threshold, if so set value.
|
||||
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance);
|
||||
uint16_t distance = waitTime / factor; // in centimetres
|
||||
if (distance < _onThreshold)
|
||||
return 1;
|
||||
|
||||
return _value;
|
||||
_distance = waitTime / factor; // in centimetres
|
||||
if (_distance < _onThreshold)
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -42,14 +42,18 @@ private:
|
|||
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
|
||||
}
|
||||
void _writePullups() override {
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup);
|
||||
// Set pullups only for in-use pins. This prevents pullup being set for a pin that
|
||||
// is intended for use as an output but hasn't been written to yet.
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup & _portInUse);
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Each bit is 1 for an input, 0 for an output, i.e. inverted.
|
||||
I2CManager.write(_I2CAddress, 2, REG_IODIR, ~_portMode);
|
||||
// Enable interrupt-on-change for pins that are inputs (_portMode=0)
|
||||
// Write 0 to IODIR for in-use pins that are outputs, 1 for others.
|
||||
uint8_t temp = ~(_portMode & _portInUse);
|
||||
I2CManager.write(_I2CAddress, 2, REG_IODIR, temp);
|
||||
// Enable interrupt-on-change for in-use pins that are inputs (_portMode=0)
|
||||
temp = ~_portMode & _portInUse;
|
||||
I2CManager.write(_I2CAddress, 2, REG_INTCON, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPINTEN, ~_portMode);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPINTEN, temp);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
|
|
|
@ -48,14 +48,19 @@ private:
|
|||
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
void _writePullups() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPPUA, _portPullup, _portPullup>>8);
|
||||
// Set pullups only for in-use pins. This prevents pullup being set for a pin that
|
||||
// is intended for use as an output but hasn't been written to yet.
|
||||
uint16_t temp = _portPullup & _portInUse;
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8);
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Write 1 to IODIR for pins that are inputs, 0 for outputs (i.e. _portMode inverted)
|
||||
I2CManager.write(_I2CAddress, 3, REG_IODIRA, ~_portMode, (~_portMode)>>8);
|
||||
// Enable interrupt for those pins which are inputs (_portMode=0)
|
||||
// Write 0 to IODIR for in-use pins that are outputs, 1 for others.
|
||||
uint16_t temp = ~(_portMode & _portInUse);
|
||||
I2CManager.write(_I2CAddress, 3, REG_IODIRA, temp, temp>>8);
|
||||
// Enable interrupt for in-use pins which are inputs (_portMode=0)
|
||||
temp = ~_portMode & _portInUse;
|
||||
I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00);
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPINTENA, ~_portMode, (~_portMode)>>8);
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
|
|
|
@ -107,12 +107,14 @@ void PCA9685::_begin() {
|
|||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
} else
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#endif
|
||||
|
@ -137,6 +139,7 @@ void PCA9685::_write(VPIN vpin, int value) {
|
|||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
|
||||
vpin, value, profile, duration);
|
||||
|
@ -169,9 +172,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
|
|||
s->fromPosition = s->currentPosition;
|
||||
}
|
||||
|
||||
// _isBusy returns true if the device is currently in executing an animation,
|
||||
// _read returns true if the device is currently in executing an animation,
|
||||
// changing the output over a period of time.
|
||||
bool PCA9685::_isBusy(VPIN vpin) {
|
||||
int PCA9685::_read(VPIN vpin) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s == NULL)
|
||||
|
@ -181,12 +185,10 @@ bool PCA9685::_isBusy(VPIN vpin) {
|
|||
}
|
||||
|
||||
void PCA9685::_loop(unsigned long currentMicros) {
|
||||
if (currentMicros - _lastRefreshTime >= refreshInterval * 1000) {
|
||||
for (int pin=0; pin<_nPins; pin++) {
|
||||
updatePosition(pin);
|
||||
}
|
||||
_lastRefreshTime = currentMicros;
|
||||
for (int pin=0; pin<_nPins; pin++) {
|
||||
updatePosition(pin);
|
||||
}
|
||||
delayUntil(currentMicros + refreshInterval * 1000UL);
|
||||
}
|
||||
|
||||
// Private function to reposition servo
|
||||
|
@ -238,20 +240,25 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
|
|||
DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value);
|
||||
#endif
|
||||
// Wait for previous request to complete
|
||||
requestBlock.wait();
|
||||
// Set up new request.
|
||||
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
|
||||
outputBuffer[1] = 0;
|
||||
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
|
||||
outputBuffer[3] = value & 0xff;
|
||||
outputBuffer[4] = value >> 8;
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
uint8_t status = requestBlock.wait();
|
||||
if (status != I2C_STATUS_OK) {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
DIAG(F("PCA9685 I2C:x%x failed %S"), _I2CAddress, I2CManager.getErrorMessage(status));
|
||||
} else {
|
||||
// Set up new request.
|
||||
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
|
||||
outputBuffer[1] = 0;
|
||||
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
|
||||
outputBuffer[3] = value & 0xff;
|
||||
outputBuffer[4] = value >> 8;
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// Display details of this device.
|
||||
void PCA9685::_display() {
|
||||
DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1);
|
||||
DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
// Internal helper function for this device
|
||||
|
|
20
IO_PCF8574.h
20
IO_PCF8574.h
|
@ -17,6 +17,24 @@
|
|||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The PCF8574 is a simple device; it only has one register. The device
|
||||
* input/output mode and pullup are configured through this, and the
|
||||
* output state is written and the input state read through it too.
|
||||
*
|
||||
* This is accomplished by having a weak resistor in series with the output,
|
||||
* and a read-back of the other end of the resistor. As an output, the
|
||||
* pin state is set to 1 or 0, and the output voltage goes to +5V or 0V
|
||||
* (through the weak resistor).
|
||||
*
|
||||
* In order to use the pin as an input, the output is written as
|
||||
* a '1' in order to pull up the resistor. Therefore the input will be
|
||||
* 1 unless the pin is pulled down externally, in which case it will be 0.
|
||||
*
|
||||
* As a consequence of this approach, it is not possible to use the device for
|
||||
* inputs without pullups.
|
||||
*/
|
||||
|
||||
#ifndef IO_PCF8574_H
|
||||
#define IO_PCF8574_H
|
||||
|
||||
|
@ -70,7 +88,7 @@ private:
|
|||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = ((uint16_t)inputBuffer[0]) & 0xff;
|
||||
else
|
||||
_portInputState = 0xff;
|
||||
_portInputState = 0xff;
|
||||
}
|
||||
|
||||
// Set up device ports
|
||||
|
|
299
IO_VL53L0X.h
Normal file
299
IO_VL53L0X.h
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The VL53L0X Time-Of-Flight sensor operates by sending a short laser pulse and detecting
|
||||
* the reflection of the pulse. The time between the pulse and the receipt of reflections
|
||||
* is measured and used to determine the distance to the reflecting object.
|
||||
*
|
||||
* For economy of memory and processing time, this driver includes only part of the code
|
||||
* that ST provide in their API. Also, the API code isn't very clear and it is not easy
|
||||
* to identify what operations are useful and what are not.
|
||||
* The operation shown here doesn't include any calibration, so is probably not as accurate
|
||||
* as using the full driver, but it's probably accurate enough for the purpose.
|
||||
*
|
||||
* The device driver allocates up to 3 vpins to the device. A digital read on the first pin
|
||||
* will return a value that indicates whether the object is within the threshold range (1)
|
||||
* or not (0). An analogue read on the first pin returns the last measured distance (in mm),
|
||||
* the second pin returns the signal strength, and the third pin returns detected
|
||||
* ambient light level. By default the device takes around 60ms to complete a ranging
|
||||
* operation, so we do a 100ms cycle (10 samples per second).
|
||||
*
|
||||
* The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module,
|
||||
* you can use this address. However, the address can be modified by software. If
|
||||
* you select another address, that address will be written to the device and used until the device is reset.
|
||||
*
|
||||
* If you have more than one module, then you will need to specify a digital VPIN (Arduino
|
||||
* digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now,
|
||||
* when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once
|
||||
* all VL53L0X modules are turned off, the driver works through each module in turn by
|
||||
* setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address.
|
||||
* In this way, many VL53L0X modules can be connected to the one I2C bus, each one
|
||||
* using a distinct I2C address.
|
||||
*
|
||||
* WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise,
|
||||
* and the device may even reset when handled. If you're not using XSHUT, then it's
|
||||
* best to tie it to +5V.
|
||||
*
|
||||
* The driver is configured as follows:
|
||||
*
|
||||
* Single VL53L0X module:
|
||||
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold);
|
||||
* Where firstVpin is the first vpin reserved for reading the device,
|
||||
* nPins is 1, 2 or 3,
|
||||
* i2cAddress is the address of the device (normally 0x29),
|
||||
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
|
||||
* and highThreshold is the distance at which the digital vpin state is set to 0 (in mm).
|
||||
*
|
||||
* Multiple VL53L0X modules:
|
||||
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold, xshutPin);
|
||||
* ...
|
||||
* Where firstVpin is the first vpin reserved for reading the device,
|
||||
* nPins is 1, 2 or 3,
|
||||
* i2cAddress is the address of the device (any valid address except 0x29),
|
||||
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
|
||||
* highThreshold is the distance at which the digital vpin state is set to 0 (in mm),
|
||||
* and xshutPin is the VPIN number corresponding to a digital output that is connected to the
|
||||
* XSHUT terminal on the module.
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* VL53L0X::create(4000, 3, 0x29, 200, 250);
|
||||
* Sensor::create(4000, 4000, 0); // Create a sensor
|
||||
*
|
||||
* When an object comes within 200mm of the sensor, a message
|
||||
* <Q 4000>
|
||||
* will be sent over the serial USB, and when the object moves more than 250mm from the sensor,
|
||||
* a message
|
||||
* <q 4000>
|
||||
* will be sent.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_VL53L0X_h
|
||||
#define IO_VL53L0X_h
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
class VL53L0X : public IODevice {
|
||||
private:
|
||||
uint8_t _i2cAddress;
|
||||
uint16_t _ambient;
|
||||
uint16_t _distance;
|
||||
uint16_t _signal;
|
||||
uint16_t _onThreshold;
|
||||
uint16_t _offThreshold;
|
||||
VPIN _xshutPin;
|
||||
bool _value;
|
||||
uint8_t _nextState = 0;
|
||||
I2CRB _rb;
|
||||
uint8_t _inBuffer[12];
|
||||
uint8_t _outBuffer[2];
|
||||
// State machine states.
|
||||
enum : uint8_t {
|
||||
STATE_INIT = 0,
|
||||
STATE_CONFIGUREADDRESS = 1,
|
||||
STATE_SKIP = 2,
|
||||
STATE_CONFIGUREDEVICE = 3,
|
||||
STATE_INITIATESCAN = 4,
|
||||
STATE_CHECKSTATUS = 5,
|
||||
STATE_GETRESULTS = 6,
|
||||
STATE_DECODERESULTS = 7,
|
||||
};
|
||||
|
||||
// Register addresses
|
||||
enum : uint8_t {
|
||||
VL53L0X_REG_SYSRANGE_START=0x00,
|
||||
VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13,
|
||||
VL53L0X_REG_RESULT_RANGE_STATUS=0x14,
|
||||
VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV=0x89,
|
||||
VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A,
|
||||
};
|
||||
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
|
||||
|
||||
public:
|
||||
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 3);
|
||||
_i2cAddress = i2cAddress;
|
||||
_onThreshold = onThreshold;
|
||||
_offThreshold = offThreshold;
|
||||
_xshutPin = xshutPin;
|
||||
_value = 0;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
if (_xshutPin == VPIN_NONE) {
|
||||
// Check if device is already responding on the nominated address.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
// Yes, it's already on this address, so skip the address initialisation.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
} else {
|
||||
_nextState = STATE_INIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
uint8_t status;
|
||||
switch (_nextState) {
|
||||
case STATE_INIT:
|
||||
// On first entry to loop, reset this module by pulling XSHUT low. All modules
|
||||
// will be reset in turn.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
|
||||
_nextState = STATE_CONFIGUREADDRESS;
|
||||
break;
|
||||
case STATE_CONFIGUREADDRESS:
|
||||
// On second entry, set XSHUT pin high to allow the module to restart.
|
||||
// On the module, there is a diode in series with the XSHUT pin to
|
||||
// protect the low-voltage pin against +5V.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1);
|
||||
// Allow the module time to restart
|
||||
delay(10);
|
||||
// Then write the desired I2C address to the device, while this is the only
|
||||
// module responding to the default address.
|
||||
I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress);
|
||||
_nextState = STATE_SKIP;
|
||||
break;
|
||||
case STATE_SKIP:
|
||||
// Do nothing on the third entry.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
break;
|
||||
case STATE_CONFIGUREDEVICE:
|
||||
// On next entry, check if device address has been set.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
// Set 2.8V mode
|
||||
write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
|
||||
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
|
||||
} else {
|
||||
DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
case STATE_INITIATESCAN:
|
||||
// Not scanning, so initiate a scan
|
||||
_outBuffer[0] = VL53L0X_REG_SYSRANGE_START;
|
||||
_outBuffer[1] = 0x01;
|
||||
I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb);
|
||||
_nextState = STATE_CHECKSTATUS;
|
||||
break;
|
||||
case STATE_CHECKSTATUS:
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // try next time
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_value = false;
|
||||
} else
|
||||
_nextState = 2;
|
||||
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
|
||||
_nextState = STATE_GETRESULTS;
|
||||
break;
|
||||
case STATE_GETRESULTS:
|
||||
// Ranging completed. Request results
|
||||
_outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
|
||||
_nextState = 3;
|
||||
delayUntil(currentMicros + 5000); // Allow 5ms to get data
|
||||
_nextState = STATE_DECODERESULTS;
|
||||
break;
|
||||
case STATE_DECODERESULTS:
|
||||
// If I2C write still busy, return.
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // try again next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
if (!(_inBuffer[0] & 1)) return; // device still busy
|
||||
uint8_t deviceRangeStatus = ((_inBuffer[0] & 0x78) >> 3);
|
||||
if (deviceRangeStatus == 0x0b) {
|
||||
// Range status OK, so use data
|
||||
_ambient = makeuint16(_inBuffer[7], _inBuffer[6]);
|
||||
_signal = makeuint16(_inBuffer[9], _inBuffer[8]);
|
||||
_distance = makeuint16(_inBuffer[11], _inBuffer[10]);
|
||||
if (_distance <= _onThreshold)
|
||||
_value = true;
|
||||
else if (_distance > _offThreshold)
|
||||
_value = false;
|
||||
}
|
||||
}
|
||||
// Completed. Restart scan on next loop entry.
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level.
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
switch (pin) {
|
||||
case 0:
|
||||
return _distance;
|
||||
case 1:
|
||||
return _signal;
|
||||
case 2:
|
||||
return _ambient;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// For digital read, return zero for all but first pin.
|
||||
int _read(VPIN vpin) override {
|
||||
if (vpin == _firstVpin)
|
||||
return _value;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
||||
_i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
inline uint16_t makeuint16(byte lsb, byte msb) {
|
||||
return (((uint16_t)msb) << 8) | lsb;
|
||||
}
|
||||
uint8_t write_reg(uint8_t reg, uint8_t data) {
|
||||
// write byte to register
|
||||
uint8_t outBuffer[2];
|
||||
outBuffer[0] = reg;
|
||||
outBuffer[1] = data;
|
||||
return I2CManager.write(_i2cAddress, outBuffer, 2);
|
||||
}
|
||||
uint8_t read_reg(uint8_t reg) {
|
||||
// read byte from register and return value
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1);
|
||||
return _inBuffer[0];
|
||||
}
|
||||
};
|
||||
|
||||
#endif // IO_VL53L0X_h
|
|
@ -87,5 +87,9 @@
|
|||
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
|
||||
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
// YFROBOT Motor Shield (V3.1)
|
||||
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
|
||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
#endif
|
||||
|
|
34
RMFT2.cpp
34
RMFT2.cpp
|
@ -19,6 +19,7 @@
|
|||
#include <Arduino.h>
|
||||
#include "RMFT2.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DIAG.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DCCEXParser.h"
|
||||
|
@ -43,6 +44,7 @@ const int16_t HASH_KEYWORD_ROUTES=-3702;
|
|||
// The thrrads exist in a ring, each time through loop() the next thread in the ring is serviced.
|
||||
|
||||
// Statics
|
||||
const int16_t LOCO_ID_WAITING=-99; // waiting for loco id from prog track
|
||||
int16_t RMFT2::progtrackLocoId; // used for callback when detecting a loco on prograck
|
||||
bool RMFT2::diag=false; // <D EXRAIL ON>
|
||||
RMFT2 * RMFT2::loopTask=NULL; // loopTask contains the address of ONE of the tasks in a ring.
|
||||
|
@ -64,6 +66,16 @@ byte RMFT2::flags[MAX_FLAGS];
|
|||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
|
||||
switch (opcode) {
|
||||
case OPCODE_AT:
|
||||
case OPCODE_AFTER:
|
||||
case OPCODE_IF:
|
||||
case OPCODE_IFNOT:
|
||||
int16_t pin = (int16_t)GET_OPERAND(0);
|
||||
if (pin<0) pin = -pin;
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
}
|
||||
|
||||
if (opcode==OPCODE_SIGNAL) {
|
||||
VPIN red=GET_OPERAND(0);
|
||||
VPIN amber=GET_OPERAND(1);
|
||||
|
@ -326,6 +338,10 @@ int RMFT2::locateRouteStart(int16_t _route) {
|
|||
void RMFT2::driveLoco(byte speed) {
|
||||
if (loco<=0) return; // Prevent broadcast!
|
||||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
Serial.println(F("<p1>")); // tell JMRI
|
||||
}
|
||||
DCC::setThrottle(loco,speed, forward^invert);
|
||||
speedo=speed;
|
||||
}
|
||||
|
@ -477,6 +493,13 @@ void RMFT2::loop2() {
|
|||
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
|
||||
break;
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
Serial.println(F("<p0>")); // Tell JMRI
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
pausingTask=NULL;
|
||||
driveLoco(speedo);
|
||||
|
@ -572,7 +595,10 @@ void RMFT2::loop2() {
|
|||
return;
|
||||
|
||||
case OPCODE_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
Serial.println(F("<p1 JOIN>")); // Tell JMRI
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
|
@ -580,14 +606,20 @@ void RMFT2::loop2() {
|
|||
break;
|
||||
|
||||
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
|
||||
progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet
|
||||
DCC::getLocoId(readLocoCallback);
|
||||
break;
|
||||
|
||||
case OPCODE_READ_LOCO2:
|
||||
if (progtrackLocoId<0) {
|
||||
if (progtrackLocoId==LOCO_ID_WAITING) {
|
||||
delayMe(100);
|
||||
return; // still waiting for callback
|
||||
}
|
||||
if (progtrackLocoId<0) {
|
||||
kill(F("No Loco Found"),progtrackLocoId);
|
||||
return; // still waiting for callback
|
||||
}
|
||||
|
||||
loco=progtrackLocoId;
|
||||
speedo=0;
|
||||
forward=true;
|
||||
|
|
2
RMFT2.h
2
RMFT2.h
|
@ -40,7 +40,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
|||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
||||
|
|
13
RMFTMacros.h
13
RMFTMacros.h
|
@ -19,8 +19,9 @@
|
|||
#ifndef RMFTMacros_H
|
||||
#define RMFTMacros_H
|
||||
|
||||
// remove normal code LCD macro (will be restored later)
|
||||
// remove normal code LCD & SERIAL macros (will be restored later)
|
||||
#undef LCD
|
||||
#undef SERIAL
|
||||
|
||||
|
||||
// This file will include and build the EXRAIL script and associated helper tricks.
|
||||
|
@ -89,6 +90,7 @@
|
|||
#define PAUSE
|
||||
#define PRINT(msg)
|
||||
#define POM(cv,value)
|
||||
#define POWEROFF
|
||||
#define READ_LOCO
|
||||
#define RED(signal_id)
|
||||
#define RESERVE(blockid)
|
||||
|
@ -147,7 +149,7 @@ const int StringMacroTracker1=__COUNTER__;
|
|||
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
||||
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
||||
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(L&Serial2,F(msg));break;
|
||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
||||
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
||||
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
||||
#include "myAutomation.h"
|
||||
|
@ -187,6 +189,7 @@ const int StringMacroTracker1=__COUNTER__;
|
|||
#undef ONTHROW
|
||||
#undef PAUSE
|
||||
#undef POM
|
||||
#undef POWEROFF
|
||||
#undef PRINT
|
||||
#undef READ_LOCO
|
||||
#undef RED
|
||||
|
@ -264,6 +267,7 @@ const int StringMacroTracker1=__COUNTER__;
|
|||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define PAUSE OPCODE_PAUSE,NOP,
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
#define POWEROFF OPCODE_POWEROFF,NOP,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
#define READ_LOCO OPCODE_READ_LOCO1,NOP,OPCODE_READ_LOCO2,NOP,
|
||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||
|
@ -299,8 +303,9 @@ const int StringMacroTracker1=__COUNTER__;
|
|||
const int StringMacroTracker2=__COUNTER__;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Restore normal code LCD macro
|
||||
// Restore normal code LCD & SERIAL macro
|
||||
#undef LCD
|
||||
#define LCD StringFormatter::lcd
|
||||
|
||||
#undef SERIAL
|
||||
#define SERIAL 0x0
|
||||
#endif
|
||||
|
|
67
Sensors.cpp
67
Sensors.cpp
|
@ -103,12 +103,6 @@ void Sensor::checkAll(Print *stream){
|
|||
// Required time elapsed since last read cycle started,
|
||||
// so initiate new scan through the sensor list
|
||||
readingSensor = firstSensor;
|
||||
#ifdef USE_NOTIFY
|
||||
if (firstSensor == firstPollSensor)
|
||||
pollSignalPhase = true;
|
||||
else
|
||||
pollSignalPhase = false;
|
||||
#endif
|
||||
lastReadCycle = thisTime;
|
||||
}
|
||||
}
|
||||
|
@ -117,12 +111,6 @@ void Sensor::checkAll(Print *stream){
|
|||
bool pause = false;
|
||||
while (readingSensor != NULL && !pause) {
|
||||
|
||||
#ifdef USE_NOTIFY
|
||||
// Check if we have reached the start of the polled portion of the sensor list.
|
||||
if (readingSensor == firstPollSensor)
|
||||
pollSignalPhase = true;
|
||||
#endif
|
||||
|
||||
// Where the sensor is attached to a pin, read pin status. For sources such as LCN,
|
||||
// which don't have an input pin to read, the LCN class calls setState() to update inputState when
|
||||
// a message is received. The IODevice::read() call returns 1 for active pins (0v) and 0 for inactive (5v).
|
||||
|
@ -130,10 +118,8 @@ void Sensor::checkAll(Print *stream){
|
|||
// routine when an input signal change is detected, and this updates the inputState directly,
|
||||
// so these inputs don't need to be polled here.
|
||||
VPIN pin = readingSensor->data.pin;
|
||||
#ifdef USE_NOTIFY
|
||||
if (pollSignalPhase)
|
||||
#endif
|
||||
if (pin!=VPIN_NONE) readingSensor->inputState = IODevice::read(pin);
|
||||
if (readingSensor->pollingRequired && pin != VPIN_NONE)
|
||||
readingSensor->inputState = IODevice::read(pin);
|
||||
|
||||
// Check if changed since last time, and process changes.
|
||||
if (readingSensor->inputState == readingSensor->active) {
|
||||
|
@ -156,22 +142,12 @@ void Sensor::checkAll(Print *stream){
|
|||
// Move to next sensor in list.
|
||||
readingSensor = readingSensor->nextSensor;
|
||||
|
||||
// Currently process max of 16 sensors per entry for polled sensors, and
|
||||
// 16 per entry for sensors notified by callback.
|
||||
// Performance measurements taken during development indicate that, with 64 sensors configured
|
||||
// on 8x 8-pin PCF8574 GPIO expanders, all inputs can be read within 1.4ms (400Mhz I2C bus speed), and a
|
||||
// full cycle of scanning 64 sensors for changes takes between 1.9 and 3.2 milliseconds.
|
||||
// Currently process max of 16 sensors per entry.
|
||||
// Performance measurements taken during development indicate that, with 128 sensors configured
|
||||
// on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices
|
||||
// within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond.
|
||||
sensorCount++;
|
||||
#ifdef USE_NOTIFY
|
||||
if (pollSignalPhase) {
|
||||
#endif
|
||||
if (sensorCount >= 16) pause = true;
|
||||
#ifdef USE_NOTIFY
|
||||
} else
|
||||
{
|
||||
if (sensorCount >= 16) pause = true;
|
||||
}
|
||||
#endif
|
||||
if (sensorCount >= 16) pause = true;
|
||||
}
|
||||
|
||||
} // Sensor::checkAll
|
||||
|
@ -223,23 +199,18 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){
|
|||
tt = (Sensor *)calloc(1,sizeof(Sensor));
|
||||
if (!tt) return tt; // memory allocation failure
|
||||
|
||||
#ifdef USE_NOTIFY
|
||||
if (pin == VPIN_NONE || IODevice::hasCallback(pin)) {
|
||||
// Callback available, or no pin to read, so link sensor on to the start of the list
|
||||
tt->nextSensor = firstSensor;
|
||||
firstSensor = tt;
|
||||
if (lastSensor == NULL) lastSensor = tt; // This is only item in list.
|
||||
} else {
|
||||
// No callback, so add to end of list so it's polled.
|
||||
if (lastSensor != NULL) lastSensor->nextSensor = tt;
|
||||
lastSensor = tt;
|
||||
if (!firstSensor) firstSensor = tt;
|
||||
if (!firstPollSensor) firstPollSensor = tt;
|
||||
}
|
||||
#else
|
||||
if (pin == VPIN_NONE)
|
||||
tt->pollingRequired = false;
|
||||
#ifdef USE_NOTIFY
|
||||
else if (IODevice::hasCallback(pin))
|
||||
tt->pollingRequired = false;
|
||||
#endif
|
||||
else
|
||||
tt->pollingRequired = true;
|
||||
|
||||
// Add to the start of the list
|
||||
tt->nextSensor = firstSensor;
|
||||
firstSensor = tt;
|
||||
#endif
|
||||
|
||||
tt->data.snum = snum;
|
||||
tt->data.pin = pin;
|
||||
|
@ -248,9 +219,8 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){
|
|||
tt->inputState = 0;
|
||||
tt->latchDelay = minReadCount;
|
||||
|
||||
int params[] = {pullUp};
|
||||
if (pin != VPIN_NONE)
|
||||
IODevice::configure(pin, IODevice::CONFIGURE_INPUT, 1, params);
|
||||
IODevice::configureInput(pin, pullUp);
|
||||
// Generally, internal pull-up resistors are not, on their own, sufficient
|
||||
// for external infrared sensors --- each sensor must have its own 1K external pull-up resistor
|
||||
|
||||
|
@ -343,6 +313,5 @@ unsigned long Sensor::lastReadCycle=0;
|
|||
#ifdef USE_NOTIFY
|
||||
Sensor *Sensor::firstPollSensor = NULL;
|
||||
Sensor *Sensor::lastSensor = NULL;
|
||||
bool Sensor::pollSignalPhase = false;
|
||||
bool Sensor::inputChangeCallbackRegistered = false;
|
||||
#endif
|
11
Sensors.h
11
Sensors.h
|
@ -45,14 +45,6 @@ struct SensorData {
|
|||
class Sensor{
|
||||
// The sensor list is a linked list where each sensor's 'nextSensor' field points to the next.
|
||||
// The pointer is null in the last on the list.
|
||||
// To partition the sensor into those sensors which require polling through cyclic calls
|
||||
// to 'IODevice::read(vpin)', and those which support callback on change, 'firstSensor'
|
||||
// points to the start of the overall list, and 'lastSensor' points to the end of the list
|
||||
// (the last sensor object). This structure allows sensors to be added to the start or the
|
||||
// end of the list easily. So if an input pin supports change notification, it is placed at the
|
||||
// end of the list. If not, it is placed at the beginning. And the pointer 'firstPollSensor'
|
||||
// is set to the first of the sensor objects that requires scanning. Thus, we can iterate
|
||||
// through the whole list, or just through the part that requires scanning.
|
||||
|
||||
public:
|
||||
SensorData data;
|
||||
|
@ -74,6 +66,7 @@ public:
|
|||
// Constructor
|
||||
Sensor();
|
||||
Sensor *nextSensor;
|
||||
|
||||
void setState(int state);
|
||||
static void load();
|
||||
static void store();
|
||||
|
@ -88,9 +81,9 @@ public:
|
|||
static const unsigned int minReadCount = 1; // number of additional scans before acting on change
|
||||
// E.g. 1 means that a change is ignored for one scan and actioned on the next.
|
||||
// Max value is 63
|
||||
bool pollingRequired = true;
|
||||
|
||||
#ifdef USE_NOTIFY
|
||||
static bool pollSignalPhase;
|
||||
static void inputChangeCallback(VPIN vpin, int state);
|
||||
static bool inputChangeCallbackRegistered;
|
||||
#endif
|
||||
|
|
17
Turnouts.cpp
17
Turnouts.cpp
|
@ -340,7 +340,8 @@
|
|||
DCCTurnout::DCCTurnout(uint16_t id, uint16_t address, uint8_t subAdd) :
|
||||
Turnout(id, TURNOUT_DCC, false)
|
||||
{
|
||||
_dccTurnoutData.address = ((address-1) << 2) + subAdd + 1;
|
||||
_dccTurnoutData.address = address;
|
||||
_dccTurnoutData.subAddress = subAdd;
|
||||
}
|
||||
|
||||
// Create function
|
||||
|
@ -351,7 +352,8 @@
|
|||
if (tt->isType(TURNOUT_DCC)) {
|
||||
// Yes, so set parameters<T>
|
||||
DCCTurnout *dt = (DCCTurnout *)tt;
|
||||
dt->_dccTurnoutData.address = ((add-1) << 2) + subAdd + 1;
|
||||
dt->_dccTurnoutData.address = add;
|
||||
dt->_dccTurnoutData.subAddress = subAdd;
|
||||
// Don't touch the _closed parameter, retain the original value.
|
||||
return tt;
|
||||
} else {
|
||||
|
@ -371,27 +373,24 @@
|
|||
EEStore::advance(sizeof(dccTurnoutData));
|
||||
|
||||
// Create new object
|
||||
DCCTurnout *tt = new DCCTurnout(turnoutData->id, (((dccTurnoutData.address-1) >> 2)+1), ((dccTurnoutData.address-1) & 3));
|
||||
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
|
||||
|
||||
return tt;
|
||||
}
|
||||
|
||||
void DCCTurnout::print(Print *stream) {
|
||||
StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), _turnoutData.id,
|
||||
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3),
|
||||
!_turnoutData.closed);
|
||||
_dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed);
|
||||
// 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,
|
||||
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3),
|
||||
!_turnoutData.closed);
|
||||
_dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed);
|
||||
}
|
||||
|
||||
bool DCCTurnout::setClosedInternal(bool close) {
|
||||
// DCC++ Classic behaviour is that Throw writes a 1 in the packet,
|
||||
// and Close writes a 0.
|
||||
// RCN-213 specifies that Throw is 0 and Close is 1.
|
||||
DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1),
|
||||
((_dccTurnoutData.address-1) & 3), close ^ !rcn213Compliant);
|
||||
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
||||
_turnoutData.closed = close;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -213,9 +213,11 @@ private:
|
|||
// DCCTurnoutData contains data specific to this subclass that is
|
||||
// written to EEPROM when the turnout is saved.
|
||||
struct DCCTurnoutData {
|
||||
// DCC address (Address in bits 15-2, subaddress in bits 1-0
|
||||
uint16_t address; // CS currently supports linear address 1-2048
|
||||
// That's DCC accessory address 1-512 and subaddress 0-3.
|
||||
// DCC address (Address in bits 15-2, subaddress in bits 1-0)
|
||||
struct {
|
||||
uint16_t address : 14;
|
||||
uint8_t subAddress : 2;
|
||||
};
|
||||
} _dccTurnoutData; // 2 bytes
|
||||
|
||||
// Constructor
|
||||
|
|
|
@ -119,11 +119,11 @@ The configuration file for DCC-EX Command Station
|
|||
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
|
||||
//
|
||||
// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780
|
||||
// controller and a PCF8574 based I2C 'backpack'.
|
||||
// controller and a commonly available PCF8574 based I2C 'backpack'.
|
||||
// To enable, uncomment one of the #define lines below
|
||||
|
||||
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
||||
// #define LCD_DRIVER 0x3F,16,2
|
||||
// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows
|
||||
// #define LCD_DRIVER 0x27,16,2
|
||||
|
||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
#include "IO_HCSR04.h"
|
||||
#include "IO_VL53L0X.h"
|
||||
|
||||
|
||||
// The #if directive prevent compile errors for Uno and Nano by excluding the
|
||||
|
@ -23,8 +24,9 @@
|
|||
// 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)
|
||||
|
@ -33,13 +35,15 @@
|
|||
//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
|
||||
// First Vpin=196
|
||||
// Number of VPINs=16 (numbered 196-211)
|
||||
// I2C address of module=0x22
|
||||
|
||||
//MCP23017 gpioModule2(164, 16, 0x20);
|
||||
//MCP23017 gpioModule2(196, 16, 0x22);
|
||||
|
||||
|
||||
// Alternative form, which allows the INT pin of the module to request a scan
|
||||
|
@ -47,19 +51,23 @@
|
|||
// 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);
|
||||
//MCP23017 gpioModule2(196, 16, 0x22, 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);
|
||||
//MCP23008 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)
|
||||
|
@ -73,7 +81,9 @@
|
|||
//PCF8574 gpioModule4(200, 8, 0x23, 40);
|
||||
|
||||
|
||||
// The following directive defines an HCSR04 ultrasonic module.
|
||||
//=======================================================================
|
||||
// The following directive defines an HCSR04 ultrasonic ranging module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// Vpin=2000 (only one VPIN per directive)
|
||||
// Number of VPINs=1
|
||||
|
@ -90,20 +100,48 @@
|
|||
//HCSR04 sonarModule2(2001, 30, 32, 20, 25);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a single VL53L0X Time-of-Flight range sensor.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// VPIN=5000
|
||||
// Number of VPINs=1
|
||||
// I2C address=0x29 (default for this chip)
|
||||
// Minimum trigger range=200mm (VPIN goes to 1 when <20cm)
|
||||
// Maximum trigger range=250mm (VPIN goes to 0 when >25cm)
|
||||
|
||||
//VL53L0X tofModule1(5000, 1, 0x29, 200, 250);
|
||||
|
||||
// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the
|
||||
// module's XSHUT pin. This allows the modules to be configured, at start,
|
||||
// with distinct I2C addresses. In this case, the address 0x29 is only used during
|
||||
// initialisation to configure each device in turn with the desired unique I2C address.
|
||||
// The examples below have the modules' XSHUT pins connected to the first two pins of
|
||||
// the first MCP23017 module (164 and 165), but Arduino pins may be used instead.
|
||||
// The first module here is given I2C address 0x30 and the second is 0x31.
|
||||
|
||||
//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 164);
|
||||
//VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 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
|
||||
// Alternative way of creating a module driver, 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);
|
||||
//MCP23017::create(196, 16, 0x22);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 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.
|
||||
|
@ -111,7 +149,9 @@ void mySetup() {
|
|||
//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
|
||||
|
@ -120,7 +160,9 @@ void mySetup() {
|
|||
//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)
|
||||
|
@ -129,11 +171,44 @@ void mySetup() {
|
|||
//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);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module.
|
||||
//=======================================================================
|
||||
// Parameters:
|
||||
// 10000 = first VPIN allocated.
|
||||
// 10 = number of VPINs allocated.
|
||||
// Serial1 = name of serial port (usually Serial1 or Serial2).
|
||||
// With these parameters, up to 10 files may be played on pins 10000-10009.
|
||||
// Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001)
|
||||
// for second file, etc. Play may also be initiated by writing an analogue
|
||||
// value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file.
|
||||
// SERVO(10000,23,30) will do the same thing, as well as setting the volume to
|
||||
// 30 (maximum value).
|
||||
// Play is stopped by RESET(10000) (or any other allocated VPIN).
|
||||
// Volume may also be set by writing an analogue value to the second pin for the player,
|
||||
// e.g. SERVO(10001,30,0) sets volume to maximum (30).
|
||||
// The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the
|
||||
// following line when the player is no longer busy.
|
||||
// E.g.
|
||||
// SEQUENCE(1)
|
||||
// AT(164) // Wait for sensor attached to pin 164 to activate
|
||||
// SET(10003) // Play fourth MP3 file
|
||||
// LCD(4, "Playing") // Display message on LCD/OLED
|
||||
// WAITFOR(10003) // Wait for playing to finish
|
||||
// LCD(4, " ") // Clear LCD/OLED line
|
||||
// FOLLOW(1) // Go back to start
|
||||
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,6 +16,7 @@ default_envs =
|
|||
unowifiR2
|
||||
nano
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
|
@ -41,7 +42,7 @@ lib_deps =
|
|||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
build_flags = -DDIAG_IO
|
||||
build_flags = -DDIAG_IO -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
platform = atmelavr
|
||||
|
|
22
version.h
22
version.h
|
@ -3,8 +3,26 @@
|
|||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "3.1.6"
|
||||
#define VERSION "3.2.0 rc2"
|
||||
// 3.2.0 Major functional and non-functional changes.
|
||||
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
||||
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
// Support for PCA9685 PWM (servo) control modules.
|
||||
// Support for analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
// Support for MP3 sound playback via DFPlayer module.
|
||||
// Support for HC-SR04 Ultrasonic range sensor module.
|
||||
// Support for VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
// Native non-blocking I2C drivers for AVR and Nano architectures (fallback
|
||||
// to blocking Wire library for other platforms).
|
||||
// EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
// New EX-RAIL automation capability.
|
||||
// Turnout class revised to expand turnout capabilities, new commands added.
|
||||
// Output class now allows ID > 255.
|
||||
// Configuration options to globally flip polarity of DCC Accessory states when driven
|
||||
// from <a> command and <T> command.
|
||||
// Increased use of display for showing loco decoder programming information.
|
||||
// ...
|
||||
// 3.1.7 Bugfix: Unknown locos should have speed forward
|
||||
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM
|
||||
// 3.1.5 Fix LCD corruption on power-up
|
||||
// 3.1.4 Refactor OLED and LCD drivers and remove unused code
|
||||
|
|
Loading…
Reference in New Issue
Block a user