diff --git a/IODevice.cpp b/IODevice.cpp index 3f33e57..408162d 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -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. diff --git a/IODevice.h b/IODevice.h index d54b6fa..673a9d2 100644 --- a/IODevice.h +++ b/IODevice.h @@ -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]; diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index a3ab48c..1b7436d 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -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); }