diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp
index 9401796..1111810 100644
--- a/DCCEXParser.cpp
+++ b/DCCEXParser.cpp
@@ -56,6 +56,7 @@ const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_RESET = 26133;
const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816;
+const int16_t HASH_KEYWORD_SERVO = 27709;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
@@ -800,6 +801,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
+ case HASH_KEYWORD_SERVO:
+ IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
+ break;
+
default: // invalid/unknown
break;
}
diff --git a/IODevice.cpp b/IODevice.cpp
index 81bea0e..43db2f6 100644
--- a/IODevice.cpp
+++ b/IODevice.cpp
@@ -56,6 +56,7 @@ void IODevice::begin() {
for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) {
dev->_begin();
}
+ _initPhase = false;
}
// Overarching static loop() method for the IODevice subsystem. Works through the
@@ -114,37 +115,6 @@ bool IODevice::hasCallback(VPIN vpin) {
return dev->_hasCallback(vpin);
}
-
-// Remove specified device if one exists. This is necessary if devices are
-// created on-the-fly by Turnouts, Sensors or Outputs since they may have
-// been saved to EEPROM and recreated on start.
-void IODevice::remove(VPIN vpin) {
- // Only works if the object is exclusive, i.e. only one VPIN.
- IODevice *previousDev = 0;
- for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
- if (dev->owns(vpin)) {
- // Found object
- if (dev->_isDeletable()) {
- // First check it isn't next one to be processed by loop().
- // If so, skip to the following one.
- if (dev == _nextLoopDevice)
- _nextLoopDevice = _nextLoopDevice->_nextDevice;
- // Now unlink
- if (!previousDev)
- _firstDevice = dev->_nextDevice;
- else
- previousDev->_nextDevice = dev->_nextDevice;
- delete dev;
-#ifdef DIAG_IO
- DIAG(F("IODevice deleted Vpin:%d"), vpin);
-#endif
- return;
- }
- }
- previousDev = dev;
- }
-}
-
// Display (to diagnostics) details of the device.
void IODevice::_display() {
DIAG(F("Unknown device Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
@@ -200,22 +170,25 @@ void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
_gpioInterruptPin = pinNumber;
}
-IONotifyStateChangeCallback *IODevice::registerInputChangeNotification(IONotifyStateChangeCallback *callback) {
- IONotifyStateChangeCallback *previousHead = _notifyCallbackChain;
- _notifyCallbackChain = callback;
- return previousHead;
-}
-
-
// Private helper function to add a device to the chain of devices.
void IODevice::addDevice(IODevice *newDevice) {
- // Link new object to the start of chain. Thereby,
- // a write or read will act on the first device found.
- newDevice->_nextDevice = _firstDevice;
- _firstDevice = newDevice;
+ // Link new object to the end of the chain. Thereby, the first devices to be declared/created
+ // will be located faster by findDevice than those which are created later.
+ // Ideally declare/create the digital IO pins first, then servos, then more esoteric devices.
+ IODevice *lastDevice;
+ if (_firstDevice == 0)
+ _firstDevice = newDevice;
+ else {
+ for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice)
+ lastDevice = dev;
+ lastDevice->_nextDevice = newDevice;
+ }
+ newDevice->_nextDevice = 0;
- // Initialise device
- newDevice->_begin();
+ // If the IODevice::begin() method has already been called, initialise device here. If not,
+ // the device's _begin() method will be called by IODevice::begin().
+ if (!_initPhase)
+ newDevice->_begin();
}
// Private helper function to locate a device by VPIN. Returns NULL if not found
@@ -231,7 +204,17 @@ IODevice *IODevice::findDevice(VPIN vpin) {
// Static data
//------------------------------------------------------------------------------------------------------------------
-IONotifyStateChangeCallback *IODevice::_notifyCallbackChain = 0;
+// Chain of callback blocks (identifying registered callback functions for state changes)
+IONotifyCallback *IONotifyCallback::first = 0;
+
+// Start of chain of devices.
+IODevice *IODevice::_firstDevice = 0;
+
+// Reference to next device to be called on _loop() method.
+IODevice *IODevice::_nextLoopDevice = 0;
+
+// Flag which is reset when IODevice::begin has been called.
+bool IODevice::_initPhase = true;
//==================================================================================================================
@@ -243,23 +226,6 @@ bool IODevice::owns(VPIN id) {
return (id >= _firstVpin && id < _firstVpin + _nPins);
}
-// Write to devices which are after the current one in the list; this
-// function allows a device to have the same input and output VPIN number, and
-// a write to the VPIN from outside the device is passed to the device, but a
-// call to writeDownstream will pass it to another device with the same
-// VPIN number if one exists.
-// void IODevice::writeDownstream(VPIN vpin, int value) {
-// for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) {
-// if (dev->owns(vpin)) {
-// dev->_write(vpin, value);
-// return;
-// }
-// }
-// #ifdef DIAG_IO
-// //DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
-// #endif
-// }
-
// Read value from virtual pin.
int IODevice::read(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
@@ -272,15 +238,6 @@ int IODevice::read(VPIN vpin) {
return false;
}
-bool IODevice::_isDeletable() {
- return false;
-}
-
-// Start of chain of devices.
-IODevice *IODevice::_firstDevice = 0;
-
-// Reference to next device to be called on _loop() method.
-IODevice *IODevice::_nextLoopDevice = 0;
#else // !defined(IO_NO_HAL)
@@ -298,6 +255,9 @@ void IODevice::write(VPIN vpin, int value) {
digitalWrite(vpin, value);
pinMode(vpin, OUTPUT);
}
+void IODevice::writeAnalogue(VPIN vpin, int value, int profile) {
+ (void)vpin; (void)value; (void)profile; // Avoid compiler warnings
+}
bool IODevice::hasCallback(VPIN vpin) {
(void)vpin; // Avoid compiler warnings
return false;
@@ -311,16 +271,13 @@ void IODevice::DumpAll() {
DIAG(F("NO HAL CONFIGURED!"));
}
bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < 49); }
-void IODevice::remove(VPIN vpin) {
- (void)vpin; // Avoid compiler warnings
-}
void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
(void) pinNumber; // Avoid compiler warning
}
-IONotifyStateChangeCallback *IODevice::registerInputChangeNotification(IONotifyStateChangeCallback *callback) {
- (void)callback; // Avoid compiler warning
- return NULL;
-}
+
+// Chain of callback blocks (identifying registered callback functions for state changes)
+// Not used in IO_NO_HAL but must be declared.
+IONotifyCallback *IONotifyCallback::first = 0;
#endif // IO_NO_HAL
@@ -373,11 +330,7 @@ void ArduinoPins::_write(VPIN vpin, int value) {
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
// First update the output state, then set into write mode if not already.
- #if defined(USE_FAST_IO)
fastWriteDigital(pin, value);
- #else
- digitalWrite(pin, value);
- #endif
if (!(_pinModes[index] & mask)) {
// Currently in read mode, change to write mode
_pinModes[index] |= mask;
@@ -400,11 +353,7 @@ int ArduinoPins::_read(VPIN vpin) {
else
pinMode(pin, INPUT);
}
- #if defined(USE_FAST_IO)
int value = !fastReadDigital(pin); // Invert (5v=0, 0v=1)
- #else
- int value = !digitalRead(pin); // Invert (5v=0, 0v=1)
- #endif
#ifdef DIAG_IO
//DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
@@ -418,9 +367,9 @@ void ArduinoPins::_display() {
/////////////////////////////////////////////////////////////////////////////////////////////////////
-#if defined(USE_FAST_IO)
void ArduinoPins::fastWriteDigital(uint8_t pin, uint8_t value) {
+#if defined(USE_FAST_IO)
if (pin >= NUM_DIGITAL_PINS) return;
uint8_t mask = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
@@ -431,16 +380,22 @@ void ArduinoPins::fastWriteDigital(uint8_t pin, uint8_t value) {
else
*outPortAdr &= ~mask;
interrupts();
+#else
+ digitalWrite(pin, value);
+#endif
}
bool ArduinoPins::fastReadDigital(uint8_t pin) {
+#if defined(USE_FAST_IO)
if (pin >= NUM_DIGITAL_PINS) return false;
uint8_t mask = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *inPortAdr = portInputRegister(port);
// read input
bool result = (*inPortAdr & mask) != 0;
+#else
+ bool result = digitalRead(pin);
+#endif
return result;
}
-#endif
diff --git a/IODevice.h b/IODevice.h
index 6717c8b..a542f56 100644
--- a/IODevice.h
+++ b/IODevice.h
@@ -49,9 +49,32 @@ typedef uint16_t VPIN;
#define VPIN_MAX 32767
#define VPIN_NONE 65535
+/*
+ * Callback support for state change notification from an IODevice subclass to a
+ * handler, e.g. Sensor object handling.
+ */
-typedef void IONotifyStateChangeCallback(VPIN vpin, int value);
-
+class IONotifyCallback {
+public:
+ typedef void IONotifyCallbackFunction(VPIN vpin, int value);
+ static void add(IONotifyCallbackFunction *function) {
+ IONotifyCallback *blk = new IONotifyCallback(function);
+ if (first) blk->next = first;
+ first = blk;
+ }
+ static void invokeAll(VPIN vpin, int value) {
+ for (IONotifyCallback *blk = first; blk != NULL; blk = blk->next)
+ blk->invoke(vpin, value);
+ }
+ static bool hasCallback() {
+ return first != NULL;
+ }
+private:
+ IONotifyCallback(IONotifyCallbackFunction *function) { invoke = function; };
+ IONotifyCallback *next = 0;
+ IONotifyCallbackFunction *invoke = 0;
+ static IONotifyCallback *first;
+};
/*
* IODevice class
@@ -82,7 +105,8 @@ public:
// Static functions to find the device and invoke its member functions
- // begin is invoked to create any standard IODevice subclass instances
+ // begin is invoked to create any standard IODevice subclass instances.
+ // Also, the _begin method of any existing instances is called from here.
static void begin();
// configure is used invoke an IODevice instance's _configure method
@@ -112,9 +136,6 @@ public:
// exists checks whether there is a device owning the specified vpin
static bool exists(VPIN vpin);
- // remove deletes the device associated with the vpin, if it is deletable
- static void remove(VPIN vpin);
-
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
// Without the shared interrupt, input states are scanned periodically to detect changes on
@@ -123,23 +144,6 @@ public:
// once the GPIO port concerned has been read.
void setGPIOInterruptPin(int16_t pinNumber);
- // Method to add a notification. it is the caller's responsibility to save the return value
- // and invoke the event handler associate with it. Example:
- //
- // NotifyStateChangeCallback *nextEv = registerInputChangeNotification(myProc);
- //
- // void processChange(VPIN pin, int value) {
- // // Do something
- // // Pass on to next event handler
- // if (nextEv) nextEv(pin, value);
- // }
- //
- // Note that this implementation is rudimentary and assumes a small number of callbacks (typically one). If
- // more than one callback is registered, then the calls to successive callback functions are
- // nested, and stack usage will be impacted. If callbacks are extensively used, it is recommended that
- // a class or struct be implemented to hold the callback address, which can be chained to avoid
- // nested callbacks.
- static IONotifyStateChangeCallback *registerInputChangeNotification(IONotifyStateChangeCallback *callback);
protected:
@@ -200,23 +204,18 @@ protected:
// Destructor
virtual ~IODevice() {};
- // isDeletable returns true if object is deletable (i.e. is not a base device driver).
- virtual bool _isDeletable();
-
// Common object fields.
VPIN _firstVpin;
int _nPins;
- // Pin number of interrupt pin for GPIO extender devices. The device will pull this
+ // 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;
// Static support function for subclass creation
static void addDevice(IODevice *newDevice);
- // Notification of change
- static IONotifyStateChangeCallback *_notifyCallbackChain;
-
+ // Current state of device
DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
private:
@@ -229,6 +228,7 @@ private:
static IODevice *_firstDevice;
static IODevice *_nextLoopDevice;
+ static bool _initPhase;
};
@@ -240,6 +240,8 @@ private:
class PCA9685 : public IODevice {
public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress);
+ // Constructor
+ PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
enum ProfileType {
Instant = 0, // Moves immediately between positions
Fast = 1, // Takes around 500ms end-to-end
@@ -249,8 +251,6 @@ public:
};
private:
- // Constructor
- PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
// Device-specific initialisation
void _begin() override;
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
@@ -301,11 +301,12 @@ private:
class DCCAccessoryDecoder: public IODevice {
public:
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
-
-private:
// Constructor
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
+
+private:
// Device-specific write function.
+ void _begin() override;
void _write(VPIN vpin, int value) override;
void _display() override;
int _packedAddress;
@@ -326,6 +327,9 @@ public:
// Constructor
ArduinoPins(VPIN firstVpin, int nPins);
+ static void fastWriteDigital(uint8_t pin, uint8_t value);
+ static bool fastReadDigital(uint8_t pin);
+
private:
// Device-specific pin configuration
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
@@ -335,8 +339,6 @@ private:
int _read(VPIN vpin) override;
void _display() override;
- void fastWriteDigital(uint8_t pin, uint8_t value);
- bool fastReadDigital(uint8_t pin);
uint8_t *_pinPullups;
uint8_t *_pinModes; // each bit is 1 for output, 0 for input
diff --git a/IO_DCCAccessory.cpp b/IO_DCCAccessory.cpp
index fefebb5..139d900 100644
--- a/IO_DCCAccessory.cpp
+++ b/IO_DCCAccessory.cpp
@@ -39,7 +39,12 @@ DCCAccessoryDecoder::DCCAccessoryDecoder(VPIN vpin, int nPins, int DCCAddress, i
_firstVpin = vpin;
_nPins = nPins;
_packedAddress = (DCCAddress << 2) + DCCSubaddress;
+}
+
+void DCCAccessoryDecoder::_begin() {
int endAddress = _packedAddress + _nPins - 1;
+ int DCCAddress = _packedAddress >> 2;
+ int DCCSubaddress = _packedAddress & 3;
DIAG(F("DCC Accessory Decoder configured Vpins:%d-%d Linear Address:%d-%d (%d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
_packedAddress, _packedAddress+_nPins-1,
DCCAddress, DCCSubaddress, endAddress >> 2, endAddress % 4);
diff --git a/IO_ExampleSerial.cpp b/IO_ExampleSerial.cpp
index 8ee8f13..6954e55 100644
--- a/IO_ExampleSerial.cpp
+++ b/IO_ExampleSerial.cpp
@@ -25,21 +25,25 @@
IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
_firstVpin = firstVpin;
_nPins = nPins;
+ _pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
+ _baud = baud;
// Save reference to serial port driver
_serial = serial;
- _serial->begin(baud);
- DIAG(F("ExampleSerial configured Vpins:%d-%d"), _firstVpin, _firstVpin+_nPins-1);
+
+ addDevice(this);
}
// Static create method for one module.
void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
- IO_ExampleSerial *dev = new IO_ExampleSerial(firstVpin, nPins, serial, baud);
- addDevice(dev);
+ new IO_ExampleSerial(firstVpin, nPins, serial, baud);
}
// Device-specific initialisation
void IO_ExampleSerial::_begin() {
+ _serial->begin(_baud);
+ DIAG(F("ExampleSerial configured Vpins:%d-%d"), _firstVpin, _firstVpin+_nPins-1);
+
// Send a few # characters to the output
for (uint8_t i=0; i<3; i++)
_serial->write('#');
@@ -65,9 +69,8 @@ void IO_ExampleSerial::_write(VPIN vpin, int value) {
// Device-specific read function.
int IO_ExampleSerial::_read(VPIN vpin) {
- // Return a value for the specified vpin. For illustration, return
- // a value indicating whether the pin number is odd.
- int result = (vpin & 1);
+ // Return a value for the specified vpin.
+ int result = _pinValues[vpin-_firstVpin];
return result;
}
@@ -80,35 +83,38 @@ void IO_ExampleSerial::_loop(unsigned long currentMicros) {
if (_serial->available()) {
// Input data available to read. Read a character.
char c = _serial->read();
- switch (inputState) {
+ switch (_inputState) {
case 0: // Waiting for start of command
if (c == '#') // Start of command received.
- inputState = 1;
+ _inputState = 1;
break;
case 1: // Expecting command character
if (c == 'N') { // 'Notify' character received
- inputState = 2;
- inputValue = inputIndex = 0;
+ _inputState = 2;
+ _inputValue = _inputIndex = 0;
} else
- inputState = 0; // Unexpected char, reset
+ _inputState = 0; // Unexpected char, reset
break;
case 2: // reading first parameter (index)
if (isdigit(c))
- inputIndex = inputIndex * 10 + (c-'0');
+ _inputIndex = _inputIndex * 10 + (c-'0');
else if (c==',')
- inputState = 3;
+ _inputState = 3;
else
- inputState = 0; // Unexpected char, reset
+ _inputState = 0; // Unexpected char, reset
break;
case 3: // reading reading second parameter (value)
if (isdigit(c))
- inputValue = inputValue * 10 - (c-'0');
+ _inputValue = _inputValue * 10 - (c-'0');
else if (c=='#') { // End of command
// Complete command received, do something with it.
- DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), inputIndex, inputValue);
- inputState = 0; // Done, start again.
+ DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
+ if (_inputIndex < _nPins) { // Store value
+ _pinValues[_inputIndex] = _inputValue;
+ }
+ _inputState = 0; // Done, start again.
} else
- inputState = 0; // Unexpected char, reset
+ _inputState = 0; // Unexpected char, reset
break;
}
}
diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h
index 1273a95..582a51c 100644
--- a/IO_ExampleSerial.h
+++ b/IO_ExampleSerial.h
@@ -17,6 +17,18 @@
* along with CommandStation. If not, see .
*/
+/*
+ * To declare a device instance,
+ * IO_ExampleSerial myDevice(1000, 10, Serial3, 9600);
+ * or to create programmatically,
+ * IO_ExampleSerial::create(1000, 10, Serial3, 9600);
+ *
+ * (uses VPINs 1000-1009, talke on Serial 3 at 9600 baud.)
+ *
+ * See IO_ExampleSerial.cpp for the protocol used over the serial line.
+ *
+ */
+
#ifndef IO_EXAMPLESERIAL_H
#define IO_EXAMPLESERIAL_H
@@ -27,6 +39,7 @@ public:
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
+protected:
void _begin() override;
void _loop(unsigned long currentMicros) override;
void _write(VPIN vpin, int value) override;
@@ -35,9 +48,11 @@ public:
private:
HardwareSerial *_serial;
- uint8_t inputState = 0;
- int inputIndex = 0;
- int inputValue = 0;
+ uint8_t _inputState = 0;
+ int _inputIndex = 0;
+ int _inputValue = 0;
+ uint16_t *_pinValues; // Pointer to block of memory containing pin values
+ unsigned long _baud;
};
#endif // IO_EXAMPLESERIAL_H
\ No newline at end of file
diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h
index 366d0fc..7179f9f 100644
--- a/IO_GPIOBase.h
+++ b/IO_GPIOBase.h
@@ -45,6 +45,10 @@ 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;
@@ -82,29 +86,29 @@ GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2
_nPins = nPins;
_I2CAddress = I2CAddress;
_gpioInterruptPin = interruptPin;
- _notifyCallbackChain = 0;
// Add device to list of devices.
addDevice(this);
+}
+template
+void GPIOBase::_begin() {
// Configure pin used for GPIO extender notification of change (if allocated)
if (_gpioInterruptPin >= 0)
pinMode(_gpioInterruptPin, INPUT_PULLUP);
I2CManager.begin();
I2CManager.setClock(400000);
- if (I2CManager.exists(I2CAddress)) {
+ if (I2CManager.exists(_I2CAddress)) {
_display();
_portMode = 0; // default to input mode
_portPullup = -1; // default to pullup enabled
- _portInputState = 0;
+ _portInputState = -1;
}
+ _setupDevice();
_deviceState = DEVSTATE_NORMAL;
_lastLoopEntry = micros();
}
-template
-void GPIOBase::_begin() {}
-
// Configuration parameters for inputs:
// params[0]: enable pullup
// params[1]: invert input (optional)
@@ -134,9 +138,7 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun
// Periodically read the input port
template
void GPIOBase::_loop(unsigned long currentMicros) {
- #ifdef DIAG_IO
T lastPortStates = _portInputState;
- #endif
if (_deviceState == DEVSTATE_SCANNING && !requestBlock.isBusy()) {
uint8_t status = requestBlock.status;
if (status == I2C_STATUS_OK) {
@@ -146,7 +148,27 @@ void GPIOBase::_loop(unsigned long currentMicros) {
DIAG(F("%S I2C:x%x Error:%d"), _deviceName, _I2CAddress, status);
}
_processCompletion(status);
+
+ // Scan for changes in input states and invoke callback (if present)
+ T differences = lastPortStates ^ _portInputState;
+ if (differences && IONotifyCallback::hasCallback()) {
+ // Scan for differences bit by bit
+ T mask = 1;
+ for (int pin=0; pin<_nPins; pin++) {
+ if (differences & mask) {
+ // Change detected.
+ IONotifyCallback::invokeAll(_firstVpin+pin, (_portInputState & mask) == 0);
+ }
+ mask <<= 1;
+ }
+ }
+
+ #ifdef DIAG_IO
+ if (differences)
+ DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState);
+ #endif
}
+
// Check if interrupt configured. If so, and pin is not pulled down, finish.
if (_gpioInterruptPin >= 0) {
if (digitalRead(_gpioInterruptPin)) return;
@@ -162,12 +184,6 @@ void GPIOBase::_loop(unsigned long currentMicros) {
_readGpioPort(false); // Initiate non-blocking read
_deviceState= DEVSTATE_SCANNING;
}
-
- #ifdef DIAG_IO
- T differences = lastPortStates ^ _portInputState;
- if (differences)
- DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState);
- #endif
}
template
diff --git a/IO_HCSR04.h b/IO_HCSR04.h
new file mode 100644
index 0000000..5234fe1
--- /dev/null
+++ b/IO_HCSR04.h
@@ -0,0 +1,173 @@
+/*
+ * © 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 .
+ */
+
+/*
+ * 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
+ * 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.
+ */
+
+#ifndef IO_HCSR04_H
+#define IO_HCSR04_H
+
+#include "IODevice.h"
+
+class HCSR04 : public IODevice {
+
+private:
+ // pins must be arduino GPIO pins, not extender pins or HAL pins.
+ int _transmitPin = -1;
+ int _receivePin = -1;
+ // Thresholds for setting active state in cm.
+ uint8_t _onThreshold; // cm
+ uint8_t _offThreshold; // cm
+ // 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) {
+ _firstVpin = vpin;
+ _nPins = 1;
+ _transmitPin = transmitPin;
+ _receivePin = receivePin;
+ _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);
+ }
+
+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();
+ DIAG(F("HCSR04 configured on VPIN:%d TXpin:%d RXpin:%d On:%dcm Off:%dcm"),
+ _firstVpin, _transmitPin, _receivePin, _onThreshold, _offThreshold);
+ }
+
+ // _read function - just return _value (calculated in _loop).
+ int _read(VPIN vpin) override {
+ (void)vpin; // avoid compiler warning
+ return _value;
+ }
+
+ // _loop function - read HC-SR04 once every 50 milliseconds.
+ void _loop(unsigned long currentMicros) override {
+ if (currentMicros - _lastExecutionTime > 50000) {
+ _lastExecutionTime = currentMicros;
+
+ _value = read_HCSR04device();
+ }
+ }
+
+private:
+ // This polls the HC-SR04 device by sending a pulse and measuring the duration of
+ // the pulse observed on the receive pin. In order to be kind to the rest of the CS
+ // software, no interrupts are used and interrupts are not disabled. The pulse duration
+ // is measured in a loop, using the micros() function. Therefore, interrupts from other
+ // sources may affect the result. However, interrupts response code in CS typically takes
+ // much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm
+ // in the HC-SR04.
+ // To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the
+ // 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() {
+ // 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;
+
+ // Send 10us pulse to trigger transmitter
+ ArduinoPins::fastWriteDigital(_transmitPin, 1);
+ delayMicroseconds(10);
+ ArduinoPins::fastWriteDigital(_transmitPin, 0);
+
+ // Wait for receive pin to be set
+ startTime = currentTime = micros();
+ maxTime = factor * _offThreshold * 2;
+ while (!ArduinoPins::fastReadDigital(_receivePin)) {
+ // lastTime = currentTime;
+ currentTime = micros();
+ waitTime = currentTime - startTime;
+ if (waitTime > maxTime) {
+ // Timeout waiting for pulse start, abort the read
+ return _value;
+ }
+ }
+
+ // Wait for receive pin to reset, and measure length of pulse
+ startTime = currentTime = micros();
+ maxTime = factor * _offThreshold;
+ while (ArduinoPins::fastReadDigital(_receivePin)) {
+ 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;
+ }
+ }
+ // 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;
+ }
+
+};
+
+#endif //IO_HCSR04_H
\ No newline at end of file
diff --git a/IO_MCP23008.h b/IO_MCP23008.h
index c04712a..3557b49 100644
--- a/IO_MCP23008.h
+++ b/IO_MCP23008.h
@@ -28,7 +28,6 @@ public:
new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
}
-private:
// Constructor
MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
: GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) {
@@ -38,6 +37,7 @@ private:
outputBuffer[0] = REG_GPIO;
}
+private:
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
}
diff --git a/IO_MCP23017.h b/IO_MCP23017.h
index 2c56ea7..d7c27ce 100644
--- a/IO_MCP23017.h
+++ b/IO_MCP23017.h
@@ -34,7 +34,6 @@ public:
new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
}
-private:
// Constructor
MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
: GPIOBase((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin)
@@ -42,9 +41,9 @@ private:
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_GPIOA;
- _setupDevice();
}
+private:
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
}
diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp
index dd2a607..1c24335 100644
--- a/IO_PCA9685.cpp
+++ b/IO_PCA9685.cpp
@@ -82,6 +82,10 @@ PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
// Initialise structure used for setting pulse rate
requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer));
+}
+
+// Device-specific initialisation
+void PCA9685::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C
// In reality, other devices including the Arduino will limit
@@ -100,10 +104,6 @@ PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
}
}
-// Device-specific initialisation
-void PCA9685::_begin() {
-}
-
// Device-specific write function, invoked from IODevice::write().
void PCA9685::_write(VPIN vpin, int value) {
#ifdef DIAG_IO
@@ -166,7 +166,7 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) {
profile==Bounce ? sizeof(_bounceProfile)-1 :
1;
s->stepNumber = 0;
- s->toPosition = min(value, 4095);
+ s->toPosition = value;
s->fromPosition = s->currentPosition;
}
diff --git a/IO_PCF8574.h b/IO_PCF8574.h
index be9ead7..2a8d363 100644
--- a/IO_PCF8574.h
+++ b/IO_PCF8574.h
@@ -28,13 +28,13 @@ public:
new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
}
-private:
PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
: GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin)
{
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
}
+private:
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode);
@@ -73,7 +73,10 @@ private:
_portInputState = 0xff;
}
- void _setupDevice() override { }
+ // Set up device ports
+ void _setupDevice() override {
+ _writePortModes();
+ }
uint8_t inputBuffer[1];
};
diff --git a/Sensors.cpp b/Sensors.cpp
index 198d8d9..fc8abb5 100644
--- a/Sensors.cpp
+++ b/Sensors.cpp
@@ -91,7 +91,7 @@ void Sensor::checkAll(Print *stream){
#ifdef USE_NOTIFY
// Register the event handler ONCE!
if (!inputChangeCallbackRegistered)
- nextInputChangeCallback = IODevice::registerInputChangeNotification(inputChangeCallback);
+ IONotifyCallback::add(inputChangeCallback);
inputChangeCallbackRegistered = true;
#endif
@@ -192,8 +192,6 @@ void Sensor::inputChangeCallback(VPIN vpin, int state) {
if (tt != NULL) { // Sensor found
tt->inputState = (state != 0);
}
- // Call next registered callback function
- if (nextInputChangeCallback) nextInputChangeCallback(vpin, state);
}
#endif
@@ -345,6 +343,5 @@ unsigned long Sensor::lastReadCycle=0;
Sensor *Sensor::firstPollSensor = NULL;
Sensor *Sensor::lastSensor = NULL;
bool Sensor::pollSignalPhase = false;
-IONotifyStateChangeCallback *Sensor::nextInputChangeCallback = 0;
bool Sensor::inputChangeCallbackRegistered = false;
#endif
\ No newline at end of file
diff --git a/Sensors.h b/Sensors.h
index d6288e0..60e414f 100644
--- a/Sensors.h
+++ b/Sensors.h
@@ -92,7 +92,6 @@ public:
#ifdef USE_NOTIFY
static bool pollSignalPhase;
static void inputChangeCallback(VPIN vpin, int state);
- static IONotifyStateChangeCallback *nextInputChangeCallback;
static bool inputChangeCallbackRegistered;
#endif
diff --git a/Turnouts.cpp b/Turnouts.cpp
index aeebd5b..79a2d65 100644
--- a/Turnouts.cpp
+++ b/Turnouts.cpp
@@ -18,7 +18,7 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see .
*/
-#define EESTOREDEBUG
+//#define EESTOREDEBUG
#include "defines.h"
#include "Turnouts.h"
#include "EEStore.h"
@@ -72,13 +72,11 @@ void Turnout::print(Print *stream){
// VPIN Digital output
StringFormatter::send(stream, F("\n"), data.id, data.vpinData.vpin, state);
break;
-#ifndef IO_NO_HAL
case TURNOUT_SERVO:
// Servo Turnout
StringFormatter::send(stream, F("\n"), data.id, data.servoData.vpin,
data.servoData.activePosition, data.servoData.inactivePosition, data.servoData.profile, state);
break;
-#endif
default:
break;
}
@@ -89,9 +87,6 @@ void Turnout::print(Print *stream){
// Returns false if turnout not found.
bool Turnout::activate(int n, bool state){
-#ifdef EESTOREDEBUG
- DIAG(F("Turnout::activate(%d,%d)"),n,state);
-#endif
Turnout * tt=get(n);
if (!tt) return false;
tt->activate(state);
@@ -136,11 +131,11 @@ void Turnout::activate(bool state) {
DCC::setAccessory((((data.dccAccessoryData.address-1) >> 2) + 1),
((data.dccAccessoryData.address-1) & 3), state);
break;
-#ifndef IO_NO_HAL
case TURNOUT_SERVO:
+#ifndef IO_NO_HAL
IODevice::write(data.servoData.vpin, state);
- break;
#endif
+ break;
case TURNOUT_VPIN:
IODevice::write(data.vpinData.vpin, state);
break;
@@ -205,12 +200,10 @@ void Turnout::load(){
case TURNOUT_LCN:
// LCN turnouts are created when the remote device sends a message.
break;
-#ifndef IO_NO_HAL
case TURNOUT_SERVO:
tt=createServo(data.id, data.servoData.vpin,
data.servoData.activePosition, data.servoData.inactivePosition, data.servoData.profile, lastKnownState);
break;
-#endif
case TURNOUT_VPIN:
tt=createVpin(data.id, data.vpinData.vpin, lastKnownState); // VPIN-based turnout
break;
@@ -294,11 +287,11 @@ Turnout *Turnout::createVpin(int id, VPIN vpin, uint8_t state){
return(tt);
}
-#ifndef IO_NO_HAL
///////////////////////////////////////////////////////////////////////////////
// Method for creating a Servo Turnout, e.g. connected to PCA9685 PWM device.
Turnout *Turnout::createServo(int id, VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile, uint8_t state){
+#ifndef IO_NO_HAL
if (activePosition > 511 || inactivePosition > 511 || profile > 4) return NULL;
Turnout *tt=create(id);
@@ -317,8 +310,11 @@ Turnout *Turnout::createServo(int id, VPIN vpin, uint16_t activePosition, uint16
return NULL;
}
return(tt);
-}
+#else
+ (void)id; (void)vpin; (void)activePosition; (void)inactivePosition; (void)profile; (void)state; // avoid compiler warnings
+ return NULL;
#endif
+}
///////////////////////////////////////////////////////////////////////////////
// Support for
@@ -326,14 +322,12 @@ Turnout *Turnout::createServo(int id, VPIN vpin, uint16_t activePosition, uint16
// and
Turnout *Turnout::create(int id, int params, int16_t p[]) {
-#ifndef IO_NO_HAL
if (p[0] == HASH_KEYWORD_SERVO) { //
if (params == 5)
return createServo(id, (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], (uint8_t)p[4]);
else
return NULL;
} else
-#endif
if (p[0] == HASH_KEYWORD_VPIN) { //
if (params==2)
return createVpin(id, p[1]);
@@ -350,11 +344,9 @@ Turnout *Turnout::create(int id, int params, int16_t p[]) {
} else if (params==2) { // for DCC or LCN
return createDCC(id, p[0], p[1]);
}
-#ifndef IO_NO_HAL
else if (params==3) { // legacy for Servo
return createServo(id, (VPIN)p[0], (uint16_t)p[1], (uint16_t)p[2]);
}
-#endif
return NULL;
}