first commit
This commit is contained in:
commit
e6a42e28e9
4355 changed files with 4626605 additions and 0 deletions
616
libraries/ESP32Servo/src/ESP32PWM.cpp
Normal file
616
libraries/ESP32Servo/src/ESP32PWM.cpp
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* 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 };
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
MCPWMTimerInfo ESP32PWM::mcpwmTimers[MCPWM_NUM_UNITS][MCPWM_NUM_TIMERS_PER_UNIT];
|
||||
#endif
|
||||
|
||||
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(bool variableFrequency) : useVariableFrequency(variableFrequency) {
|
||||
resolutionBits = 8;
|
||||
pwmChannel = -1;
|
||||
pin = -1;
|
||||
myFreq = -1;
|
||||
isMCPWM = false; // Explicitly initialize to false
|
||||
if (PWMCount == -1) {
|
||||
for (int i = 0; i < NUM_PWM; i++)
|
||||
ChannelUsed[i] = NULL; // load invalid data into the storage array of pin mapping
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// MCPWMTimerInfo struct is initialized with default values in the struct definition
|
||||
ESP_LOGI(TAG, "ESP32S3 detected - MCPWM support enabled");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
ESP_LOGI(TAG, "ESP32C5 detected - MCPWM support not enabled");
|
||||
#else
|
||||
ESP_LOGI(TAG, "Non-ESP32S3 target - using LEDC only");
|
||||
#endif
|
||||
PWMCount = PWM_BASE_INDEX; // 0th channel does not work with the PWM system
|
||||
}
|
||||
}
|
||||
|
||||
ESP32PWM::~ESP32PWM() {
|
||||
if (attached()) {
|
||||
if (isMCPWM) {
|
||||
// MCPWM detach not needed, deallocate handles it
|
||||
} else {
|
||||
#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, uint8_t channel) {
|
||||
|
||||
#ifdef ESP_ARDUINO_VERSION_MAJOR
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
return ledcAttachChannel(pin, freq, bit_num, channel);
|
||||
#else
|
||||
return ledcSetup(channel, freq, bit_num);
|
||||
#endif
|
||||
#else
|
||||
return ledcSetup(channel, 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 && !isMCPWM) { // not yet allocated
|
||||
if (useVariableFrequency) {
|
||||
// try LEDC first
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bool freqAllocated = ((timerFreqSet[i] == freqlocal) || (timerFreqSet[i] == -1));
|
||||
if (freqAllocated && timerCount[i] < 4) {
|
||||
if (timerFreqSet[i] == -1) {
|
||||
timerFreqSet[i] = freqlocal;
|
||||
}
|
||||
timerNum = i;
|
||||
for (int index=0; index<4; ++index) {
|
||||
int myTimerNumber = timerAndIndexToChannel(timerNum,index);
|
||||
if ((myTimerNumber >= 0) && (!ChannelUsed[myTimerNumber])) {
|
||||
pwmChannel = myTimerNumber;
|
||||
ChannelUsed[pwmChannel] = this;
|
||||
timerCount[timerNum]++;
|
||||
PWMCount++;
|
||||
myFreq = freq;
|
||||
isMCPWM = false;
|
||||
return pwmChannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if no LEDC, try MCPWM (ESP32S3 only)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
for(int u=0; u<MCPWM_NUM_UNITS; u++) {
|
||||
for(int t=0; t<MCPWM_NUM_TIMERS_PER_UNIT; t++) {
|
||||
if (mcpwmTimers[u][t].operatorCount == 0) {
|
||||
mcpwmTimers[u][t].freq = freqlocal;
|
||||
mcpwmUnit = (mcpwm_unit_t)u;
|
||||
mcpwmTimer = (mcpwm_timer_t)t;
|
||||
mcpwmOperator = MCPWM_OPR_A;
|
||||
mcpwmTimers[u][t].operators[0] = this;
|
||||
mcpwmTimers[u][t].operatorCount = 1;
|
||||
PWMCount++;
|
||||
myFreq = freq;
|
||||
isMCPWM = true;
|
||||
pwmChannel = -1; // not used
|
||||
timerNum = -1;
|
||||
return 0; // dummy
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// non-variable, prefer MCPWM with shared freq, fallback to LEDC
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
for(int u=0; u<MCPWM_NUM_UNITS; u++) {
|
||||
for(int t=0; t<MCPWM_NUM_TIMERS_PER_UNIT; t++) {
|
||||
bool freqMatch = (mcpwmTimers[u][t].freq == freqlocal || mcpwmTimers[u][t].freq == -1);
|
||||
if (freqMatch && mcpwmTimers[u][t].operatorCount < MCPWM_NUM_OPERATORS_PER_TIMER) {
|
||||
if (mcpwmTimers[u][t].freq == -1) {
|
||||
mcpwmTimers[u][t].freq = freqlocal;
|
||||
}
|
||||
mcpwmUnit = (mcpwm_unit_t)u;
|
||||
mcpwmTimer = (mcpwm_timer_t)t;
|
||||
int opIndex = mcpwmTimers[u][t].operatorCount;
|
||||
mcpwmOperator = (opIndex == 0) ? MCPWM_OPR_A : MCPWM_OPR_B;
|
||||
mcpwmTimers[u][t].operators[opIndex] = this;
|
||||
mcpwmTimers[u][t].operatorCount++;
|
||||
PWMCount++;
|
||||
myFreq = freq;
|
||||
isMCPWM = true;
|
||||
pwmChannel = -1;
|
||||
timerNum = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// if no MCPWM available, fallback to LEDC shared freq
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bool freqAllocated = ((timerFreqSet[i] == freqlocal) || (timerFreqSet[i] == -1));
|
||||
if (freqAllocated && timerCount[i] < 4) {
|
||||
if (timerFreqSet[i] == -1) {
|
||||
timerFreqSet[i] = freqlocal;
|
||||
}
|
||||
timerNum = i;
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
int myTimerNumber = timerAndIndexToChannel(timerNum, index);
|
||||
if ((myTimerNumber >= 0) && (!ChannelUsed[myTimerNumber])) {
|
||||
pwmChannel = myTimerNumber;
|
||||
ChannelUsed[pwmChannel] = this;
|
||||
timerCount[timerNum]++;
|
||||
PWMCount++;
|
||||
myFreq = freq;
|
||||
isMCPWM = false;
|
||||
return pwmChannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isMCPWM) {
|
||||
return 0; // already allocated
|
||||
} 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 (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
ESP_LOGI(TAG, "PWM deallocating MCPWM unit %d timer %d operator %d", mcpwmUnit, mcpwmTimer, mcpwmOperator);
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].operatorCount--;
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].operators[mcpwmOperator == MCPWM_OPR_A ? 0 : 1] = NULL;
|
||||
if (mcpwmTimers[mcpwmUnit][mcpwmTimer].operatorCount == 0) {
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].freq = -1;
|
||||
}
|
||||
#else
|
||||
// This should never happen - isMCPWM should only be true on ESP32S3
|
||||
ESP_LOGE(TAG, "ERROR: MCPWM deallocate attempted on non-ESP32S3 target!");
|
||||
isMCPWM = false; // Reset to safe state
|
||||
#endif
|
||||
} else if (pwmChannel >= 0) {
|
||||
ESP_LOGI(TAG, "PWM deallocating LEDc #%d",pwmChannel);
|
||||
timerCount[getTimer()]--;
|
||||
if (timerCount[getTimer()] == 0) {
|
||||
timerFreqSet[getTimer()] = -1; // last pwn closed out
|
||||
}
|
||||
ChannelUsed[pwmChannel] = NULL;
|
||||
pwmChannel = -1;
|
||||
}
|
||||
timerNum = -1;
|
||||
attachedState = false;
|
||||
isMCPWM = false;
|
||||
PWMCount--;
|
||||
}
|
||||
|
||||
int ESP32PWM::getChannel() {
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
return (mcpwmUnit * 10 + mcpwmTimer * 2 + (mcpwmOperator == MCPWM_OPR_A ? 0 : 1)) + 100; // offset to avoid conflict
|
||||
#else
|
||||
// This should never happen - isMCPWM should only be true on ESP32S3
|
||||
ESP_LOGE(TAG, "ERROR: MCPWM getChannel attempted on non-ESP32S3 target!");
|
||||
isMCPWM = false; // Reset to safe state
|
||||
return -1; // Return invalid channel as fallback
|
||||
#endif
|
||||
} else if (pwmChannel < 0) {
|
||||
ESP_LOGE(TAG, "FAIL! must setup() before using get channel!");
|
||||
}
|
||||
return pwmChannel;
|
||||
}
|
||||
|
||||
double ESP32PWM::setup(double freq, uint8_t resolution_bits) {
|
||||
if (!isMCPWM) {
|
||||
checkFrequencyForSideEffects(freq);
|
||||
}
|
||||
|
||||
resolutionBits = resolution_bits;
|
||||
if (attached()) {
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// for MCPWM, re-init timer with new freq
|
||||
mcpwm_config_t pwm_config;
|
||||
pwm_config.frequency = freq;
|
||||
pwm_config.cmpr_a = 0;
|
||||
pwm_config.cmpr_b = 0;
|
||||
pwm_config.counter_mode = MCPWM_UP_COUNTER;
|
||||
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
|
||||
mcpwm_init(mcpwmUnit, mcpwmTimer, &pwm_config);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef ESP_ARDUINO_VERSION_MAJOR
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
ledcDetach(pin);
|
||||
double val = ledcAttachChannel(getPin(), freq, resolution_bits, getChannel());
|
||||
#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 freq;
|
||||
}
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
mcpwm_config_t pwm_config;
|
||||
pwm_config.frequency = freq;
|
||||
pwm_config.cmpr_a = 0;
|
||||
pwm_config.cmpr_b = 0;
|
||||
pwm_config.counter_mode = MCPWM_UP_COUNTER;
|
||||
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
|
||||
mcpwm_init(mcpwmUnit, mcpwmTimer, &pwm_config);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
|
||||
#endif
|
||||
return freq;
|
||||
} else {
|
||||
#ifdef ESP_ARDUINO_VERSION_MAJOR
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
return ledcAttachChannel(getPin(), freq, resolution_bits, getChannel());
|
||||
#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;
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
float duty_percent = (float)duty / (1 << resolutionBits) * 100.0f;
|
||||
mcpwm_set_duty(mcpwmUnit, mcpwmTimer, mcpwmOperator, duty_percent);
|
||||
#else
|
||||
// This should never happen - isMCPWM should only be true on ESP32S3
|
||||
ESP_LOGE(TAG, "ERROR: MCPWM operation attempted on non-ESP32S3 target!");
|
||||
isMCPWM = false; // Reset to safe state
|
||||
#endif
|
||||
} else {
|
||||
#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, getChannel());
|
||||
writeScaled(dutyScaled);
|
||||
ledcAttachChannel(getPin(), freq, resolutionBits, getChannel()); // re-attach the pin after frequency adjust
|
||||
#else
|
||||
ledcDetachPin(pin);
|
||||
// Remove the PWM during frequency adjust
|
||||
_ledcSetupTimerFreq(getPin(), freq, resolutionBits, getChannel());
|
||||
writeScaled(dutyScaled);
|
||||
ledcAttachPin(pin, getChannel()); // re-attach the pin after frequency adjust
|
||||
#endif
|
||||
#else
|
||||
ledcDetachPin(pin);
|
||||
// Remove the PWM during frequency adjust
|
||||
_ledcSetupTimerFreq(getPin(), freq, resolutionBits, getChannel());
|
||||
writeScaled(dutyScaled);
|
||||
ledcAttachPin(pin, getChannel()); // re-attach the pin after frequency adjust
|
||||
#endif
|
||||
|
||||
} else {
|
||||
_ledcSetupTimerFreq(getPin(), freq, resolutionBits, getChannel());
|
||||
writeScaled(dutyScaled);
|
||||
}
|
||||
}
|
||||
void ESP32PWM::adjustFrequency(double freq, double dutyScaled) {
|
||||
if (!useVariableFrequency) {
|
||||
ESP_LOGE(TAG, "ERROR: Cannot change frequency on fixed-frequency PWM channel (pin %d). Frequency is locked for shared timer operation.", pin);
|
||||
return;
|
||||
}
|
||||
if(dutyScaled<0)
|
||||
dutyScaled=getDutyScaled();
|
||||
writeScaled(dutyScaled);
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].freq = (long)freq;
|
||||
myFreq = freq;
|
||||
// re-init timer with new freq
|
||||
mcpwm_config_t pwm_config;
|
||||
pwm_config.frequency = freq;
|
||||
pwm_config.cmpr_a = 0;
|
||||
pwm_config.cmpr_b = 0;
|
||||
pwm_config.counter_mode = MCPWM_UP_COUNTER;
|
||||
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
|
||||
mcpwm_init(mcpwmUnit, mcpwmTimer, &pwm_config);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
|
||||
mcpwm_set_duty_type(mcpwmUnit, mcpwmTimer, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
|
||||
// update all operators on this timer
|
||||
for (int i = 0; i < mcpwmTimers[mcpwmUnit][mcpwmTimer].operatorCount; i++) {
|
||||
if (mcpwmTimers[mcpwmUnit][mcpwmTimer].operators[i] != NULL) {
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].operators[i]->myFreq = freq;
|
||||
mcpwmTimers[mcpwmUnit][mcpwmTimer].operators[i]->writeScaled(mcpwmTimers[mcpwmUnit][mcpwmTimer].operators[i]->getDutyScaled());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
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) {
|
||||
if (isMCPWM) {
|
||||
adjustFrequency(freq, 0.5);
|
||||
} else {
|
||||
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() {
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// MCPWM doesn't have a read function, return stored duty
|
||||
return myDuty;
|
||||
#else
|
||||
// This should never happen - isMCPWM should only be true on ESP32S3
|
||||
ESP_LOGE(TAG, "ERROR: MCPWM read attempted on non-ESP32S3 target!");
|
||||
isMCPWM = false; // Reset to safe state
|
||||
return myDuty; // Return stored duty as fallback
|
||||
#endif
|
||||
} else {
|
||||
#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;
|
||||
if (isMCPWM) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
mcpwm_io_signals_t signal = (mcpwmOperator == MCPWM_OPR_A) ? MCPWM0A : MCPWM0B;
|
||||
if (mcpwmUnit == MCPWM_UNIT_1) signal = (mcpwmOperator == MCPWM_OPR_A) ? MCPWM1A : MCPWM1B;
|
||||
mcpwm_gpio_init(mcpwmUnit, signal, pin);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef ESP_ARDUINO_VERSION_MAJOR
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
success = ledcAttachChannel(pin, readFreq(), resolutionBits, getChannel());
|
||||
#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 the ESP32S2 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 the ESP32S3 pin requested! %d PWM available on: 1-21,35-45,47-48", pin);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
ESP_LOGE(TAG, "ERROR PWM channel unavailable on the ESP32C5 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 the ESP32C3 pin requested! %d PWM available on: 1-10,18-21", pin);
|
||||
#else
|
||||
ESP_LOGE(TAG, "ERROR PWM channel unavailable on the ESP32 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)){
|
||||
this->pin = pin;
|
||||
int ret=setup(freq, resolution_bits);
|
||||
ESP_LOGI(TAG, "Pin Setup %d with code %d",pin,ret);
|
||||
attachPin(pin);
|
||||
}
|
||||
else
|
||||
ESP_LOGE(TAG, "ERROR Pin Failed %d ",pin);
|
||||
}
|
||||
void ESP32PWM::detachPin(int pin) {
|
||||
if (!isMCPWM) {
|
||||
#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];
|
||||
}
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
for(int u=0; u<MCPWM_NUM_UNITS; u++) {
|
||||
for(int t=0; t<MCPWM_NUM_TIMERS_PER_UNIT; t++) {
|
||||
for(int o=0; o<MCPWM_NUM_OPERATORS_PER_TIMER; o++) {
|
||||
if (ESP32PWM::mcpwmTimers[u][t].operators[o] != NULL) {
|
||||
if (ESP32PWM::mcpwmTimers[u][t].operators[o]->getPin() == pin)
|
||||
return ESP32PWM::mcpwmTimers[u][t].operators[o];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
215
libraries/ESP32Servo/src/ESP32PWM.h
Normal file
215
libraries/ESP32Servo/src/ESP32PWM.h
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* ESP32PWM.h - Enhanced PWM Library for ESP32
|
||||
*
|
||||
* This library provides PWM functionality for ESP32 chips with support for:
|
||||
* - ESP32S3: MCPWM hardware acceleration for optimal servo performance
|
||||
* - Variable frequency mode: LEDC preferred, MCPWM fallback for flexibility
|
||||
* - Fixed frequency mode: MCPWM preferred, LEDC fallback for shared timers
|
||||
* - Automatic hardware allocation with intelligent fallbacks
|
||||
*
|
||||
* Key Features:
|
||||
* - Dual hardware support (LEDC + MCPWM on S3)
|
||||
* - 20 total PWM channels on ESP32S3 (8 LEDC + 12 MCPWM)
|
||||
* - 12 (6 supported) total PWM channels on ESP32C5 (6 LEDC + 6 MCPWM (not supported by library yet))
|
||||
* - Frequency locking for fixed-frequency applications (servos)
|
||||
* - Seamless hardware fallback when preferred hardware unavailable
|
||||
*
|
||||
* Usage:
|
||||
* - ESP32PWM pwm; // Variable frequency (default)
|
||||
* - ESP32PWM pwm(true); // Variable frequency (explicit)
|
||||
* - ESP32PWM pwm(false); // Fixed frequency
|
||||
*
|
||||
* Created on: Sep 22, 2018
|
||||
* Author: hephaestus
|
||||
* Enhanced for ESP32S3 MCPWM support
|
||||
*/
|
||||
|
||||
#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
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
/**
|
||||
* @brief Number of PWM channels
|
||||
* @details The ESP32C5 has 6 LEDC channels and 6 MCPWM channels, but the MCPWM channels require further changes
|
||||
* @see Page 5 of https://documentation.espressif.com/esp32-c5_datasheet_en.pdf
|
||||
*/
|
||||
// #define NUM_PWM 12 // TODO: Add MCPWM support for the ESP32-C5
|
||||
#define NUM_PWM 6 // LEDC channels only
|
||||
#else
|
||||
#define NUM_PWM 16
|
||||
#endif
|
||||
|
||||
// MCPWM support for ESP32S3
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#include "driver/mcpwm.h"
|
||||
#define MCPWM_NUM_UNITS 2
|
||||
#define MCPWM_NUM_TIMERS_PER_UNIT 3
|
||||
#define MCPWM_NUM_OPERATORS_PER_TIMER 2
|
||||
|
||||
class ESP32PWM; // Forward declaration
|
||||
|
||||
struct MCPWMTimerInfo {
|
||||
bool initialized = false;
|
||||
long freq = -1;
|
||||
int operatorCount = 0;
|
||||
ESP32PWM* operators[MCPWM_NUM_OPERATORS_PER_TIMER] = {nullptr, nullptr};
|
||||
};
|
||||
|
||||
#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;
|
||||
bool useVariableFrequency = false;
|
||||
bool isMCPWM = false;
|
||||
int allocatenext(double freq);
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
mcpwm_unit_t mcpwmUnit;
|
||||
mcpwm_timer_t mcpwmTimer;
|
||||
mcpwm_operator_t mcpwmOperator;
|
||||
#endif
|
||||
|
||||
static double _ledcSetupTimerFreq(uint8_t pin, double freq,
|
||||
uint8_t bit_num, uint8_t channel);
|
||||
|
||||
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(bool variableFrequency = true); // true=variable freq (LEDC preferred), false=fixed freq (MCPWM preferred)
|
||||
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 (only works on variable-frequency channels)
|
||||
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];
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
static MCPWMTimerInfo mcpwmTimers[MCPWM_NUM_UNITS][MCPWM_NUM_TIMERS_PER_UNIT];
|
||||
#endif
|
||||
|
||||
// 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_ESP32C5)
|
||||
if ((pin >=0 && pin <= 14) ||
|
||||
(pin >= 12 && pin <= 14) ||
|
||||
(pin >= 23 && pin <= 28))
|
||||
#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() {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
return NUM_PWM + (MCPWM_NUM_UNITS * MCPWM_NUM_TIMERS_PER_UNIT * MCPWM_NUM_OPERATORS_PER_TIMER) - PWMCount;
|
||||
#else
|
||||
return NUM_PWM - PWMCount;
|
||||
#endif
|
||||
}
|
||||
static boolean DISABLE_DAC;
|
||||
|
||||
|
||||
};
|
||||
|
||||
ESP32PWM* pwmFactory(int pin);
|
||||
|
||||
#endif /* LIBRARIES_ESP32SERVO_SRC_ESP32PWM_H_ */
|
||||
272
libraries/ESP32Servo/src/ESP32Servo.cpp
Normal file
272
libraries/ESP32Servo/src/ESP32Servo.cpp
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
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 ESP32S2 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 ESP32S3 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 ESP32C3 pin can not be a servo: %d Servo available on: 1-10,18-21", pin);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
ESP_LOGE(TAG, "This ESP32C5 pin can not be a servo: %d Servo available on: 0-14,12-14,23-28", pin);
|
||||
#else
|
||||
ESP_LOGE(TAG, "This ESP32 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));
|
||||
}
|
||||
|
||||
|
||||
187
libraries/ESP32Servo/src/ESP32Servo.h
Normal file
187
libraries/ESP32Servo/src/ESP32Servo.h
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
Copyright (c) 2017 John K. Bennett. All right reserved.
|
||||
|
||||
ESP32_Servo.h - Enhanced Servo library for ESP32 - Version 1
|
||||
|
||||
Original Servo.h written by Michael Margolis in 2009
|
||||
|
||||
This library provides servo control with optimized hardware support:
|
||||
- ESP32S3: MCPWM hardware acceleration for precise servo timing
|
||||
- Fixed frequency mode for consistent servo operation
|
||||
- Automatic hardware allocation with LEDC fallback
|
||||
|
||||
Key Features:
|
||||
- Uses fixed-frequency PWM for servo compatibility
|
||||
- MCPWM preferred on S3, LEDC fallback for maximum channels
|
||||
- 50Hz default frequency with 1000-2000us pulse range
|
||||
- Up to 16 servo channels (limited by available PWM hardware)
|
||||
|
||||
Usage:
|
||||
- Servo myservo; // Create servo instance
|
||||
- myservo.attach(pin); // Attach to pin with default range
|
||||
- myservo.write(angle); // Set servo angle (0-180 degrees)
|
||||
|
||||
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 20 // maximum PWM channels supported (varies by ESP32 variant)
|
||||
|
||||
/*
|
||||
* 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{false}; // fixed frequency for servo use
|
||||
int REFRESH_CPS = 50;
|
||||
|
||||
};
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue