1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-22 23:56: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
// then only the first one found will be used.
void IODevice::writeAnalogue(VPIN vpin, int value, int profile) {
// 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) {
IODevice *dev = findDevice(vpin);
if (dev) {
dev->_writeAnalogue(vpin, value, profile);
dev->_writeAnalogue(vpin, value, profile, duration);
return;
}
#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.
void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
(void)vpin; (void)paramCount; (void)params; // Avoid compiler warnings
if (configType == CONFIGURE_INPUT || configType == CONFIGURE_OUTPUT)
return true;
else
return false;
}
bool IODevice::configure(VPIN, ConfigTypeEnum, int, int []) { return true; }
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;
}
void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {}
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)
@ -272,10 +262,8 @@ void IODevice::loop() {}
void IODevice::DumpAll() {
DIAG(F("NO HAL CONFIGURED!"));
}
bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < 49); }
void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
(void) pinNumber; // Avoid compiler warning
}
bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < NUM_DIGITAL_PINS); }
void IODevice::setGPIOInterruptPin(int16_t) {}
// Chain of callback blocks (identifying registered callback functions for state changes)
// Not used in IO_NO_HAL but must be declared.

View File

@ -42,6 +42,7 @@
#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.
@ -128,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, 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
// the output over a period of time.
@ -174,8 +175,8 @@ protected:
};
// 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;
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
(void)vpin; (void)value; (void) profile; (void)duration;
};
// 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);
// Constructor
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
enum ProfileType {
Instant = 0, // Moves immediately between positions
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!!
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
private:
@ -263,7 +266,7 @@ private:
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;
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override;
bool _isBusy(VPIN vpin) override;
void _loop(unsigned long currentMicros) override;
void updatePosition(uint8_t pin);
@ -279,10 +282,10 @@ private:
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.
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.
}; // 12 bytes per element, i.e. per pin in use
}; // 14 bytes per element, i.e. per pin in use
struct ServoData *_servoData [16];

View File

@ -65,7 +65,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
int state = params[3];
if (state != -1) {
// Position servo to initial state
_writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, Instant);
_writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, 0, 0);
}
return true;
@ -96,7 +96,6 @@ void PCA9685::_begin() {
// Initialise I/O module here.
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_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period.
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
// 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.
#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) {
#ifdef DIAG_IO
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);
} else {
// 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().
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
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
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
@ -142,30 +155,32 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) {
s->activePosition = _defaultActivePosition;
s->inactivePosition = _defaultInactivePosition;
s->currentPosition = value;
s->profile = Instant;
s->profile = Instant; // Use instant profile (but not this time)
}
// Animated profile. Initiate the appropriate action.
s->currentProfile = profile;
s->numSteps = profile==Fast ? 10 :
profile==Medium ? 20 :
profile==Slow ? 40 :
profile==Bounce ? sizeof(_bounceProfile)-1 :
1;
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
s->numSteps = profileValue==Instant ? 1 :
profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
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->toPosition = value;
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.
bool PCA9685::_isActive(VPIN vpin) {
bool PCA9685::_isBusy(VPIN vpin) {
int pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL)
return false; // No structure means no animation!
else
return (s->numSteps != 0);
return (s->stepNumber < s->numSteps);
}
void PCA9685::_loop(unsigned long currentMicros) {
@ -194,7 +209,7 @@ void PCA9685::updatePosition(uint8_t pin) {
if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo
s->stepNumber++;
if (s->currentProfile == Bounce) {
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
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
s->stepNumber++;
} else if (s->stepNumber == s->numSteps + _catchupSteps
&& s->currentPosition != 4095 && s->currentPosition != 0) {
&& s->currentPosition != 0) {
#ifdef IO_SWITCH_OFF_SERVO
// Wait has finished, so switch off PWM to prevent annoying servo buzz
writeDevice(pin, 0);
if ((s->currentProfile & NoPowerOff) == 0) {
// Wait has finished, so switch off PWM to prevent annoying servo buzz
writeDevice(pin, 0);
}
#endif
s->numSteps = 0; // Done now.
}
@ -236,7 +253,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
// Display details of this device.
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);
}