mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-30 03:26:13 +01:00
a36dccfad0
UserAddin allows a function to be 'plugged in' to the IODevice (HAL) framework and executed cyclically, using just one line of code in the myHal.cpp. This will facilitate functions for displaying CS state on OLEDs and LCDs, among other things.
479 lines
16 KiB
C++
479 lines
16 KiB
C++
/*
|
|
* © 2023, Paul Antoine, Discord user @ADUBOURG
|
|
* © 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"
|
|
#include "inttypes.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,
|
|
CONFIGURE_ANALOGOUTPUT = 4,
|
|
CONFIGURE_ANALOGINPUT = 5,
|
|
} 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();
|
|
|
|
// reset function to invoke all driver's _begin() methods again, to
|
|
// reset the state of the devices and reinitialise.
|
|
static void reset();
|
|
|
|
// configure is used invoke an IODevice instance's _configure method
|
|
static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]);
|
|
|
|
// User-friendly function for configuring an input pin.
|
|
inline static bool configureInput(VPIN vpin, bool pullupEnable) {
|
|
int params[] = {pullupEnable};
|
|
return IODevice::configure(vpin, CONFIGURE_INPUT, 1, params);
|
|
}
|
|
|
|
// User-friendly function for configuring a servo pin.
|
|
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);
|
|
}
|
|
|
|
// 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, 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.
|
|
static bool isBusy(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);
|
|
|
|
// read invokes the IODevice instance's _readAnalogue method.
|
|
static int readAnalogue(VPIN vpin);
|
|
static int configureAnalogIn(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);
|
|
|
|
// Method to check if pins will overlap before creating new device.
|
|
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
|
|
|
|
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
|
|
// pin range.
|
|
IODevice *findDeviceFollowing(VPIN vpin);
|
|
|
|
// 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, uint8_t param1=0, uint16_t param2=0) {
|
|
(void)vpin; (void)value; (void) param1; (void)param2;
|
|
};
|
|
|
|
// Method to read digital pin state (optionally implemented within device class)
|
|
virtual int _read(VPIN vpin) {
|
|
(void)vpin;
|
|
return 0;
|
|
};
|
|
|
|
// Method to read analogue pin state (optionally implemented within device class)
|
|
virtual int _readAnalogue(VPIN vpin) {
|
|
(void)vpin;
|
|
return 0;
|
|
};
|
|
|
|
protected:
|
|
|
|
// Constructor
|
|
IODevice(VPIN firstVpin=0, int nPins=0) {
|
|
_firstVpin = firstVpin;
|
|
_nPins = nPins;
|
|
_nextEntryTime = 0;
|
|
_I2CAddress=0;
|
|
}
|
|
|
|
// Method to perform initialisation of the device (optionally implemented within device class)
|
|
virtual void _begin() {}
|
|
|
|
// Method to check whether the vpin corresponds to this device
|
|
bool owns(VPIN vpin);
|
|
|
|
// 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;
|
|
};
|
|
|
|
virtual int _configureAnalogIn(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) {
|
|
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
|
|
};
|
|
|
|
// Method for displaying info on DIAG output (optionally implemented within device class)
|
|
virtual void _display();
|
|
|
|
// Destructor
|
|
virtual ~IODevice() {};
|
|
|
|
// Non-virtual function
|
|
void delayUntil(unsigned long futureMicrosCount) {
|
|
_nextEntryTime = futureMicrosCount;
|
|
}
|
|
|
|
// Common object fields.
|
|
VPIN _firstVpin;
|
|
int _nPins;
|
|
I2CAddress _I2CAddress;
|
|
// 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;
|
|
|
|
// Static support function for subclass creation
|
|
static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL);
|
|
|
|
// Method to find device handling Vpin
|
|
static IODevice *findDevice(VPIN vpin);
|
|
|
|
// Current state of device
|
|
DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
|
|
|
|
private:
|
|
IODevice *_nextDevice = 0;
|
|
unsigned long _nextEntryTime;
|
|
static IODevice *_firstDevice;
|
|
|
|
static IODevice *_nextLoopDevice;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for PCA9685 16-channel PWM module.
|
|
*/
|
|
|
|
class PCA9685 : public IODevice {
|
|
public:
|
|
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50);
|
|
enum ProfileType : uint8_t {
|
|
Instant = 0, // Moves immediately between positions (if duration not specified)
|
|
UseDuration = 0, // Use specified duration
|
|
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!!
|
|
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
|
|
};
|
|
|
|
private:
|
|
// Constructor
|
|
PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency);
|
|
// 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, uint8_t profile, uint16_t duration) 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);
|
|
void _display() override;
|
|
|
|
|
|
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
|
|
uint16_t stepNumber; // Index of current step (starting from 0)
|
|
uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
|
|
uint8_t currentProfile; // profile being used for current animation.
|
|
uint16_t duration; // time (tenths of a second) for animation to complete.
|
|
}; // 14 bytes per element, i.e. per pin in use
|
|
|
|
struct ServoData *_servoData [16];
|
|
|
|
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
|
|
static const uint8_t FLASH _bounceProfile[30];
|
|
|
|
const unsigned int refreshInterval = 50; // refresh every 50ms
|
|
|
|
// structures for setting up non-blocking writes to servo controller
|
|
I2CRB requestBlock;
|
|
uint8_t outputBuffer[5];
|
|
uint8_t prescaler; // clock prescaler for setting PWM frequency
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for DCC accessory decoder.
|
|
*/
|
|
|
|
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);
|
|
// 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));
|
|
}
|
|
|
|
static void fastWriteDigital(uint8_t pin, uint8_t value);
|
|
static bool fastReadDigital(uint8_t pin);
|
|
|
|
private:
|
|
// Constructor
|
|
ArduinoPins(VPIN firstVpin, int nPins);
|
|
|
|
// 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 functions.
|
|
int _read(VPIN vpin) override;
|
|
int _readAnalogue(VPIN vpin) override;
|
|
int _configureAnalogIn(VPIN vpin) override;
|
|
void _display() override;
|
|
|
|
|
|
uint8_t *_pinPullups;
|
|
uint8_t *_pinModes; // each bit is 1 for output, 0 for input
|
|
uint8_t *_pinInUse;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* IODevice subclass for EX-Turntable.
|
|
*/
|
|
|
|
class EXTurntable : public IODevice {
|
|
public:
|
|
static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
|
|
// Constructor
|
|
EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
|
|
enum ActivityNumber : uint8_t {
|
|
Turn = 0, // Rotate turntable, maintain phase
|
|
Turn_PInvert = 1, // Rotate turntable, invert phase
|
|
Home = 2, // Initiate homing
|
|
Calibrate = 3, // Initiate calibration sequence
|
|
LED_On = 4, // Turn LED on
|
|
LED_Slow = 5, // Set LED to a slow blink
|
|
LED_Fast = 6, // Set LED to a fast blink
|
|
LED_Off = 7, // Turn LED off
|
|
Acc_On = 8, // Turn accessory pin on
|
|
Acc_Off = 9, // Turn accessory pin off
|
|
};
|
|
|
|
private:
|
|
// Device-specific write function.
|
|
void _begin() override;
|
|
void _loop(unsigned long currentMicros) override;
|
|
int _read(VPIN vpin) override;
|
|
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
|
void _display() override;
|
|
uint8_t _stepperStatus;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// IODevice framework for invoking user-written functions.
|
|
// To use, define a function that you want to be regularly
|
|
// invoked, and then create an instance of UserAddin.
|
|
// For example, you can show the status, on screen 3, of the first eight
|
|
// locos in the speed table:
|
|
//
|
|
// void updateLocoScreen() {
|
|
// for (int i=0; i<8; i++) {
|
|
// if (DCC::speedTable[i].loco > 0) {
|
|
// int speed = DCC::speedTable[i].speedCode;
|
|
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
|
|
// speed & 0x7f, speed & 0x80 ? 'R' : 'F');
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// void halSetup() {
|
|
// ...
|
|
// UserAddin(updateLocoScreen, 1000); // Update every 1000ms
|
|
// ...
|
|
// }
|
|
//
|
|
class UserAddin : public IODevice {
|
|
private:
|
|
void (*_invokeUserFunction)();
|
|
int _delay; // milliseconds
|
|
public:
|
|
UserAddin(void (*func)(), int delay) {
|
|
_invokeUserFunction = func;
|
|
_delay = delay;
|
|
addDevice(this);
|
|
}
|
|
// userFunction has no return value, no parameter. delay is in milliseconds.
|
|
static void create(void (*userFunction)(), int delay) {
|
|
new UserAddin(userFunction, delay);
|
|
}
|
|
protected:
|
|
void _begin() { _display(); }
|
|
void _loop(unsigned long currentMicros) override {
|
|
_invokeUserFunction();
|
|
// _loop won't be called again until _delay ms have elapsed.
|
|
delayUntil(currentMicros + _delay * 1000UL);
|
|
}
|
|
void _display() override {
|
|
DIAG(F("UserAddin run every %dms"), _delay);
|
|
}
|
|
};
|
|
|
|
#include "IO_MCP23008.h"
|
|
#include "IO_MCP23017.h"
|
|
#include "IO_PCF8574.h"
|
|
#include "IO_PCF8575.h"
|
|
#include "IO_duinoNodes.h"
|
|
#include "IO_EXIOExpander.h"
|
|
|
|
|
|
#endif // iodevice_h
|