Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hardware PWM for fan, laser/spindle and servos on ESP32 boards #23802

Merged
merged 25 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f2a1e10
Add hardware PWM for fan & laser/spindle PWM
HoverClub Feb 25, 2022
91b5087
Delete duplicate code
HoverClub Feb 25, 2022
95a4e90
Replaced deleted code
HoverClub Feb 25, 2022
d3a3a85
Revert "Replaced deleted code"
HoverClub Feb 25, 2022
e342689
Revert "Delete duplicate code"
HoverClub Feb 25, 2022
571e41b
misc. cleanup
thinkyhead Feb 26, 2022
c2b1e3a
use structs, replace WRITE
thinkyhead Feb 26, 2022
5f9d625
outdent
thinkyhead Feb 26, 2022
a8bd231
use structs, replace WRITE
thinkyhead Feb 26, 2022
4ef3989
Array changes
HoverClub Feb 28, 2022
23d609b
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Feb 28, 2022
fd683d5
Merge branch 'bugfix-2.0.x' of https://github.com/HoverClub/Marlin in…
HoverClub Feb 28, 2022
381c2bc
fixed chanPin variable name
HoverClub Feb 28, 2022
49dcbda
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Mar 2, 2022
e0b745b
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Mar 11, 2022
76a4b3f
Update Configuration_adv.h
HoverClub Mar 13, 2022
6c0c152
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Mar 13, 2022
b753786
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Mar 14, 2022
407d9f5
Change ADC ref for MKS TinyBee
HoverClub Mar 14, 2022
8259ee9
Corrects ADC Vref divisor for MKS TinyBee
HoverClub Mar 14, 2022
8bccc11
Revert "Corrects ADC Vref divisor for MKS TinyBee"
HoverClub Mar 14, 2022
63c2965
Revert "Change ADC ref for MKS TinyBee"
HoverClub Mar 14, 2022
931fd6b
Correct ADC Vref divisor for MKS TinyBee
HoverClub Mar 14, 2022
47e6067
Revert "Correct ADC Vref divisor for MKS TinyBee"
HoverClub Mar 14, 2022
7ef3f91
Merge branch 'MarlinFirmware:bugfix-2.0.x' into bugfix-2.0.x
HoverClub Mar 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -3464,7 +3464,7 @@
#define SPINDLE_LASER_USE_PWM // Enable if your controller supports setting the speed/power
#if ENABLED(SPINDLE_LASER_USE_PWM)
#define SPINDLE_LASER_PWM_INVERT false // Set to "true" if the speed/power goes up when you want it to go slower
#define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC)
#define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR, ESP32 and LPC)
#endif

//#define AIR_EVACUATION // Cutter Vacuum / Laser Blower motor control with G-codes M10-M11
Expand Down
101 changes: 82 additions & 19 deletions Marlin/src/HAL/ESP32/HAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,16 @@ uint16_t MarlinHAL::adc_result;
esp_adc_cal_characteristics_t characteristics[ADC_ATTEN_MAX];
adc_atten_t attenuations[ADC1_CHANNEL_MAX] = {};
uint32_t thresholds[ADC_ATTEN_MAX];
volatile int numPWMUsed = 0,
pwmPins[MAX_PWM_PINS],
pwmValues[MAX_PWM_PINS];

volatile int numPWMUsed = 0;
volatile struct { pin_t pin; int value; } pwmState[MAX_PWM_PINS];

pin_t chan_pin[CHANNEL_MAX_NUM + 1] = { 0 }; // PWM capable IOpins - not 0 or >33 on ESP32

struct {
uint32_t freq; // ledcReadFreq doesn't work if a duty hasn't been set yet!
uint16_t res;
} pwmInfo[(CHANNEL_MAX_NUM + 1) / 2];

// ------------------------
// Public functions
Expand Down Expand Up @@ -254,25 +261,81 @@ void MarlinHAL::adc_start(const pin_t pin) {
adc1_set_attenuation(chan, atten);
}

void analogWrite(pin_t pin, int value) {
// Use ledc hardware for internal pins
if (pin < 34) {
static int cnt_channel = 1, pin_to_channel[40] = { 0 };
if (pin_to_channel[pin] == 0) {
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 490, 8);
ledcWrite(cnt_channel, value);
pin_to_channel[pin] = cnt_channel++;
// ------------------------
// PWM
// ------------------------

int8_t channel_for_pin(const uint8_t pin) {
for (int i = 0; i <= CHANNEL_MAX_NUM; i++)
if (chan_pin[i] == pin) return i;
return -1;
}

// get PWM channel for pin - if none then attach a new one
// return -1 if fail or invalid pin#, channel # (0-15) if success
int8_t get_pwm_channel(const pin_t pin, const uint32_t freq, const uint16_t res) {
if (!WITHIN(pin, 1, MAX_PWM_IOPIN)) return -1; // Not a hardware PWM pin!
int8_t cid = channel_for_pin(pin);
if (cid >= 0) return cid;

// Find an empty adjacent channel (same timer & freq/res)
for (int i = 0; i <= CHANNEL_MAX_NUM; i++) {
if (chan_pin[i] == 0) {
if (chan_pin[i ^ 0x1] != 0) {
if (pwmInfo[i / 2].freq == freq && pwmInfo[i / 2].res == res) {
chan_pin[i] = pin; // Allocate PWM to this channel
ledcAttachPin(pin, i);
return i;
}
}
else if (cid == -1) // Pair of empty channels?
cid = i & 0xFE; // Save lower channel number
}
ledcWrite(pin_to_channel[pin], value);
}
// not attached, is an empty timer slot avail?
if (cid >= 0) {
chan_pin[cid] = pin;
pwmInfo[cid / 2].freq = freq;
pwmInfo[cid / 2].res = res;
ledcSetup(cid, freq, res);
ledcAttachPin(pin, cid);
}
return cid; // -1 if no channel avail
}

void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=_BV(PWM_RESOLUTION)-1*/, const bool invert/*=false*/) {
const int8_t cid = get_pwm_channel(pin, PWM_FREQUENCY, PWM_RESOLUTION);
if (cid >= 0) {
uint32_t duty = map(invert ? v_size - v : v, 0, v_size, 0, _BV(PWM_RESOLUTION)-1);
ledcWrite(cid, duty);
}
}

int8_t MarlinHAL::set_pwm_frequency(const pin_t pin, const uint32_t f_desired) {
const int8_t cid = channel_for_pin(pin);
if (cid >= 0) {
if (f_desired == ledcReadFreq(cid)) return cid; // no freq change
ledcDetachPin(chan_pin[cid]);
chan_pin[cid] = 0; // remove old freq channel
}
return get_pwm_channel(pin, f_desired, PWM_RESOLUTION); // try for new one
}

// use hardware PWM if avail, if not then ISR
void analogWrite(const pin_t pin, const uint16_t value, const uint32_t freq/*=PWM_FREQUENCY*/, const uint16_t res/*=8*/) { // always 8 bit resolution!
// Use ledc hardware for internal pins
const int8_t cid = get_pwm_channel(pin, freq, res);
if (cid >= 0) {
ledcWrite(cid, value); // set duty value
return;
}

// not a hardware PWM pin OR no PWM channels available
int idx = -1;

// Search Pin
for (int i = 0; i < numPWMUsed; ++i)
if (pwmPins[i] == pin) { idx = i; break; }
if (pwmState[i].pin == pin) { idx = i; break; }

// not found ?
if (idx < 0) {
Expand All @@ -281,15 +344,15 @@ void analogWrite(pin_t pin, int value) {

// Take new slot for pin
idx = numPWMUsed;
pwmPins[idx] = pin;
pwmState[idx].pin = pin;
// Start timer on first use
if (idx == 0) HAL_timer_start(MF_TIMER_PWM, PWM_TIMER_FREQUENCY);

++numPWMUsed;
}

// Use 7bit internal value - add 1 to have 100% high at 255
pwmValues[idx] = (value + 1) / 2;
pwmState[idx].value = (value + 1) / 2;
}

// Handle PWM timer interrupt
Expand All @@ -300,9 +363,9 @@ HAL_PWM_TIMER_ISR() {

for (int i = 0; i < numPWMUsed; ++i) {
if (count == 0) // Start of interval
WRITE(pwmPins[i], pwmValues[i] ? HIGH : LOW);
else if (pwmValues[i] == count) // End of duration
WRITE(pwmPins[i], LOW);
digitalWrite(pwmState[i].pin, pwmState[i].value ? HIGH : LOW);
else if (pwmState[i].value == count) // End of duration
digitalWrite(pwmState[i].pin, LOW);
}

// 128 for 7 Bit resolution
Expand Down
25 changes: 18 additions & 7 deletions Marlin/src/HAL/ESP32/HAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
#define CRITICAL_SECTION_START() portENTER_CRITICAL(&spinlock)
#define CRITICAL_SECTION_END() portEXIT_CRITICAL(&spinlock)

#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment
#define PWM_FREQUENCY 1000u // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency()
#define PWM_RESOLUTION 10u // Default PWM bit resolution
#define CHANNEL_MAX_NUM 15u // max PWM channel # to allocate (7 to only use low speed, 15 to use low & high)
#define MAX_PWM_IOPIN 33u // hardware pwm pins < 34

// ------------------------
// Types
// ------------------------
Expand All @@ -83,7 +89,7 @@ typedef Servo hal_servo_t;
void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0);
void noTone(const pin_t _pin);

void analogWrite(pin_t pin, int value);
void analogWrite(const pin_t pin, const uint16_t value, const uint32_t freq=PWM_FREQUENCY, const uint16_t res=8);

//
// Pin Mapping for M42, M43, M226
Expand Down Expand Up @@ -209,12 +215,17 @@ class MarlinHAL {
static uint16_t adc_value() { return adc_result; }

/**
* Set the PWM duty cycle for the pin to the given value.
* No inverting the duty cycle in this HAL.
* No changing the maximum size of the provided value to enable finer PWM duty control in this HAL.
* If not already allocated, allocate a hardware PWM channel
* to the pin and set the duty cycle..
* Optionally invert the duty cycle [default = false]
* Optionally change the scale of the provided value to enable finer PWM duty control [default = 255]
*/
static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) {
analogWrite(pin, v);
}
static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false);

/**
* Allocate and set the frequency of a hardware PWM pin
* Returns -1 if no pin available.
*/
static int8_t set_pwm_frequency(const pin_t pin, const uint32_t f_desired);

};
18 changes: 8 additions & 10 deletions Marlin/src/HAL/ESP32/Servo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,26 @@
// so we only allocate servo channels up high to avoid side effects with regards to analogWrite (fans, leds, laser pwm etc.)
int Servo::channel_next_free = 12;

Servo::Servo() {
channel = channel_next_free++;
}
Servo::Servo() {}

int8_t Servo::attach(const int inPin) {
if (channel >= CHANNEL_MAX_NUM) return -1;
if (inPin > 0) pin = inPin;

ledcSetup(channel, 50, 16); // channel X, 50 Hz, 16-bit depth
ledcAttachPin(pin, channel);
return true;
channel = get_pwm_channel(pin, 50u, 16u);
return channel; // -1 if no PWM avail.
}

void Servo::detach() { ledcDetachPin(pin); }
// leave channel connected to servo - set duty to zero
void Servo::detach() {
if (channel >= 0) ledcWrite(channel, 0);
}

int Servo::read() { return degrees; }

void Servo::write(int inDegrees) {
degrees = constrain(inDegrees, MIN_ANGLE, MAX_ANGLE);
int us = map(degrees, MIN_ANGLE, MAX_ANGLE, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
int duty = map(us, 0, TAU_USEC, 0, MAX_COMPARE);
ledcWrite(channel, duty);
if (channel >= 0) ledcWrite(channel, duty); // don't save duty for servos!
}

void Servo::move(const int value) {
Expand Down
3 changes: 1 addition & 2 deletions Marlin/src/HAL/ESP32/Servo.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ class Servo {
MAX_PULSE_WIDTH = 2400, // Longest pulse sent to a servo
TAU_MSEC = 20,
TAU_USEC = (TAU_MSEC * 1000),
MAX_COMPARE = _BV(16) - 1, // 65535
CHANNEL_MAX_NUM = 16;
MAX_COMPARE = _BV(16) - 1; // 65535

public:
Servo();
Expand Down
8 changes: 6 additions & 2 deletions Marlin/src/HAL/ESP32/inc/SanityCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
#error "EMERGENCY_PARSER is not yet implemented for ESP32. Disable EMERGENCY_PARSER to continue."
#endif

#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY
#error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on ESP32."
#if (ENABLED(SPINDLE_LASER_USE_PWM) && SPINDLE_LASER_FREQUENCY > 78125) || (ENABLED(FAST_PWM_FAN_FREQUENCY) && FAST_PWM_FAN_FREQUENCY > 78125)
#error "SPINDLE_LASER_FREQUENCY and FAST_PWM_FREQUENCY maximum value is 78125Hz for ESP32."
#endif

#if HAS_TMC_SW_SERIAL
Expand All @@ -40,3 +40,7 @@
#if ENABLED(POSTMORTEM_DEBUGGING)
#error "POSTMORTEM_DEBUGGING is not yet supported on ESP32."
#endif

#if MB(MKS_TINYBEE) && ENABLED(FAST_PWM_FAN)
#error "FAST_PWM_FAN is not available on TinyBee."
#endif