1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-23 08:06:13 +01:00

HAL writeAnalogue function change.

IODevice::writeAnalogue() has an additional optional parameter "duration", specifying the time taken for the animation in units of 100ms (max 3276 seconds, or about 54 minutes).
This commit is contained in:
Neil McKechnie 2021-08-27 15:42:47 +01:00
parent 1dd574dc03
commit 7e601c38c4
3 changed files with 59 additions and 51 deletions

View File

@ -144,11 +144,12 @@ void IODevice::write(VPIN vpin, int value) {
} }
// Write analogue value to virtual pin(s). If multiple devices are allocated the same pin // Write analogue value to virtual pin(s). If multiple devices are allocated the same pin
// then only the first one found will be used. // then only the first one found will be used. Duration is the time that the
void IODevice::writeAnalogue(VPIN vpin, int value, int profile) { // 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) {
IODevice *dev = findDevice(vpin); IODevice *dev = findDevice(vpin);
if (dev) { if (dev) {
dev->_writeAnalogue(vpin, value, profile); dev->_writeAnalogue(vpin, value, profile, duration);
return; return;
} }
#ifdef DIAG_IO #ifdef DIAG_IO
@ -246,24 +247,13 @@ int IODevice::read(VPIN vpin) {
// Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more. // Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more.
void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); } void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { bool IODevice::configure(VPIN, ConfigTypeEnum, int, int []) { return true; }
(void)vpin; (void)paramCount; (void)params; // Avoid compiler warnings
if (configType == CONFIGURE_INPUT || configType == CONFIGURE_OUTPUT)
return true;
else
return false;
}
void IODevice::write(VPIN vpin, int value) { void IODevice::write(VPIN vpin, int value) {
digitalWrite(vpin, value); digitalWrite(vpin, value);
pinMode(vpin, OUTPUT); pinMode(vpin, OUTPUT);
} }
void IODevice::writeAnalogue(VPIN vpin, int value, int profile) { void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {}
(void)vpin; (void)value; (void)profile; // Avoid compiler warnings bool IODevice::hasCallback(VPIN) { return false; }
}
bool IODevice::hasCallback(VPIN vpin) {
(void)vpin; // Avoid compiler warnings
return false;
}
int IODevice::read(VPIN vpin) { int IODevice::read(VPIN vpin) {
pinMode(vpin, INPUT_PULLUP); pinMode(vpin, INPUT_PULLUP);
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1) return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
@ -272,10 +262,8 @@ void IODevice::loop() {}
void IODevice::DumpAll() { void IODevice::DumpAll() {
DIAG(F("NO HAL CONFIGURED!")); DIAG(F("NO HAL CONFIGURED!"));
} }
bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < 49); } bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < NUM_DIGITAL_PINS); }
void IODevice::setGPIOInterruptPin(int16_t pinNumber) { void IODevice::setGPIOInterruptPin(int16_t) {}
(void) pinNumber; // Avoid compiler warning
}
// Chain of callback blocks (identifying registered callback functions for state changes) // Chain of callback blocks (identifying registered callback functions for state changes)
// Not used in IO_NO_HAL but must be declared. // Not used in IO_NO_HAL but must be declared.

View File

@ -42,6 +42,7 @@
#include "DIAG.h" #include "DIAG.h"
#include "FSH.h" #include "FSH.h"
#include "I2CManager.h" #include "I2CManager.h"
#include "inttypes.h"
typedef uint16_t VPIN; typedef uint16_t VPIN;
// Limit VPIN number to max 32767. Above this number, printing often gives negative values. // Limit VPIN number to max 32767. Above this number, printing often gives negative values.
@ -128,7 +129,7 @@ public:
static void write(VPIN vpin, int value); static void write(VPIN vpin, int value);
// write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs) // write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs)
static void writeAnalogue(VPIN vpin, int value, int profile); static void writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration=0);
// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing // 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. // the output over a period of time.
@ -174,8 +175,8 @@ protected:
}; };
// Method to write an 'analogue' value (optionally implemented within device class) // Method to write an 'analogue' value (optionally implemented within device class)
virtual void _writeAnalogue(VPIN vpin, int value, int profile) { virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
(void)vpin; (void)value; (void) profile; (void)vpin; (void)value; (void) profile; (void)duration;
}; };
// Function called to check whether callback notification is supported by this pin. // Function called to check whether callback notification is supported by this pin.
@ -249,12 +250,14 @@ public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress); static void create(VPIN vpin, int nPins, uint8_t I2CAddress);
// Constructor // Constructor
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress); PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
enum ProfileType { enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end Slow = 3, // 2 seconds end-to-end
Bounce = 4 // For semaphores/turnouts with a bit of bounce!! Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
}; };
private: private:
@ -263,7 +266,7 @@ private:
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
// Device-specific write functions. // Device-specific write functions.
void _write(VPIN vpin, int value) override; void _write(VPIN vpin, int value) override;
void _writeAnalogue(VPIN vpin, int value, int profile) override; void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override;
bool _isBusy(VPIN vpin) override; bool _isBusy(VPIN vpin) override;
void _loop(unsigned long currentMicros) override; void _loop(unsigned long currentMicros) override;
void updatePosition(uint8_t pin); void updatePosition(uint8_t pin);
@ -279,10 +282,10 @@ private:
uint16_t fromPosition : 12; uint16_t fromPosition : 12;
uint16_t toPosition : 12; uint16_t toPosition : 12;
uint8_t profile; // Config parameter uint8_t profile; // Config parameter
uint8_t stepNumber; // Index of current step (starting from 0) uint16_t stepNumber; // Index of current step (starting from 0)
uint8_t numSteps; // Number of steps in animation, or 0 if none in progress. uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
uint8_t currentProfile; // profile being used for current animation. uint8_t currentProfile; // profile being used for current animation.
}; // 12 bytes per element, i.e. per pin in use }; // 14 bytes per element, i.e. per pin in use
struct ServoData *_servoData [16]; struct ServoData *_servoData [16];

View File

@ -65,7 +65,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
int state = params[3]; int state = params[3];
if (state != -1) { if (state != -1) {
// Position servo to initial state // Position servo to initial state
_writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, Instant); _writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, 0, 0);
} }
return true; return true;
@ -96,7 +96,6 @@ void PCA9685::_begin() {
// Initialise I/O module here. // Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
DIAG(F("PCA9685 I2C:%x configured Vpins:%d-%d"), _I2CAddress, _firstVpin, _firstVpin+_nPins-1);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period.
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
@ -104,10 +103,14 @@ void PCA9685::_begin() {
// In theory, we should wait 500us before sending any other commands to each device, to allow // In theory, we should wait 500us before sending any other commands to each device, to allow
// the PWM oscillator to get running. However, we don't do any specific wait, as there's // the PWM oscillator to get running. However, we don't do any specific wait, as there's
// plenty of other stuff to do before we will send a command. // plenty of other stuff to do before we will send a command.
#if defined(DIAG_IO)
_display();
#endif
} }
} }
// Device-specific write function, invoked from IODevice::write(). // Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) { void PCA9685::_write(VPIN vpin, int value) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
@ -121,14 +124,24 @@ void PCA9685::_write(VPIN vpin, int value) {
writeDevice(pin, value ? _defaultActivePosition : _defaultInactivePosition); writeDevice(pin, value ? _defaultActivePosition : _defaultInactivePosition);
} else { } else {
// Use configured parameters for advanced transitions // Use configured parameters for advanced transitions
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile); _writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, 0);
} }
} }
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) { // Profile is as follows:
// Bit 7: 0=Set PWM to 0% to power off servo motor when finished
// 1=Keep PWM pulses on (better when using PWM to drive an LED)
// Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds)
// 1 (Fast) Move servo in 0.5 seconds
// 2 (Medium) Move servo in 1.0 seconds
// 3 (Slow) Move servo in 2.0 seconds
// 4 (Bounce) Servo 'bounces' at extremes.
//
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d"), vpin, value, profile); DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
vpin, value, profile, duration);
#endif #endif
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
if (value > 4095) value = 4095; if (value > 4095) value = 4095;
@ -142,30 +155,32 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) {
s->activePosition = _defaultActivePosition; s->activePosition = _defaultActivePosition;
s->inactivePosition = _defaultInactivePosition; s->inactivePosition = _defaultInactivePosition;
s->currentPosition = value; s->currentPosition = value;
s->profile = Instant; s->profile = Instant; // Use instant profile (but not this time)
} }
// Animated profile. Initiate the appropriate action. // Animated profile. Initiate the appropriate action.
s->currentProfile = profile; s->currentProfile = profile;
s->numSteps = profile==Fast ? 10 : uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
profile==Medium ? 20 : s->numSteps = profileValue==Instant ? 1 :
profile==Slow ? 40 : profileValue==Fast ? 10 : // 0.5 seconds
profile==Bounce ? sizeof(_bounceProfile)-1 : profileValue==Medium ? 20 : // 1.0 seconds
1; profileValue==Slow ? 40 : // 2.0 seconds
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
duration * 2; // Convert from deciseconds (100ms) to refresh cycles (50ms)
s->stepNumber = 0; s->stepNumber = 0;
s->toPosition = value; s->toPosition = value;
s->fromPosition = s->currentPosition; s->fromPosition = s->currentPosition;
} }
// _isActive returns true if the device is currently in executing an animation, // _isBusy returns true if the device is currently in executing an animation,
// changing the output over a period of time. // changing the output over a period of time.
bool PCA9685::_isActive(VPIN vpin) { bool PCA9685::_isBusy(VPIN vpin) {
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin]; struct ServoData *s = _servoData[pin];
if (s == NULL) if (s == NULL)
return false; // No structure means no animation! return false; // No structure means no animation!
else else
return (s->numSteps != 0); return (s->stepNumber < s->numSteps);
} }
void PCA9685::_loop(unsigned long currentMicros) { void PCA9685::_loop(unsigned long currentMicros) {
@ -194,7 +209,7 @@ void PCA9685::updatePosition(uint8_t pin) {
if (s->stepNumber < s->numSteps) { if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo // Animation in progress, reposition servo
s->stepNumber++; s->stepNumber++;
if (s->currentProfile == Bounce) { if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash // Retrieve step positions from array in flash
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
@ -208,10 +223,12 @@ void PCA9685::updatePosition(uint8_t pin) {
// We've finished animation, wait a little to allow servo to catch up // We've finished animation, wait a little to allow servo to catch up
s->stepNumber++; s->stepNumber++;
} else if (s->stepNumber == s->numSteps + _catchupSteps } else if (s->stepNumber == s->numSteps + _catchupSteps
&& s->currentPosition != 4095 && s->currentPosition != 0) { && s->currentPosition != 0) {
#ifdef IO_SWITCH_OFF_SERVO #ifdef IO_SWITCH_OFF_SERVO
// Wait has finished, so switch off PWM to prevent annoying servo buzz if ((s->currentProfile & NoPowerOff) == 0) {
writeDevice(pin, 0); // Wait has finished, so switch off PWM to prevent annoying servo buzz
writeDevice(pin, 0);
}
#endif #endif
s->numSteps = 0; // Done now. s->numSteps = 0; // Done now.
} }
@ -236,7 +253,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
// Display details of this device. // Display details of this device.
void PCA9685::_display() { void PCA9685::_display() {
DIAG(F("PCA9685 I2C:x%x Vpins:%d-%d"), _I2CAddress, (int)_firstVpin, DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d"), _I2CAddress, (int)_firstVpin,
(int)_firstVpin+_nPins-1); (int)_firstVpin+_nPins-1);
} }