summaryrefslogtreecommitdiff
path: root/libraries/ESP32Servo/src
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/ESP32Servo/src')
-rw-r--r--libraries/ESP32Servo/src/ESP32PWM.cpp411
-rw-r--r--libraries/ESP32Servo/src/ESP32PWM.h150
-rw-r--r--libraries/ESP32Servo/src/ESP32Servo.cpp270
-rw-r--r--libraries/ESP32Servo/src/ESP32Servo.h171
4 files changed, 1002 insertions, 0 deletions
diff --git a/libraries/ESP32Servo/src/ESP32PWM.cpp b/libraries/ESP32Servo/src/ESP32PWM.cpp
new file mode 100644
index 0000000..f0facc9
--- /dev/null
+++ b/libraries/ESP32Servo/src/ESP32PWM.cpp
@@ -0,0 +1,411 @@
+/*
+ * ESP32PWM.cpp
+ *
+ * Created on: Sep 22, 2018
+ * Author: hephaestus
+ */
+
+#include <ESP32PWM.h>
+#include "esp32-hal-ledc.h"
+
+// initialize the class variable ServoCount
+int ESP32PWM::PWMCount = -1; // the total number of attached servos
+bool ESP32PWM::explicateAllocationMode=false;
+ESP32PWM * ESP32PWM::ChannelUsed[NUM_PWM]; // used to track whether a channel is in service
+long ESP32PWM::timerFreqSet[4] = { -1, -1, -1, -1 };
+int ESP32PWM::timerCount[4] = { 0, 0, 0, 0 };
+
+static const char* TAG = "ESP32PWM";
+
+// The ChannelUsed array elements are 0 if never used, 1 if in use, and -1 if used and disposed
+// (i.e., available for reuse)
+/**
+ * allocateTimer
+ * @param a timer number 0-3 indicating which timer to allocate in this library
+ * Switch to explicate allocation mode
+ *
+ */
+void ESP32PWM::allocateTimer(int timerNumber){
+ if(timerNumber<0 || timerNumber>3)
+ return;
+ if(ESP32PWM::explicateAllocationMode==false){
+ ESP32PWM::explicateAllocationMode=true;
+ for(int i=0;i<4;i++)
+ ESP32PWM::timerCount[i]=4;// deallocate all timers to start mode
+ }
+ ESP32PWM::timerCount[timerNumber]=0;
+}
+
+ESP32PWM::ESP32PWM() {
+ resolutionBits = 8;
+ pwmChannel = -1;
+ pin = -1;
+ myFreq = -1;
+ if (PWMCount == -1) {
+ for (int i = 0; i < NUM_PWM; i++)
+ ChannelUsed[i] = NULL; // load invalid data into the storage array of pin mapping
+ PWMCount = PWM_BASE_INDEX; // 0th channel does not work with the PWM system
+ }
+}
+
+ESP32PWM::~ESP32PWM() {
+ if (attached()) {
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ ledcDetach(pin);
+#else
+ ledcDetachPin(pin);
+#endif
+#else
+ ledcDetachPin(pin);
+#endif
+ }
+ deallocate();
+}
+
+double ESP32PWM::_ledcSetupTimerFreq(uint8_t pin, double freq,
+ uint8_t bit_num) {
+
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ return ledcAttach(pin, freq, bit_num);
+#else
+ return ledcSetup(pin, freq, bit_num);
+#endif
+#else
+ return ledcSetup(pin, freq, bit_num);
+#endif
+
+}
+
+int ESP32PWM::timerAndIndexToChannel(int timerNum, int index) {
+ int localIndex = 0;
+ for (int j = 0; j < NUM_PWM; j++) {
+ if (((j / 2) % 4) == timerNum) {
+ if (localIndex == index) {
+ return j;
+ }
+ localIndex++;
+ }
+ }
+ return -1;
+}
+int ESP32PWM::allocatenext(double freq) {
+ long freqlocal = (long) freq;
+ if (pwmChannel < 0) {
+ for (int i = 0; i < 4; i++) {
+ bool freqAllocated = ((timerFreqSet[i] == freqlocal)
+ || (timerFreqSet[i] == -1));
+ if (freqAllocated && timerCount[i] < 4) {
+ if (timerFreqSet[i] == -1) {
+ //Serial.println("Starting timer "+String(i)+" at freq "+String(freq));
+ timerFreqSet[i] = freqlocal;
+ }
+ //Serial.println("Free channel timer "+String(i)+" at freq "+String(freq)+" remaining "+String(4-timerCount[i]));
+
+ timerNum = i;
+ for (int index=0; index<4; ++index)
+ {
+ int myTimerNumber = timerAndIndexToChannel(timerNum,index);
+ if ((myTimerNumber >= 0) && (!ChannelUsed[myTimerNumber]))
+ {
+ pwmChannel = myTimerNumber;
+// Serial.println(
+// "PWM on ledc channel #" + String(pwmChannel)
+// + " using 'timer " + String(timerNum)
+// + "' to freq " + String(freq) + "Hz");
+ ChannelUsed[pwmChannel] = this;
+ timerCount[timerNum]++;
+ PWMCount++;
+ myFreq = freq;
+ return pwmChannel;
+ }
+ }
+ } else {
+// if(timerFreqSet[i]>0)
+// Serial.println("Timer freq mismatch target="+String(freq)+" on timer "+String(i)+" was "+String(timerFreqSet[i]));
+// else
+// Serial.println("Timer out of channels target="+String(freq)+" on timer "+String(i)+" was "+String(timerCount[i]));
+ }
+ }
+ } else {
+ return pwmChannel;
+ }
+ ESP_LOGE(TAG,
+ "ERROR All PWM timers allocated! Can't accomodate %.3f Hz\r\nHalting...", freq);
+ while (1)
+ ;
+}
+void ESP32PWM::deallocate() {
+ if (pwmChannel < 0)
+ return;
+ ESP_LOGE(TAG, "PWM deallocating LEDc #%d",pwmChannel);
+ timerCount[getTimer()]--;
+ if (timerCount[getTimer()] == 0) {
+ timerFreqSet[getTimer()] = -1; // last pwn closed out
+ }
+ timerNum = -1;
+ attachedState = false;
+ ChannelUsed[pwmChannel] = NULL;
+ pwmChannel = -1;
+ PWMCount--;
+
+}
+
+int ESP32PWM::getChannel() {
+ if (pwmChannel < 0) {
+ ESP_LOGE(TAG, "FAIL! must setup() before using get channel!");
+ }
+ return pwmChannel;
+}
+
+double ESP32PWM::setup(double freq, uint8_t resolution_bits) {
+ checkFrequencyForSideEffects(freq);
+
+ resolutionBits = resolution_bits;
+ if (attached()) {
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ ledcDetach(pin);
+ double val = ledcAttach(getPin(), freq, resolution_bits);
+#else
+ ledcDetachPin(pin);
+ double val = ledcSetup(getChannel(), freq, resolution_bits);
+#endif
+#else
+ ledcDetachPin(pin);
+ double val = ledcSetup(getChannel(), freq, resolution_bits);
+#endif
+
+ attachPin(pin);
+ return val;
+ }
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ return ledcAttach(getPin(), freq, resolution_bits);
+#else
+ return ledcSetup(getChannel(), freq, resolution_bits);
+#endif
+#else
+ return ledcSetup(getChannel(), freq, resolution_bits);
+#endif
+}
+double ESP32PWM::getDutyScaled() {
+ return mapf((double) myDuty, 0, (double) ((1 << resolutionBits) - 1), 0.0,
+ 1.0);
+}
+void ESP32PWM::writeScaled(double duty) {
+ write(mapf(duty, 0.0, 1.0, 0, (double) ((1 << resolutionBits) - 1)));
+}
+void ESP32PWM::write(uint32_t duty) {
+ myDuty = duty;
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ ledcWrite(getPin(), duty);
+#else
+ ledcWrite(getChannel(), duty);
+#endif
+#else
+ ledcWrite(getChannel(), duty);
+#endif
+}
+void ESP32PWM::adjustFrequencyLocal(double freq, double dutyScaled) {
+ timerFreqSet[getTimer()] = (long) freq;
+ myFreq = freq;
+ if (attached()) {
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ ledcDetach(pin);
+ // Remove the PWM during frequency adjust
+ _ledcSetupTimerFreq(getPin(), freq, resolutionBits);
+ writeScaled(dutyScaled);
+ ledcAttach(getPin(), freq, resolutionBits); // re-attach the pin after frequency adjust
+#else
+ ledcDetachPin(pin);
+ // Remove the PWM during frequency adjust
+ _ledcSetupTimerFreq(getChannel(), freq, resolutionBits);
+ writeScaled(dutyScaled);
+ ledcAttachPin(pin, getChannel()); // re-attach the pin after frequency adjust
+#endif
+#else
+ ledcDetachPin(pin);
+ // Remove the PWM during frequency adjust
+ _ledcSetupTimerFreq(getChannel(), freq, resolutionBits);
+ writeScaled(dutyScaled);
+ ledcAttachPin(pin, getChannel()); // re-attach the pin after frequency adjust
+#endif
+
+ } else {
+ _ledcSetupTimerFreq(getPin(), freq, resolutionBits);
+ writeScaled(dutyScaled);
+ }
+}
+void ESP32PWM::adjustFrequency(double freq, double dutyScaled) {
+ if(dutyScaled<0)
+ dutyScaled=getDutyScaled();
+ writeScaled(dutyScaled);
+ for (int i = 0; i < timerCount[getTimer()]; i++) {
+ int pwm = timerAndIndexToChannel(getTimer(), i);
+ if (ChannelUsed[pwm] != NULL) {
+ if (ChannelUsed[pwm]->myFreq != freq) {
+ ChannelUsed[pwm]->adjustFrequencyLocal(freq,
+ ChannelUsed[pwm]->getDutyScaled());
+ }
+ }
+ }
+}
+double ESP32PWM::writeTone(double freq) {
+ for (int i = 0; i < timerCount[getTimer()]; i++) {
+ int pwm = timerAndIndexToChannel(getTimer(), i);
+ if (ChannelUsed[pwm] != NULL) {
+ if (ChannelUsed[pwm]->myFreq != freq) {
+ ChannelUsed[pwm]->adjustFrequencyLocal(freq,
+ ChannelUsed[pwm]->getDutyScaled());
+ }
+ write(1 << (resolutionBits-1)); // writeScaled(0.5);
+ }
+ }
+
+ return 0;
+}
+double ESP32PWM::writeNote(note_t note, uint8_t octave) {
+ const uint16_t noteFrequencyBase[12] = {
+ // C C# D Eb E F F# G G# A Bb B
+ 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459,
+ 7902 };
+
+ if (octave > 8 || note >= NOTE_MAX) {
+ return 0;
+ }
+ double noteFreq = (double) noteFrequencyBase[note]
+ / (double) (1 << (8 - octave));
+ return writeTone(noteFreq);
+}
+uint32_t ESP32PWM::read() {
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ return ledcRead(getPin());
+#else
+ return ledcRead(getChannel());
+#endif
+#else
+ return ledcRead(getChannel());
+#endif
+
+}
+double ESP32PWM::readFreq() {
+ return myFreq;
+}
+void ESP32PWM::attach(int p) {
+ pin = p;
+ attachedState = true;
+}
+void ESP32PWM::attachPin(uint8_t pin) {
+
+ if (hasPwm(pin)) {
+ attach(pin);
+ bool success=true;
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ success=ledcAttach(pin, readFreq(), resolutionBits);
+#else
+ ledcAttachPin(pin, getChannel());
+#endif
+#else
+ ledcAttachPin(pin, getChannel());
+#endif
+ if(success)
+ return;
+ ESP_LOGE(TAG, "ERROR PWM channel failed to configure on pin %d!", pin);
+ return;
+ }
+
+#if defined(CONFIG_IDF_TARGET_ESP32S2)
+ ESP_LOGE(TAG, "ERROR PWM channel unavailable on pin requested! %d PWM available on: 1-21,26,33-42",pin);
+#elif defined(CONFIG_IDF_TARGET_ESP32S3)
+ ESP_LOGE(TAG, "ERROR PWM channel unavailable on pin requested! %d PWM available on: 1-21,35-45,47-48",pin);
+#elif defined(CONFIG_IDF_TARGET_ESP32C3)
+ ESP_LOGE(TAG, "ERROR PWM channel unavailable on pin requested! %d PWM available on: 1-10,18-21",pin);
+#else
+ ESP_LOGE(TAG, "ERROR PWM channel unavailable on pin requested! %d PWM available on: 2,4,5,12-19,21-23,25-27,32-33",pin);
+#endif
+
+}
+void ESP32PWM::attachPin(uint8_t pin, double freq, uint8_t resolution_bits) {
+
+ if (hasPwm(pin)){
+ int ret=setup(freq, resolution_bits);
+ ESP_LOGW(TAG, "Pin Setup %d with code %d",pin,ret);
+ }
+ else
+ ESP_LOGE(TAG, "ERROR Pin Failed %d ",pin);
+ attachPin(pin);
+}
+void ESP32PWM::detachPin(int pin) {
+#ifdef ESP_ARDUINO_VERSION_MAJOR
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+
+ ledcDetach(pin);
+#else
+ ledcDetachPin(pin);
+#endif
+#else
+ ledcDetachPin(pin);
+#endif
+ deallocate();
+}
+/* Side effects of frequency changes happen because of shared timers
+ *
+ * LEDC Chan to Group/Channel/Timer Mapping
+ ** ledc: 0 => Group: 0, Channel: 0, Timer: 0
+ ** ledc: 1 => Group: 0, Channel: 1, Timer: 0
+ ** ledc: 2 => Group: 0, Channel: 2, Timer: 1
+ ** ledc: 3 => Group: 0, Channel: 3, Timer: 1
+ ** ledc: 4 => Group: 0, Channel: 4, Timer: 2
+ ** ledc: 5 => Group: 0, Channel: 5, Timer: 2
+ ** ledc: 6 => Group: 0, Channel: 6, Timer: 3
+ ** ledc: 7 => Group: 0, Channel: 7, Timer: 3
+ ** ledc: 8 => Group: 1, Channel: 0, Timer: 0
+ ** ledc: 9 => Group: 1, Channel: 1, Timer: 0
+ ** ledc: 10 => Group: 1, Channel: 2, Timer: 1
+ ** ledc: 11 => Group: 1, Channel: 3, Timer: 1
+ ** ledc: 12 => Group: 1, Channel: 4, Timer: 2
+ ** ledc: 13 => Group: 1, Channel: 5, Timer: 2
+ ** ledc: 14 => Group: 1, Channel: 6, Timer: 3
+ ** ledc: 15 => Group: 1, Channel: 7, Timer: 3
+ */
+
+bool ESP32PWM::checkFrequencyForSideEffects(double freq) {
+
+ allocatenext(freq);
+ for (int i = 0; i < timerCount[getTimer()]; i++) {
+ int pwm = timerAndIndexToChannel(getTimer(), i);
+
+ if (pwm == pwmChannel)
+ continue;
+ if (ChannelUsed[pwm] != NULL)
+ if (ChannelUsed[pwm]->getTimer() == getTimer()) {
+ double diff = abs(ChannelUsed[pwm]->myFreq - freq);
+ if (abs(diff) > 0.1) {
+ ESP_LOGW(TAG,
+ "\tWARNING PWM channel %d \
+ shares a timer with channel %d\n \
+ \tchanging the frequency to %.3f \
+ Hz will ALSO change channel %d \
+ \n\tfrom its previous frequency of %.3f Hz\n "
+ ,pwmChannel, pwm, freq, pwm, ChannelUsed[pwm]->myFreq);
+ ChannelUsed[pwm]->myFreq = freq;
+ }
+ }
+ }
+ return true;
+}
+
+ESP32PWM* pwmFactory(int pin) {
+ for (int i = 0; i < NUM_PWM; i++)
+ if (ESP32PWM::ChannelUsed[i] != NULL) {
+ if (ESP32PWM::ChannelUsed[i]->getPin() == pin)
+ return ESP32PWM::ChannelUsed[i];
+ }
+ return NULL;
+}
diff --git a/libraries/ESP32Servo/src/ESP32PWM.h b/libraries/ESP32Servo/src/ESP32PWM.h
new file mode 100644
index 0000000..581a244
--- /dev/null
+++ b/libraries/ESP32Servo/src/ESP32PWM.h
@@ -0,0 +1,150 @@
+/*
+ * ESP32PWM.h
+ *
+ * Created on: Sep 22, 2018
+ * Author: hephaestus
+ */
+
+#ifndef LIBRARIES_ESP32SERVO_SRC_ESP32PWM_H_
+#define LIBRARIES_ESP32SERVO_SRC_ESP32PWM_H_
+#include "esp32-hal-ledc.h"
+#if defined(ARDUINO)
+ #include "Arduino.h"
+#endif
+
+#if defined(CONFIG_IDF_TARGET_ESP32C3)
+#define NUM_PWM 6
+#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
+#define NUM_PWM 8
+#else
+#define NUM_PWM 16
+#endif
+
+#define PWM_BASE_INDEX 0
+#define USABLE_ESP32_PWM (NUM_PWM-PWM_BASE_INDEX)
+#include <cstdint>
+
+class ESP32PWM {
+private:
+
+ void attach(int pin);
+ int pwmChannel = 0; // channel number for this servo
+ bool attachedState = false;
+ int pin;
+ uint8_t resolutionBits;
+ double myFreq;
+ int allocatenext(double freq);
+
+ static double _ledcSetupTimerFreq(uint8_t pin, double freq,
+ uint8_t bit_num);
+
+ bool checkFrequencyForSideEffects(double freq);
+
+ void adjustFrequencyLocal(double freq, double dutyScaled);
+ static double mapf(double x, double in_min, double in_max, double out_min,
+ double out_max) {
+ if(x>in_max)
+ return out_max;
+ if(x<in_min)
+ return out_min;
+ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+ }
+
+ double setup(double freq, uint8_t resolution_bits=10);
+ //channel 0-15 resolution 1-16bits freq limits depend on resolution9
+ void attachPin(uint8_t pin);
+ // pin allocation
+ void deallocate();
+public:
+ // setup
+ ESP32PWM();
+ virtual ~ESP32PWM();
+
+
+ void detachPin(int pin);
+ void attachPin(uint8_t pin, double freq, uint8_t resolution_bits=10);
+ bool attached() {
+ return attachedState;
+ }
+
+ // write raw duty cycle
+ void write(uint32_t duty);
+ // Write a duty cycle to the PWM using a unit vector from 0.0-1.0
+ void writeScaled(double duty);
+ //Adjust frequency
+ double writeTone(double freq);
+ double writeNote(note_t note, uint8_t octave);
+ void adjustFrequency(double freq, double dutyScaled=-1);
+
+ // Read pwm data
+ uint32_t read();
+ double readFreq();
+ double getDutyScaled();
+
+ //Timer data
+ static int timerAndIndexToChannel(int timer, int index);
+ /**
+ * allocateTimer
+ * @param a timer number 0-3 indicating which timer to allocate in this library
+ * Switch to explicate allocation mode
+ *
+ */
+ static void allocateTimer(int timerNumber);
+ static bool explicateAllocationMode;
+ int getTimer() {
+ return timerNum;
+ }
+ int timerNum = -1;
+ uint32_t myDuty = 0;
+ int getChannel();
+ static int PWMCount; // the total number of attached pwm
+ static int timerCount[4];
+ static ESP32PWM * ChannelUsed[NUM_PWM]; // used to track whether a channel is in service
+ static long timerFreqSet[4];
+
+ // Helper functions
+ int getPin() {
+ return pin;
+ }
+ static bool hasPwm(int pin) {
+#if defined(CONFIG_IDF_TARGET_ESP32S2)
+ if ((pin >=1 && pin <= 21) || //21
+ (pin == 26) || //1
+ (pin >= 33 && pin <= 42)) //10
+#elif defined(CONFIG_IDF_TARGET_ESP32S3)
+ if ((pin >=1 && pin <= 21) || //20
+ (pin >= 35 && pin <= 45) || //11
+ (pin == 47) || (pin == 48)) //2
+#elif defined(CONFIG_IDF_TARGET_ESP32C3)
+ if ((pin >=0 && pin <= 10) || //11
+ (pin >= 18 && pin <= 21)) //4
+#elif defined(CONFIG_IDF_TARGET_ESP32C6)
+ if ((pin >=0 && pin <= 9) || //10
+ (pin >= 12 && pin <= 23)) //12
+#elif defined(CONFIG_IDF_TARGET_ESP32H2)
+ if ((pin >=0 && pin <= 5) || //6
+ (pin >= 8 && pin <= 14) || //7
+ (pin >= 22 && pin <= 27)) //6
+#else
+ if ((pin == 2) || //1
+ (pin == 4) || //1
+ (pin == 5) || //1
+ ((pin >= 12) && (pin <= 19)) || //8
+ ((pin >= 21) && (pin <= 23)) || //3
+ ((pin >= 25) && (pin <= 27)) || //3
+ (pin == 32) || (pin == 33)) //2
+#endif
+ return true;
+ return false;
+ }
+ static int channelsRemaining() {
+ return NUM_PWM - PWMCount;
+ }
+ static boolean DISABLE_DAC;
+
+
+};
+
+ESP32PWM* pwmFactory(int pin);
+
+#endif /* LIBRARIES_ESP32SERVO_SRC_ESP32PWM_H_ */
diff --git a/libraries/ESP32Servo/src/ESP32Servo.cpp b/libraries/ESP32Servo/src/ESP32Servo.cpp
new file mode 100644
index 0000000..277be6a
--- /dev/null
+++ b/libraries/ESP32Servo/src/ESP32Servo.cpp
@@ -0,0 +1,270 @@
+/*
+Copyright (c) 2017 John K. Bennett. All right reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+* Notes on the implementation:
+* The ESP32 supports 16 hardware LED PWM channels that are intended
+* to be used for LED brightness control. The low level ESP32 code
+* (esp32-hal-ledc.*) allows us to set the PWM frequency and bit-depth,
+* and then manipulate them by setting bits in the relevant control
+* registers.
+*
+* Different servos require different pulse widths to vary servo angle, but the range is
+* an approximately 500-2500 microsecond pulse every 20ms (50Hz). In general, hobbyist servos
+* sweep 180 degrees, so the lowest number in the published range for a particular servo
+* represents an angle of 0 degrees, the middle of the range represents 90 degrees, and the top
+* of the range represents 180 degrees. So for example, if the range is 1000us to 2000us,
+* 1000us would equal an angle of 0, 1500us would equal 90 degrees, and 2000us would equal 180
+* degrees. We vary pulse width (recall that the pulse period is already set to 20ms) as follows:
+*
+* The ESP32 PWM timers allow us to set the timer width (max 20 bits). Thus
+* the timer "tick" length is (pulse_period/2**timer_width), and the equation for pulse_high_width
+* (the portion of the 20ms cycle that the signal is high) becomes:
+*
+* pulse_high_width = count * tick_length
+* = count * (pulse_period/2**timer_width)
+*
+* and count = (pulse_high_width / (pulse_period/2**timer_width))
+*
+* So, for example, if I want a 1500us pulse_high_width, I set pulse_period to 20ms (20000us)
+* (this value is set in the ledcSetup call), and count (used in the ledcWrite call) to
+* 1500/(20000/65536), or 4924. This is the value we write to the timer in the ledcWrite call.
+* If we increase the timer_width, the timer_count values need to be adjusted.
+*
+* The servo signal pins connect to any available GPIO pins on the ESP32, but not all pins are
+* GPIO pins.
+*
+* The ESP32 is a 32 bit processor that includes FP support; this code reflects that fact.
+*/
+
+#include <ESP32Servo.h>
+#if defined(ARDUINO)
+ #include "Arduino.h"
+#endif
+
+static const char* TAG = "ESP32Servo";
+
+Servo::Servo()
+{ // initialize this channel with plausible values, except pin # (we set pin # when attached)
+ REFRESH_CPS = 50;
+ this->ticks = DEFAULT_PULSE_WIDTH_TICKS;
+ this->timer_width = DEFAULT_TIMER_WIDTH;
+ this->pinNumber = -1; // make it clear that we haven't attached a pin to this channel
+ this->min = DEFAULT_uS_LOW;
+ this->max = DEFAULT_uS_HIGH;
+ this->timer_width_ticks = pow(2,this->timer_width);
+
+}
+ESP32PWM * Servo::getPwm(){
+
+ return &pwm;
+}
+
+int Servo::attach(int pin)
+{
+
+ return (this->attach(pin, DEFAULT_uS_LOW, DEFAULT_uS_HIGH));
+}
+
+int Servo::attach(int pin, int min, int max)
+{
+ ESP_LOGW(TAG, "Attempting to Attach servo on pin=%d min=%d max=%d",pin,min,max);
+
+#ifdef ENFORCE_PINS
+ // ESP32 Recommend only the following pins 2,4,12-19,21-23,25-27,32-33
+ // ESP32-S2 only the following pins 1-21,26,33-42
+ if (pwm.hasPwm(pin))
+ {
+#endif
+
+ // OK to proceed; first check for new/reuse
+ if (this->pinNumber < 0) // we are attaching to a new or previously detached pin; we need to initialize/reinitialize
+ {
+ this->ticks = DEFAULT_PULSE_WIDTH_TICKS;
+ this->timer_width = DEFAULT_TIMER_WIDTH;
+ this->timer_width_ticks = pow(2,this->timer_width);
+ }
+ this->pinNumber = pin;
+#ifdef ENFORCE_PINS
+ }
+ else
+ {
+#ifdef __XTENSA_esp32s3__
+if(
+#endif
+
+#if defined(CONFIG_IDF_TARGET_ESP32S2)
+ ESP_LOGE(TAG, "This pin can not be a servo: %d Servo available on: 1-21,26,33-42", pin);
+#elif defined(CONFIG_IDF_TARGET_ESP32S3)
+ ESP_LOGE(TAG, "This pin can not be a servo: %d Servo available on: 1-21,35-45,47-48", pin);
+#elif defined(CONFIG_IDF_TARGET_ESP32C3)
+ ESP_LOGE(TAG, "This pin can not be a servo: %d Servo available on: 1-10,18-21", pin);
+#else
+ ESP_LOGE(TAG, "This pin can not be a servo: %d Servo available on: 2,4,5,12-19,21-23,25-27,32-33",pin);
+#endif
+ return 0;
+ }
+#endif
+
+
+ // min/max checks
+ if (min < MIN_PULSE_WIDTH) // ensure pulse width is valid
+ min = MIN_PULSE_WIDTH;
+ if (max > MAX_PULSE_WIDTH)
+ max = MAX_PULSE_WIDTH;
+ this->min = min; //store this value in uS
+ this->max = max; //store this value in uS
+ // Set up this channel
+ // if you want anything other than default timer width, you must call setTimerWidth() before attach
+
+ pwm.attachPin(this->pinNumber,REFRESH_CPS, this->timer_width ); // GPIO pin assigned to channel
+ ESP_LOGW(TAG, "Success to Attach servo : %d on PWM %d",pin,pwm.getChannel());
+
+ return pwm.getChannel();
+}
+
+void Servo::detach()
+{
+ if (this->attached())
+ {
+ //keep track of detached servos channels so we can reuse them if needed
+ pwm.detachPin(this->pinNumber);
+
+ this->pinNumber = -1;
+ }
+}
+
+void Servo::write(int value)
+{
+ // treat values less than MIN_PULSE_WIDTH (500) as angles in degrees (valid values in microseconds are handled as microseconds)
+ if (value < MIN_PULSE_WIDTH)
+ {
+ if (value < 0)
+ value = 0;
+ else if (value > 180)
+ value = 180;
+
+ value = map(value, 0, 180, this->min, this->max);
+ }
+ this->writeMicroseconds(value);
+}
+
+void Servo::writeMicroseconds(int value)
+{
+ writeTicks(usToTicks(value)); // convert to ticks
+}
+
+void Servo::writeTicks(int value)
+{
+ // calculate and store the values for the given channel
+ if (this->attached()) // ensure channel is valid
+ {
+ if (value < usToTicks(this->min)) // ensure ticks are in range
+ value = usToTicks(this->min);
+ else if (value > usToTicks(this->max))
+ value = usToTicks(this->max);
+ this->ticks = value;
+ // do the actual write
+ pwm.write( this->ticks);
+ }
+}
+
+void Servo::release()
+{
+ if (this->attached()) // ensure channel is valid
+ pwm.write(0);
+}
+
+int Servo::read() // return the value as degrees
+{
+ return (map(readMicroseconds(), this->min, this->max, 0, 180));
+}
+
+int Servo::readMicroseconds()
+{
+ int pulsewidthUsec;
+ if (this->attached())
+ {
+ pulsewidthUsec = ticksToUs(this->ticks);
+ }
+ else
+ {
+ pulsewidthUsec = 0;
+ }
+
+ return (pulsewidthUsec);
+}
+
+int Servo::readTicks()
+{
+ return this->ticks;
+}
+
+bool Servo::attached()
+{
+ return (pwm.attached());
+}
+
+void Servo::setTimerWidth(int value)
+{
+ // only allow values between 10 and 14 for ESP32-C3
+ // only allow values between 16 and 20 for other ESP32
+ if (value < MINIMUM_TIMER_WIDTH )
+ value = MINIMUM_TIMER_WIDTH;
+ else if (value > MAXIMUM_TIMER_WIDTH)
+ value = MAXIMUM_TIMER_WIDTH;
+
+ // Fix the current ticks value after timer width change
+ // The user can reset the tick value with a write() or writeUs()
+ int widthDifference = this->timer_width - value;
+ // if positive multiply by diff; if neg, divide
+ if (widthDifference > 0)
+ {
+ this->ticks = widthDifference * this->ticks;
+ }
+ else if (widthDifference < 0)
+ {
+ this->ticks = this->ticks/-widthDifference;
+ }
+
+ this->timer_width = value;
+ this->timer_width_ticks = pow(2,this->timer_width);
+
+ // If this is an attached servo, clean up
+ if (this->attached())
+ {
+ // detach, setup and attach again to reflect new timer width
+ pwm.detachPin(this->pinNumber);
+ pwm.attachPin(this->pinNumber, REFRESH_CPS, this->timer_width);
+ }
+}
+
+int Servo::readTimerWidth()
+{
+ return (this->timer_width);
+}
+
+int Servo::usToTicks(int usec)
+{
+ return (int)((double)usec / ((double)REFRESH_USEC / (double)this->timer_width_ticks)*(((double)REFRESH_CPS)/50.0));
+}
+
+int Servo::ticksToUs(int ticks)
+{
+ return (int)((double)ticks * ((double)REFRESH_USEC / (double)this->timer_width_ticks)/(((double)REFRESH_CPS)/50.0));
+}
+
+
diff --git a/libraries/ESP32Servo/src/ESP32Servo.h b/libraries/ESP32Servo/src/ESP32Servo.h
new file mode 100644
index 0000000..ed5f852
--- /dev/null
+++ b/libraries/ESP32Servo/src/ESP32Servo.h
@@ -0,0 +1,171 @@
+/*
+ Copyright (c) 2017 John K. Bennett. All right reserved.
+
+ ESP32_Servo.h - Servo library for ESP32 - Version 1
+
+ Original Servo.h written by Michael Margolis in 2009
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ A servo is activated by creating an instance of the Servo class, and passing
+ the desired GPIO pin to the attach() method.
+ The servos are pulsed in the background using the value most recently
+ written using the write() method.
+
+ The class methods are:
+
+ Servo - Class for manipulating servo motors connected to ESP32 pins.
+ int attach(pin ) - Attaches the given GPIO pin to the next free channel
+ (channels that have previously been detached are used first),
+ returns channel number or 0 if failure. All pin numbers are allowed,
+ but only pins 2,4,12-19,21-23,25-27,32-33 are recommended.
+ int attach(pin, min, max ) - Attaches to a pin setting min and max
+ values in microseconds; enforced minimum min is 500, enforced max
+ is 2500. Other semantics same as attach().
+ void write () - Sets the servo angle in degrees; a value below 500 is
+ treated as a value in degrees (0 to 180). These limit are enforced,
+ i.e., values are treated as follows:
+ Value Becomes
+ ----- -------
+ < 0 0
+ 0 - 180 value (treated as degrees)
+ 181 - 499 180
+ 500 - (min-1) min
+ min-max (from attach or default) value (treated as microseconds)
+ (max+1) - 2500 max
+
+ void writeMicroseconds() - Sets the servo pulse width in microseconds.
+ min and max are enforced (see above).
+ int read() - Gets the last written servo pulse width as an angle between 0 and 180.
+ int readMicroseconds() - Gets the last written servo pulse width in microseconds.
+ bool attached() - Returns true if this servo instance is attached.
+ void detach() - Stops an the attached servo, frees its attached pin, and frees
+ its channel for reuse).
+
+ *** ESP32-specific functions **
+ setTimerWidth(value) - Sets the PWM timer width (must be 16-20) (ESP32 ONLY);
+ as a side effect, the pulse width is recomputed.
+ int readTimerWidth() - Gets the PWM timer width (ESP32 ONLY)
+ */
+
+#ifndef ESP32_Servo_h
+#define ESP32_Servo_h
+//#include "analogWrite.h"
+#include "ESP32PWM.h"
+//#include "ESP32Tone.h"
+//Enforce only using PWM pins on the ESP32
+#define ENFORCE_PINS
+// Default Arduino Servo.h
+#define DEFAULT_uS_LOW 544
+#define DEFAULT_uS_HIGH 2400
+
+// Values for TowerPro MG995 large servos (and many other hobbyist servos)
+//#define DEFAULT_uS_LOW 1000 // 1000us
+//#define DEFAULT_uS_HIGH 2000 // 2000us
+
+// Values for TowerPro SG90 small servos
+//#define DEFAULT_uS_LOW 400
+//#define DEFAULT_uS_HIGH 2400
+
+#ifdef ARDUINO_ESP32C3_DEV
+#define MINIMUM_TIMER_WIDTH 10
+#define MAXIMUM_TIMER_WIDTH 14
+#define DEFAULT_TIMER_WIDTH 10
+#else
+#define MINIMUM_TIMER_WIDTH 10
+#define MAXIMUM_TIMER_WIDTH 20
+#define DEFAULT_TIMER_WIDTH 10
+#endif
+#define DEFAULT_TIMER_WIDTH_TICKS 1024
+
+#define ESP32_Servo_VERSION 1 // software version of this library
+
+#define MIN_PULSE_WIDTH 500 // the shortest pulse sent to a servo
+#define MAX_PULSE_WIDTH 2500 // the longest pulse sent to a servo
+#define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached
+#define DEFAULT_PULSE_WIDTH_TICKS 4825
+//#define REFRESH_CPS 50
+#define REFRESH_USEC 20000
+
+#define MAX_SERVOS 16 // no. of PWM channels in ESP32
+
+/*
+ * This group/channel/timmer mapping is for information only;
+ * the details are handled by lower-level code
+ *
+ * LEDC Chan to Group/Channel/Timer Mapping
+ ** ledc: 0 => Group: 0, Channel: 0, Timer: 0
+ ** ledc: 1 => Group: 0, Channel: 1, Timer: 0
+ ** ledc: 2 => Group: 0, Channel: 2, Timer: 1
+ ** ledc: 3 => Group: 0, Channel: 3, Timer: 1
+ ** ledc: 4 => Group: 0, Channel: 4, Timer: 2
+ ** ledc: 5 => Group: 0, Channel: 5, Timer: 2
+ ** ledc: 6 => Group: 0, Channel: 6, Timer: 3
+ ** ledc: 7 => Group: 0, Channel: 7, Timer: 3
+ ** ledc: 8 => Group: 1, Channel: 0, Timer: 0
+ ** ledc: 9 => Group: 1, Channel: 1, Timer: 0
+ ** ledc: 10 => Group: 1, Channel: 2, Timer: 1
+ ** ledc: 11 => Group: 1, Channel: 3, Timer: 1
+ ** ledc: 12 => Group: 1, Channel: 4, Timer: 2
+ ** ledc: 13 => Group: 1, Channel: 5, Timer: 2
+ ** ledc: 14 => Group: 1, Channel: 6, Timer: 3
+ ** ledc: 15 => Group: 1, Channel: 7, Timer: 3
+ */
+
+class Servo {
+
+public:
+ Servo();
+ // Arduino Servo Library calls
+ int attach(int pin); // attach the given pin to the next free channel, returns channel number or 0 if failure
+ int attach(int pin, int min, int max); // as above but also sets min and max values for writes.
+ void detach();
+ void write(int value); // if value is < MIN_PULSE_WIDTH its treated as an angle, otherwise as pulse width in microseconds
+ void writeMicroseconds(int value); // Write pulse width in microseconds
+ void writeTicks(int value); // Write ticks, the smallest increment the servo can handle
+ void release();
+ int read(); // returns current pulse width as an angle between 0 and 180 degrees
+ int readMicroseconds(); // returns current pulse width in microseconds for this servo
+ int readTicks(); // returns current ticks, the smallest increment the servo can handle
+ bool attached(); // return true if this servo is attached, otherwise false
+
+ // ESP32 only functions
+ void setTimerWidth(int value); // set the PWM timer width (ESP32 ONLY)
+ int readTimerWidth(); // get the PWM timer width (ESP32 ONLY)
+ void setPeriodHertz(int hertz){
+ REFRESH_CPS=hertz;
+ setTimerWidth(this->timer_width);
+ }
+private:
+ int usToTicks(int usec);
+ int ticksToUs(int ticks);
+// static int ServoCount; // the total number of attached servos
+// static int ChannelUsed[]; // used to track whether a channel is in service
+// int servoChannel = 0; // channel number for this servo
+
+ int min = DEFAULT_uS_LOW; // minimum pulse width for this servo
+ int max = DEFAULT_uS_HIGH; // maximum pulse width for this servo
+ int pinNumber = 0; // GPIO pin assigned to this channel
+ int timer_width = DEFAULT_TIMER_WIDTH; // ESP32 allows variable width PWM timers
+ int ticks = DEFAULT_PULSE_WIDTH_TICKS; // current pulse width on this channel
+ int timer_width_ticks = DEFAULT_TIMER_WIDTH_TICKS; // no. of ticks at rollover; varies with width
+ ESP32PWM * getPwm(); // get the PWM object
+ ESP32PWM pwm;
+ int REFRESH_CPS = 50;
+
+};
+#endif