mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 23:56:13 +01:00
9dacd24d27
* Add <D SERVO vpin position> command Allow a PWM servo to be driven to any arbitrary position. * Enhancements for HAL drivers Add state change notification for external GPIO module drivers; Allow drivers to be installed statically by declaration (as an alternative to the 'create' call). * Create IO_HCSR04.h HAL driver for HC-SR04 ultrasonic distance sensor (sonar). * Enable servo commands in NO-HAL mode, but return error. Avoid compile errors in RMFT.cpp when compiled with basic HAL by including the Turnout::createServo function as a stub that returns NULL. * Update IO_HCSR04.h Minor changes * Change <D SERVO> Give the <D SERVO> command an optional parameter of the profile. For example, <D SERVO 100 200 3> will slowly move the servo on pin 100 to PWM position corresponding to 200. If omitted, the servo will move immediately (no animation). * IODevice (HAL) changes 1) Put new devices on the end of the chain instead of the beginning. This will give better performance for devices created first (ArduinoPins and extender GPIO devices, typically). 2) Remove unused functions. * Update IO_HCSR04.h Allow thresholds for ON and OFF to be separately configured at creation. * Update IODevice.cpp Fix compile error on IO_NO_HAL minimal HAL version. * Update IO_PCA9685.cpp Remove unnecessary duplicated call to min() function.
353 lines
12 KiB
C++
353 lines
12 KiB
C++
/*
|
|
* © 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 iodevice_h
|
|
#define iodevice_h
|
|
|
|
// Define symbol DIAG_IO to enable diagnostic output
|
|
//#define DIAG_IO Y
|
|
|
|
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
|
|
//#define DIAG_LOOPTIMES
|
|
|
|
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
|
|
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
|
|
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
|
#define IO_NO_HAL
|
|
#endif
|
|
|
|
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
|
|
// animation has completed. This switches off the servo motor, preventing
|
|
// the continuous buzz sometimes found on servos, and reducing the
|
|
// power consumption of the servo when inactive.
|
|
// It is recommended to enable this, unless it causes you problems.
|
|
#define IO_SWITCH_OFF_SERVO
|
|
|
|
#include "DIAG.h"
|
|
#include "FSH.h"
|
|
#include "I2CManager.h"
|
|
|
|
typedef uint16_t VPIN;
|
|
// Limit VPIN number to max 32767. Above this number, printing often gives negative values.
|
|
// This should be enough for 99% of users.
|
|
#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.
|
|
*/
|
|
|
|
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
|
|
*
|
|
* This class is the basis of the Hardware Abstraction Layer (HAL) for
|
|
* the DCC++EX Command Station. All device classes derive from this.
|
|
*
|
|
*/
|
|
|
|
class IODevice {
|
|
public:
|
|
|
|
// Parameter values to identify type of call to IODevice::configure.
|
|
typedef enum : uint8_t {
|
|
CONFIGURE_INPUT = 1,
|
|
CONFIGURE_SERVO = 2,
|
|
CONFIGURE_OUTPUT = 3,
|
|
} ConfigTypeEnum;
|
|
|
|
typedef enum : uint8_t {
|
|
DEVSTATE_DORMANT = 0,
|
|
DEVSTATE_PROBING = 1,
|
|
DEVSTATE_INITIALISING = 2,
|
|
DEVSTATE_NORMAL = 3,
|
|
DEVSTATE_SCANNING = 4,
|
|
DEVSTATE_FAILED = 5,
|
|
} DeviceStateEnum;
|
|
|
|
// Static functions to find the device and invoke its member functions
|
|
|
|
// 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
|
|
static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]);
|
|
|
|
// write invokes the IODevice instance's _write method.
|
|
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, int profile);
|
|
|
|
// isActive returns true if the device is currently in an animation of some sort, e.g. is changing
|
|
// the output over a period of time.
|
|
static bool isActive(VPIN vpin);
|
|
|
|
// check whether the pin supports notification. If so, then regular _read calls are not required.
|
|
static bool hasCallback(VPIN vpin);
|
|
|
|
// read invokes the IODevice instance's _read method.
|
|
static int read(VPIN vpin);
|
|
|
|
// loop invokes the IODevice instance's _loop method.
|
|
static void loop();
|
|
|
|
static void DumpAll();
|
|
|
|
// exists checks whether there is a device owning the specified vpin
|
|
static bool exists(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
|
|
// GPIO extender pins. If a shared interrupt pin is configured, then input states are scanned
|
|
// only when the shared interrupt pin is pulled low. The external GPIO module releases the pin
|
|
// once the GPIO port concerned has been read.
|
|
void setGPIOInterruptPin(int16_t pinNumber);
|
|
|
|
|
|
protected:
|
|
|
|
// Method to perform initialisation of the device (optionally implemented within device class)
|
|
virtual void _begin() {}
|
|
|
|
// Method to configure device (optionally implemented within device class)
|
|
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
|
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
|
|
return false;
|
|
};
|
|
|
|
// Method to write new state (optionally implemented within device class)
|
|
virtual void _write(VPIN vpin, int value) {
|
|
(void)vpin; (void)value;
|
|
};
|
|
|
|
// Method to write an analogue value (optionally implemented within device class)
|
|
virtual void _writeAnalogue(VPIN vpin, int value, int profile) {
|
|
(void)vpin; (void)value; (void) profile;
|
|
};
|
|
|
|
// Method called from within a filter device to trigger its output (which may
|
|
// have the same VPIN id as the input to the filter). It works through the
|
|
// later devices in the chain only.
|
|
void writeDownstream(VPIN vpin, int value);
|
|
|
|
// 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)
|
|
virtual int _read(VPIN vpin) {
|
|
(void)vpin;
|
|
return 0;
|
|
};
|
|
|
|
// _isActive 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 _isActive(VPIN vpin) {
|
|
(void)vpin;
|
|
return false;
|
|
}
|
|
|
|
// Method to perform updates on an ongoing basis (optionally implemented within device class)
|
|
virtual void _loop(unsigned long currentMicros) {
|
|
(void)currentMicros; // Suppress compiler warning.
|
|
};
|
|
|
|
// Method for displaying info on DIAG output (optionally implemented within device class)
|
|
virtual void _display();
|
|
|
|
// Destructor
|
|
virtual ~IODevice() {};
|
|
|
|
// Common object fields.
|
|
VPIN _firstVpin;
|
|
int _nPins;
|
|
|
|
// 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);
|
|
|
|
// Current state of device
|
|
DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
|
|
|
|
private:
|
|
// Method to check whether the vpin corresponds to this device
|
|
bool owns(VPIN vpin);
|
|
// Method to find device handling Vpin
|
|
static IODevice *findDevice(VPIN vpin);
|
|
|
|
IODevice *_nextDevice = 0;
|
|
static IODevice *_firstDevice;
|
|
|
|
static IODevice *_nextLoopDevice;
|
|
static bool _initPhase;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for PCA9685 16-channel PWM module.
|
|
*/
|
|
|
|
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
|
|
Medium = 2, // 1 second end-to-end
|
|
Slow = 3, // 2 seconds end-to-end
|
|
Bounce = 4 // For semaphores/turnouts with a bit of bounce!!
|
|
};
|
|
|
|
private:
|
|
// Device-specific initialisation
|
|
void _begin() override;
|
|
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
|
// Device-specific write functions.
|
|
void _write(VPIN vpin, int value) override;
|
|
void _writeAnalogue(VPIN vpin, int value, int profile) override;
|
|
bool _isActive(VPIN vpin) override;
|
|
void _loop(unsigned long currentMicros) override;
|
|
void updatePosition(uint8_t pin);
|
|
void writeDevice(uint8_t pin, int value);
|
|
void _display() override;
|
|
|
|
uint8_t _I2CAddress; // 0x40-0x43 possible
|
|
|
|
struct ServoData {
|
|
uint16_t activePosition : 12; // Config parameter
|
|
uint16_t inactivePosition : 12; // Config parameter
|
|
uint16_t currentPosition : 12;
|
|
uint16_t fromPosition : 12;
|
|
uint16_t toPosition : 12;
|
|
uint8_t profile; // Config parameter
|
|
uint8_t stepNumber; // Index of current step (starting from 0)
|
|
uint8_t numSteps; // Number of steps in animation, or 0 if none in progress.
|
|
int8_t state;
|
|
}; // 12 bytes per element, i.e. per pin in use
|
|
|
|
struct ServoData *_servoData [16];
|
|
|
|
static const uint16_t _defaultActivePosition = 410;
|
|
static const uint16_t _defaultInactivePosition = 205;
|
|
|
|
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
|
|
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;
|
|
uint8_t outputBuffer[5];
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for DCC accessory decoder.
|
|
*/
|
|
|
|
class DCCAccessoryDecoder: public IODevice {
|
|
public:
|
|
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
|
// 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;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for arduino input/output pins.
|
|
*/
|
|
|
|
class ArduinoPins: public IODevice {
|
|
public:
|
|
static void create(VPIN firstVpin, int nPins) {
|
|
addDevice(new ArduinoPins(firstVpin, nPins));
|
|
}
|
|
|
|
// 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;
|
|
// Device-specific write function.
|
|
void _write(VPIN vpin, int value) override;
|
|
// Device-specific read function.
|
|
int _read(VPIN vpin) override;
|
|
void _display() override;
|
|
|
|
|
|
uint8_t *_pinPullups;
|
|
uint8_t *_pinModes; // each bit is 1 for output, 0 for input
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "IO_MCP23008.h"
|
|
#include "IO_MCP23017.h"
|
|
#include "IO_PCF8574.h"
|
|
|
|
#endif // iodevice_h
|