diff --git a/library.properties b/library.properties index 4d17a02..f4868ce 100644 --- a/library.properties +++ b/library.properties @@ -6,4 +6,4 @@ sentence=Allows Arduino boards to control a variety of servo motors. paragraph=This library can control a great number of servos.
It makes careful use of timers: the library can control 12 servos using only 1 timer.
On the Arduino Due you can control up to 60 servos. category=Device Control url=https://www.arduino.cc/reference/en/libraries/servo/ -architectures=avr,megaavr,sam,samd,nrf52,stm32f4,mbed,mbed_nano,mbed_portenta,mbed_rp2040,renesas,renesas_portenta,renesas_uno +architectures=avr,megaavr,sam,samd,nrf52,stm32f4,mbed,mbed_nano,mbed_portenta,mbed_rp2040,renesas,renesas_portenta,renesas_uno,psoc6 diff --git a/src/Servo.h b/src/Servo.h index ed6f56a..4ac7857 100644 --- a/src/Servo.h +++ b/src/Servo.h @@ -79,8 +79,10 @@ #include "xmc/ServoTimers.h" #elif defined(ARDUINO_ARCH_ESP32) #include "esp32/ServoTimers.h" +#elif defined(ARDUINO_ARCH_PSOC6) +#include "psoc6/ServoTimers.h" #else -#error "This library only supports boards with an AVR, SAM, SAMD, NRF52, STM32F4, Renesas, XMC or ESP32 processor." +#error "This library only supports boards with an AVR, SAM, SAMD, NRF52, STM32F4, Renesas, XMC, ESP32 or PSOC6 processor." #endif #define Servo_VERSION 2 // software version of this library @@ -95,7 +97,7 @@ #define INVALID_SERVO 255 // flag indicating an invalid servo index -#if !defined(ARDUINO_ARCH_STM32F4) && !defined(ARDUINO_ARCH_XMC) +#if !defined(ARDUINO_ARCH_STM32F4) && !defined(ARDUINO_ARCH_XMC) &&!defined(ARDUINO_ARCH_PSOC6) typedef struct { uint8_t nbr :6 ; // a pin number from 0 to 63 diff --git a/src/psoc6/Servo.cpp b/src/psoc6/Servo.cpp new file mode 100644 index 0000000..41b2a2f --- /dev/null +++ b/src/psoc6/Servo.cpp @@ -0,0 +1,180 @@ +/****************************************************************************** + * The MIT License + * + * Copyright (c) 2010, LeafLabs, LLC. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *****************************************************************************/ + +#if defined(ARDUINO_ARCH_PSOC6) + +#include "ServoTimers.h" + +uint8_t _ServoCount = 1; // internal counter to check if max numbers of servos is reached +static uint8_t _allowed[MAX_PWM_SERVOS] = ALLOWED_PINS; // internal array to check allowed pwm pins +static uint8_t _servos[MAX_PWM_SERVOS]; // static array of used servo pins for checking + + +/** + * @brief None blocking wait loop. + * + * @param uS microseconds to wait + */ +static void _delayUs(unsigned long uS) +{ + unsigned long time_now = micros(); + while (micros() < time_now + uS) + ; +} + +Servo::Servo() +{ + if(_ServoCount <= MAX_PWM_SERVOS) + { + this->servoIndex = _ServoCount++; + + // Initialize the servo with default values + this->_minAngle = MIN_ANGLE; + this->_maxAngle = MAX_ANGLE; + this->_minPW = MIN_PULSE_WIDTH; + this->_maxPW = MAX_PULSE_WIDTH; + this->_pin = 0; + this->_isActive = false; + this->_pwm = 0; + this->_deg = 0.0; + + for(int i = 0; i < MAX_PWM_SERVOS; i++) + { + _servos[i] = 0xFF; // Initialize all servo pins to unused state + } + } + else + { + this->servoIndex = INVALID_SERVO; // No more servos can be attached + } +} + +uint8_t Servo::attach(uint8_t pin, uint16_t min, uint16_t max) +{ + if(this->servoIndex <= MAX_PWM_SERVOS) + { + // Validate the pin + bool pin_allowed = false; + for(int i = 0; i < MAX_PWM_SERVOS; i++) + { + // Check if pin already in use + if(_servos[i] == pin) + { + return INVALID_SERVO; + } + // Check if selected pin has a PWM unit on the used PSOC6 board + if(_allowed[i] == pin) + pin_allowed = true; + } + + // Return if pin is not found in allowed pin list + if(!pin_allowed) + return INVALID_SERVO; + + // Set min/max values according to input and check for absolute limits + if(min < MIN_PULSE_CHECK) + { + this->_minAngle = constrain(min, MIN_ANGLE, MAX_ANGLE); + this->_minPW = MIN_PULSE_WIDTH; + } + else + { + this->_minAngle = MIN_ANGLE; // TODO: has to be calculated + this->_minPW = constrain(min, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); + } + + if(max < MIN_PULSE_CHECK) + { + this->_maxAngle = constrain(max, MIN_ANGLE, MAX_ANGLE); + this->_maxPW = 2 * MAX_PULSE_WIDTH; + } + else + { + this->_maxAngle = MAX_ANGLE; // TODO: has to be calculated + this->_maxPW = constrain(max, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); + } + + this->_pin = pin; + this->_isActive = true; + _servos[this->servoIndex - 1] = pin; // Mark the pin as used + analogWriteResolution(RESOLUTION); + setAnalogWriteFrequency(this->_pin, REFRESH_FREQUENCY); // Set the default frequency for the pin + + } + return this->servoIndex; +} + +void Servo::detach() +{ + this->servoIndex = _ServoCount--; + + this->_minAngle = MIN_ANGLE; + this->_maxAngle = MAX_ANGLE; + this->_minPW = MIN_PULSE_WIDTH; + this->_maxPW = MAX_PULSE_WIDTH; + + this->_pin = 0; + this->_isActive = false; + this->_pwm = 0; + this->_deg = 0.0; + _servos[this->servoIndex - 1] = 0xFF; // Mark the pin as unused +} + +void Servo::write(int value) +{ + if (value < MIN_PULSE_CHECK) + { + // angle must be inside the boundaries + double angle = constrain(value, this->_minAngle, this->_maxAngle); + double dutyCycle = ( 0.5 + ( angle / MAX_ANGLE ) * 2.0 ) * DUTYCYCLE_STEPS; + + this->_deg = angle; + this->_pwm = uint16_t(dutyCycle); + + analogWrite(this->_pin, uint16_t(dutyCycle)); + _delayUs(50); + } else { + writeMicroseconds(value); + } +} + +void Servo::writeMicroseconds(int value) +{ + // value must be inside the boundaries + double pw = constrain(value,this->_minPW, this->_maxPW); + double dutyCycle = map(pw, MIN_PULSE_WIDTH,MAX_PULSE_WIDTH, 0.5 * DUTYCYCLE_STEPS, 2.5 * DUTYCYCLE_STEPS); + + this->_deg = ( dutyCycle - DUTYCYCLE_STEPS * 0.5 ) * MAX_ANGLE / ( 2 * DUTYCYCLE_STEPS ); + this->_pwm = uint16_t(dutyCycle); + analogWrite(this->_pin, uint16_t(dutyCycle)); + + _delayUs(50); +} +#endif + + + + diff --git a/src/psoc6/ServoTimers.h b/src/psoc6/ServoTimers.h new file mode 100644 index 0000000..5bbb99e --- /dev/null +++ b/src/psoc6/ServoTimers.h @@ -0,0 +1,169 @@ +/****************************************************************************** + * The MIT License + * + * Copyright (c) 2010, LeafLabs, LLC. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *****************************************************************************/ + +/* + * @copyright Copyright (c) 2025-2026 Infineon Technologies AG + */ + +#ifndef __SERVO_TIMERS_H__ +#define __SERVO_TIMERS_H__ + +#include + +#define MAX_PWM_SERVOS 28 +#define ALLOWED_PINS {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30} + + +#define MIN_ANGLE 0 // the minimal angle in degree +#define MAX_ANGLE 180 // the maximal angle in degree +#define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo in microseconds +#define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo in microseconds + +#define MIN_PULSE_CHECK 500 // border with below = angle and above = pulse width +#define REFRESH_FREQUENCY 50u // the refresh frequency on analog pins +#define REFRESH_TIME 20.0 // the PWM refresh frequency for the servo motor +#define DUTYCYCLE_STEPS 65536.0 / REFRESH_TIME // the number of duty cycle steps during one refresh period +#define RESOLUTION 16 // the resolution of the adc during analog write + +#define INVALID_SERVO 255 // flag indicating an invalid servo index + +/** Class for interfacing with RC servomotors. */ +class Servo +{ +public: + /** + * @brief Construct a new Servo instance. + * + * The new instance will not be attached to any pin, but only PWM capable pins will run. + * see pin list above. + */ + Servo(); + + /** + * @brief Associate this instance with a servomotor whose input is + * connected to pin. + * + * If this instance is already attached to a pin, it will be + * detached before being attached to the new pin. + * If the pin is not allowed for running PWM or the max number of + * PWM channels on the PSOC6 board is reached it will return + * with an INVALID_SERVO, otherwise with the servoIndex number. + * + * @param pin Pin connected to the servo pulse wave input. This + * pin must be capable of PWM output. + * + * @param min If this value is below MIN_PULSE_CHECK it will be associated + * with an angle in degree. Otherwise it will be the minimum + * pulse width. + * min as an angle must be between MIN_ANGLE < angle < MAX_ANGLE + * with default as MIN_ANGLE + * min as a pulse width must be between MIN_PULSE_WIDTH < pwm < MAX_PULSE_WIDTH + * with a default as MIN_PULSE_WIDTH + * + * @param max If this value is below MIN_PULSE_CHECK it will be associated + * with an angle in degree. Otherwise it will be the maximum + * pulse width. + * max as an angle must be between MIN_ANGLE < angle < MAX_ANGLE + * with default as MAX_ANGLE + * max as a pulse width must be between MIN_PULSE_WIDTH < pwm < MAX_PULSE_WIDTH + * with a default as MAX_PULSE_WIDTH + * + * @return servoIndex number or INVALID_SERVO = 255 in case of an error + */ + uint8_t attach(uint8_t pin, uint16_t min = MIN_ANGLE, uint16_t max = MAX_ANGLE); + + + /** + * @brief Stop driving the servo pulse train. + * + * If not currently attached to a motor, this function has no effect. + * + * @return true if this call did anything, false otherwise. + */ + void detach(); + + /** + * @brief Set the servomotor target angle by recalculating the duty cycle + * for PSOC6 PWM settings. + * + * @param value Target angle, in degrees. If the target angle is + * outside the range specified at attach(), it + * will be clamped to lie in that range. + * + * @see Servo::attach() + */ + void write(int value); + + /** + * @brief Set the pulse width, in microseconds by recalculating it for the + * PSOC6 PWM settings. It also calculates the angle from the pwm value. + * + * @param value Pulse width to send to the servomotor, in + * microseconds. If outside of the range + * specified at attach() time, it is clamped to + * lie in that range. + * + * @see Servo::attach() + */ + void writeMicroseconds(int value); + + /** + * returns the current value in degree as an angle between 0 and 189 degrees + * + * @see Servo::attach() + */ + int read() const { return uint16_t(this->_deg); } + + /** + * returns the current pwm value in microseconds. + * + * @see Servo::attach() + */ + int readMicroseconds() const { return uint16_t(this->_pwm); } + + /** + * @brief Check if this instance is attached to a servo. + * @return true if this instance is attached to a servo, false otherwise. + * @see Servo::attachedPin() + */ + bool attached() const { return this->_isActive; } + +private: + uint16_t _minPW; // the initial minPulseWidth, if not set than MIN_PULSE_WIDTH + uint16_t _maxPW; // the initial maxPulseWidth, if not set than MAX_PULSE_WIDTH + int16_t _minAngle; // the initial minAngle, if not set than MIN_ANGLE + int16_t _maxAngle; // the initial maxAngle, if not set than MAX_ANGLE + int16_t _pin; // attached arduino pin number + double _deg; // actual angle in degree + double _pwm; // actual pwm signal in microseconds + uint8_t _isActive; // true if this pin is active, otherwise false + + uint8_t servoIndex; // the actual number of Servos attached to this library + + +}; + +#endif \ No newline at end of file