Преглед изворни кода

✨ ESP32 - Hardware PWM for fan, cutter, servos (#23802)

John Robertson пре 2 година
родитељ
комит
1f1571f726
No account linked to committer's email address

+ 1
- 1
Marlin/Configuration_adv.h Прегледај датотеку

@@ -3464,7 +3464,7 @@
3464 3464
   #define SPINDLE_LASER_USE_PWM                // Enable if your controller supports setting the speed/power
3465 3465
   #if ENABLED(SPINDLE_LASER_USE_PWM)
3466 3466
     #define SPINDLE_LASER_PWM_INVERT    false  // Set to "true" if the speed/power goes up when you want it to go slower
3467
-    #define SPINDLE_LASER_FREQUENCY     2500   // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC)
3467
+    #define SPINDLE_LASER_FREQUENCY     2500   // (Hz) Spindle/laser frequency (only on supported HALs: AVR, ESP32 and LPC)
3468 3468
   #endif
3469 3469
 
3470 3470
   //#define AIR_EVACUATION                     // Cutter Vacuum / Laser Blower motor control with G-codes M10-M11

+ 82
- 19
Marlin/src/HAL/ESP32/HAL.cpp Прегледај датотеку

@@ -73,9 +73,16 @@ uint16_t MarlinHAL::adc_result;
73 73
 esp_adc_cal_characteristics_t characteristics[ADC_ATTEN_MAX];
74 74
 adc_atten_t attenuations[ADC1_CHANNEL_MAX] = {};
75 75
 uint32_t thresholds[ADC_ATTEN_MAX];
76
-volatile int numPWMUsed = 0,
77
-             pwmPins[MAX_PWM_PINS],
78
-             pwmValues[MAX_PWM_PINS];
76
+
77
+volatile int numPWMUsed = 0;
78
+volatile struct { pin_t pin; int value; } pwmState[MAX_PWM_PINS];
79
+
80
+pin_t chan_pin[CHANNEL_MAX_NUM + 1] = { 0 }; // PWM capable IOpins - not 0 or >33 on ESP32
81
+
82
+struct {
83
+  uint32_t freq; // ledcReadFreq doesn't work if a duty hasn't been set yet!
84
+  uint16_t res;
85
+} pwmInfo[(CHANNEL_MAX_NUM + 1) / 2];
79 86
 
80 87
 // ------------------------
81 88
 // Public functions
@@ -254,25 +261,81 @@ void MarlinHAL::adc_start(const pin_t pin) {
254 261
   adc1_set_attenuation(chan, atten);
255 262
 }
256 263
 
257
-void analogWrite(pin_t pin, int value) {
258
-  // Use ledc hardware for internal pins
259
-  if (pin < 34) {
260
-    static int cnt_channel = 1, pin_to_channel[40] = { 0 };
261
-    if (pin_to_channel[pin] == 0) {
262
-      ledcAttachPin(pin, cnt_channel);
263
-      ledcSetup(cnt_channel, 490, 8);
264
-      ledcWrite(cnt_channel, value);
265
-      pin_to_channel[pin] = cnt_channel++;
264
+// ------------------------
265
+// PWM
266
+// ------------------------
267
+
268
+int8_t channel_for_pin(const uint8_t pin) {
269
+  for (int i = 0; i <= CHANNEL_MAX_NUM; i++)
270
+    if (chan_pin[i] == pin) return i;
271
+  return -1;
272
+}
273
+
274
+// get PWM channel for pin - if none then attach a new one
275
+// return -1 if fail or invalid pin#, channel # (0-15) if success
276
+int8_t get_pwm_channel(const pin_t pin, const uint32_t freq, const uint16_t res) {
277
+  if (!WITHIN(pin, 1, MAX_PWM_IOPIN)) return -1; // Not a hardware PWM pin!
278
+  int8_t cid = channel_for_pin(pin);
279
+  if (cid >= 0) return cid;
280
+
281
+  // Find an empty adjacent channel (same timer & freq/res)
282
+  for (int i = 0; i <= CHANNEL_MAX_NUM; i++) {
283
+    if (chan_pin[i] == 0) {
284
+      if (chan_pin[i ^ 0x1] != 0) {
285
+        if (pwmInfo[i / 2].freq == freq && pwmInfo[i / 2].res == res) {
286
+          chan_pin[i] = pin; // Allocate PWM to this channel
287
+          ledcAttachPin(pin, i);
288
+          return i;
289
+        }
290
+      }
291
+      else if (cid == -1)    // Pair of empty channels?
292
+        cid = i & 0xFE;      // Save lower channel number
266 293
     }
267
-    ledcWrite(pin_to_channel[pin], value);
294
+  }
295
+  // not attached, is an empty timer slot avail?
296
+  if (cid >= 0) {
297
+    chan_pin[cid] = pin;
298
+    pwmInfo[cid / 2].freq = freq;
299
+    pwmInfo[cid / 2].res = res;
300
+    ledcSetup(cid, freq, res);
301
+    ledcAttachPin(pin, cid);
302
+  }
303
+  return cid; // -1 if no channel avail
304
+}
305
+
306
+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*/) {
307
+  const int8_t cid = get_pwm_channel(pin, PWM_FREQUENCY, PWM_RESOLUTION);
308
+  if (cid >= 0) {
309
+    uint32_t duty = map(invert ? v_size - v : v, 0, v_size, 0, _BV(PWM_RESOLUTION)-1);
310
+    ledcWrite(cid, duty);
311
+  }
312
+}
313
+
314
+int8_t MarlinHAL::set_pwm_frequency(const pin_t pin, const uint32_t f_desired) {
315
+  const int8_t cid = channel_for_pin(pin);
316
+  if (cid >= 0) {
317
+    if (f_desired == ledcReadFreq(cid)) return cid; // no freq change
318
+    ledcDetachPin(chan_pin[cid]);
319
+    chan_pin[cid] = 0;              // remove old freq channel
320
+  }
321
+  return get_pwm_channel(pin, f_desired, PWM_RESOLUTION); // try for new one
322
+}
323
+
324
+// use hardware PWM if avail, if not then ISR
325
+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!
326
+  // Use ledc hardware for internal pins
327
+  const int8_t cid = get_pwm_channel(pin, freq, res);
328
+  if (cid >= 0) {
329
+    ledcWrite(cid, value); // set duty value
268 330
     return;
269 331
   }
270 332
 
333
+  // not a hardware PWM pin OR no PWM channels available
271 334
   int idx = -1;
272 335
 
273 336
   // Search Pin
274 337
   for (int i = 0; i < numPWMUsed; ++i)
275
-    if (pwmPins[i] == pin) { idx = i; break; }
338
+    if (pwmState[i].pin == pin) { idx = i; break; }
276 339
 
277 340
   // not found ?
278 341
   if (idx < 0) {
@@ -281,7 +344,7 @@ void analogWrite(pin_t pin, int value) {
281 344
 
282 345
     // Take new slot for pin
283 346
     idx = numPWMUsed;
284
-    pwmPins[idx] = pin;
347
+    pwmState[idx].pin = pin;
285 348
     // Start timer on first use
286 349
     if (idx == 0) HAL_timer_start(MF_TIMER_PWM, PWM_TIMER_FREQUENCY);
287 350
 
@@ -289,7 +352,7 @@ void analogWrite(pin_t pin, int value) {
289 352
   }
290 353
 
291 354
   // Use 7bit internal value - add 1 to have 100% high at 255
292
-  pwmValues[idx] = (value + 1) / 2;
355
+  pwmState[idx].value = (value + 1) / 2;
293 356
 }
294 357
 
295 358
 // Handle PWM timer interrupt
@@ -300,9 +363,9 @@ HAL_PWM_TIMER_ISR() {
300 363
 
301 364
   for (int i = 0; i < numPWMUsed; ++i) {
302 365
     if (count == 0)                   // Start of interval
303
-      WRITE(pwmPins[i], pwmValues[i] ? HIGH : LOW);
304
-    else if (pwmValues[i] == count)   // End of duration
305
-      WRITE(pwmPins[i], LOW);
366
+      digitalWrite(pwmState[i].pin, pwmState[i].value ? HIGH : LOW);
367
+    else if (pwmState[i].value == count)   // End of duration
368
+      digitalWrite(pwmState[i].pin, LOW);
306 369
   }
307 370
 
308 371
   // 128 for 7 Bit resolution

+ 18
- 7
Marlin/src/HAL/ESP32/HAL.h Прегледај датотеку

@@ -64,6 +64,12 @@
64 64
 #define CRITICAL_SECTION_START() portENTER_CRITICAL(&spinlock)
65 65
 #define CRITICAL_SECTION_END()   portEXIT_CRITICAL(&spinlock)
66 66
 
67
+#define HAL_CAN_SET_PWM_FREQ   // This HAL supports PWM Frequency adjustment
68
+#define PWM_FREQUENCY  1000u   // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency()
69
+#define PWM_RESOLUTION   10u   // Default PWM bit resolution
70
+#define CHANNEL_MAX_NUM  15u   // max PWM channel # to allocate (7 to only use low speed, 15 to use low & high)
71
+#define MAX_PWM_IOPIN    33u   // hardware pwm pins < 34
72
+
67 73
 // ------------------------
68 74
 // Types
69 75
 // ------------------------
@@ -83,7 +89,7 @@ typedef Servo hal_servo_t;
83 89
 void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0);
84 90
 void noTone(const pin_t _pin);
85 91
 
86
-void analogWrite(pin_t pin, int value);
92
+void analogWrite(const pin_t pin, const uint16_t value, const uint32_t freq=PWM_FREQUENCY, const uint16_t res=8);
87 93
 
88 94
 //
89 95
 // Pin Mapping for M42, M43, M226
@@ -209,12 +215,17 @@ public:
209 215
   static uint16_t adc_value() { return adc_result; }
210 216
 
211 217
   /**
212
-   * Set the PWM duty cycle for the pin to the given value.
213
-   * No inverting the duty cycle in this HAL.
214
-   * No changing the maximum size of the provided value to enable finer PWM duty control in this HAL.
218
+   * If not already allocated, allocate a hardware PWM channel
219
+   * to the pin and set the duty cycle..
220
+   * Optionally invert the duty cycle [default = false]
221
+   * Optionally change the scale of the provided value to enable finer PWM duty control [default = 255]
215 222
    */
216
-  static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) {
217
-    analogWrite(pin, v);
218
-  }
223
+  static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false);
224
+
225
+  /**
226
+   * Allocate and set the frequency of a hardware PWM pin
227
+   * Returns -1 if no pin available.
228
+   */
229
+  static int8_t set_pwm_frequency(const pin_t pin, const uint32_t f_desired);
219 230
 
220 231
 };

+ 8
- 10
Marlin/src/HAL/ESP32/Servo.cpp Прегледај датотеку

@@ -31,20 +31,18 @@
31 31
 // so we only allocate servo channels up high to avoid side effects with regards to analogWrite (fans, leds, laser pwm etc.)
32 32
 int Servo::channel_next_free = 12;
33 33
 
34
-Servo::Servo() {
35
-  channel = channel_next_free++;
36
-}
34
+Servo::Servo() {}
37 35
 
38 36
 int8_t Servo::attach(const int inPin) {
39
-  if (channel >= CHANNEL_MAX_NUM) return -1;
40 37
   if (inPin > 0) pin = inPin;
41
-
42
-  ledcSetup(channel, 50, 16); // channel X, 50 Hz, 16-bit depth
43
-  ledcAttachPin(pin, channel);
44
-  return true;
38
+  channel = get_pwm_channel(pin, 50u, 16u);
39
+  return channel; // -1 if no PWM avail.
45 40
 }
46 41
 
47
-void Servo::detach() { ledcDetachPin(pin); }
42
+// leave channel connected to servo - set duty to zero
43
+void Servo::detach() {
44
+  if (channel >= 0) ledcWrite(channel, 0);
45
+}
48 46
 
49 47
 int Servo::read() { return degrees; }
50 48
 
@@ -52,7 +50,7 @@ void Servo::write(int inDegrees) {
52 50
   degrees = constrain(inDegrees, MIN_ANGLE, MAX_ANGLE);
53 51
   int us = map(degrees, MIN_ANGLE, MAX_ANGLE, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
54 52
   int duty = map(us, 0, TAU_USEC, 0, MAX_COMPARE);
55
-  ledcWrite(channel, duty);
53
+  if (channel >= 0) ledcWrite(channel, duty); // don't save duty for servos!
56 54
 }
57 55
 
58 56
 void Servo::move(const int value) {

+ 1
- 2
Marlin/src/HAL/ESP32/Servo.h Прегледај датотеку

@@ -30,8 +30,7 @@ class Servo {
30 30
                    MAX_PULSE_WIDTH = 2400,  // Longest pulse sent to a servo
31 31
                    TAU_MSEC = 20,
32 32
                    TAU_USEC = (TAU_MSEC * 1000),
33
-                   MAX_COMPARE = _BV(16) - 1, // 65535
34
-                   CHANNEL_MAX_NUM = 16;
33
+                   MAX_COMPARE = _BV(16) - 1; // 65535
35 34
 
36 35
 public:
37 36
   Servo();

+ 6
- 2
Marlin/src/HAL/ESP32/inc/SanityCheck.h Прегледај датотеку

@@ -25,8 +25,8 @@
25 25
   #error "EMERGENCY_PARSER is not yet implemented for ESP32. Disable EMERGENCY_PARSER to continue."
26 26
 #endif
27 27
 
28
-#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY
29
-  #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on ESP32."
28
+#if (ENABLED(SPINDLE_LASER_USE_PWM) && SPINDLE_LASER_FREQUENCY > 78125) || (ENABLED(FAST_PWM_FAN_FREQUENCY) && FAST_PWM_FAN_FREQUENCY > 78125)
29
+  #error "SPINDLE_LASER_FREQUENCY and FAST_PWM_FREQUENCY maximum value is 78125Hz for ESP32."
30 30
 #endif
31 31
 
32 32
 #if HAS_TMC_SW_SERIAL
@@ -40,3 +40,7 @@
40 40
 #if ENABLED(POSTMORTEM_DEBUGGING)
41 41
   #error "POSTMORTEM_DEBUGGING is not yet supported on ESP32."
42 42
 #endif
43
+
44
+#if MB(MKS_TINYBEE) && ENABLED(FAST_PWM_FAN)
45
+  #error "FAST_PWM_FAN is not available on TinyBee."
46
+#endif

Loading…
Откажи
Сачувај