-
Notifications
You must be signed in to change notification settings - Fork 7.7k
ESP32-S3 RMT-based PWM sweep causes glitches; not as seamless as Timer1 on Arduino Uno #11529
-
Board
ESP32-S3
Device Description
ESP32-S3 module on custom board. Only one GPIO (GPIO10) is used for PWM output via RMT.
Hardware Configuration
Only GPIO10 is used as output to measure PWM via oscilloscope. No other components attached.
Version
latest stable Release (if not listed below)
IDE Name
Arduino IDE
Operating System
Windows 11
Flash frequency
80 MHz
PSRAM enabled
no
Upload speed
921600
Description
When sweeping PWM frequency and Ton dynamically using RMT on ESP32-S3, the output is not smooth. There's a noticeable glitch or "bang" at each frequency step. The same logic works perfectly and seamlessly on Arduino Uno using Timer1 hardware PWM with Phase Correct mode.
Expected: clean frequency and Ton sweep with no break in output
Observed: visible discontinuity or glitch at each step due to teardown/reinit of RMT channel
Sketch
Below code i used in Arduino UNO worked flawlessly. const int pwmPin = 9; // OC1A = Pin 9 on Arduino Uno const float freqStart = 28000.0; // Hz const float freqEnd = 24000.0; // Hz const float freqStep = -10.0; // Hz const float tonStart = 8.0; // μs const float tonEnd = 21.0; // μs const int delayMs = 5; // Delay per step const int offDelayMs = 3000; // 5 sec OFF after sweep void setup() { pinMode(pwmPin, OUTPUT); delay(2000); // Timer1: Phase Correct PWM, TOP = ICR1 TCCR1A = _BV(COM1A1); // Non-inverting PWM on OC1A TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8: Phase-correct, no prescaler Serial.begin(115200); } void loop() { float freq = freqStart; float ton = tonStart; float freqRange = freqStart - freqEnd; float tonRange = tonEnd - tonStart; int steps = freqRange / -freqStep; for (int i = 0; i <= steps; i++) { freq = freqStart + i * freqStep; ton = tonStart + (tonRange * i / steps); float period_us = 1000000.0 / freq; uint16_t top = (16000000UL / (2 * freq)); // ICR1 value uint16_t duty = (ton / period_us) * top; // OCR1A value ICR1 = top; OCR1A = duty; Serial.print("Freq: "); Serial.print(freq, 1); Serial.print(" Hz | Ton: "); Serial.print(ton, 1); Serial.print(" us | TOP: "); Serial.print(top); Serial.print(" | OCR1A: "); Serial.println(duty); delay(delayMs); } // Turn off PWM OCR1A = 0; Serial.println("PWM OFF for 5 seconds"); delay(offDelayMs); } Below code i used in ESP32-S3 but the wave is banging. #include <Arduino.h> #include <driver/rmt_tx.h> #include <Ticker.h> // --- Pin Setup --- #define PWM_PIN 10 // Pick a reliable RMT-capable GPIO // --- Sweep Parameters --- const float freqStart = 28000.0; const float freqEnd = 24000.0; const float freqStep = -10.0; const float tonStart = 8.0; const float tonEnd = 21.0; const int delayMs = 5; const int offDelayMs = 5000; rmt_channel_handle_t rmt_chan = nullptr; rmt_encoder_handle_t rmt_encoder = nullptr; void setupRMT(float freq, float ton_us) { if (rmt_chan) { rmt_disable(rmt_chan); rmt_del_channel(rmt_chan); rmt_chan = nullptr; } uint32_t period_us = 1000000.0 / freq; uint32_t toff_us = period_us - ton_us; rmt_tx_channel_config_t tx_config = {}; tx_config.clk_src = RMT_CLK_SRC_DEFAULT; tx_config.gpio_num = (gpio_num_t)PWM_PIN; tx_config.mem_block_symbols = 64; tx_config.resolution_hz = 1000000; // 1 μs resolution tx_config.trans_queue_depth = 1; tx_config.flags.invert_out = false; tx_config.flags.with_dma = false; ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_config, &rmt_chan)); rmt_copy_encoder_config_t enc_config = {}; ESP_ERROR_CHECK(rmt_new_copy_encoder(&enc_config, &rmt_encoder)); ESP_ERROR_CHECK(rmt_enable(rmt_chan)); rmt_symbol_word_t item; item.level0 = 1; item.duration0 = (uint16_t)ton_us; item.level1 = 0; item.duration1 = (uint16_t)toff_us; rmt_transmit_config_t config = {}; config.loop_count = 1000; ESP_ERROR_CHECK(rmt_transmit(rmt_chan, rmt_encoder, &item, sizeof(item), &config)); } void stopPWM() { if (rmt_chan) { rmt_disable(rmt_chan); gpio_reset_pin((gpio_num_t)PWM_PIN); gpio_set_direction((gpio_num_t)PWM_PIN, GPIO_MODE_OUTPUT); gpio_set_level((gpio_num_t)PWM_PIN, 0); rmt_del_channel(rmt_chan); rmt_chan = nullptr; } } void setup() { Serial.begin(115200); delay(2000); Serial.println("Starting PWM Sweep..."); } void loop() { float freq = freqStart; float ton = tonStart; float freqRange = freqStart - freqEnd; float tonRange = tonEnd - tonStart; int steps = freqRange / -freqStep; for (int i = 0; i <= steps; i++) { freq = freqStart + i * freqStep; ton = tonStart + (tonRange * i / steps); setupRMT(freq, ton); float period_us = 1000000.0 / freq; Serial.printf("Freq: %.1f Hz | Ton: %.1f us | Period: %.1f us\n", freq, ton, period_us); delay(delayMs); } stopPWM(); Serial.println("PWM OFF for 5 seconds"); delay(offDelayMs); }
Debug Message
No crash or backtrace. The issue is in waveform stability and timing.
Other Steps to Reproduce
Output is connected to oscilloscope
Observe the waveform during frequency sweep
Clear gaps and interruptions are visible in ESP32-S3 output
Arduino Uno waveform is smooth and uninterrupted
I have checked existing issues, online documentation and the Troubleshooting Guide
- I confirm I have checked existing issues, online documentation and Troubleshooting guide.
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 2 replies
-
first to point out that you are using ESP-IDF APIs and not Arduino. Also you destroy the RMT channel on each change, instead of just modifying it. Last but not least, we have LEDC for PWM generation. While it's possible with RMT, LEDC is geared more towards PWM
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks for the idea of Ledc, with help of GPT, I tried below code but got error, can you please guide where I'm missing?
Code:
#include <Arduino.h> // --- Pin Setup --- #define PWM_PIN 1 #define LEDC_CHANNEL LEDC_CHANNEL_0 // --- Sweep Parameters --- const float freqStart = 28000.0; const float freqEnd = 24000.0; const float freqStep = -10.0; const float tonStart = 8.0; const float tonEnd = 21.0; const int delayMs = 5; const int offDelayMs = 5000; void setup() { Serial.begin(115200); delay(2000); Serial.println("Starting LEDC PWM Sweep..."); ledcSetup(LEDC_CHANNEL, freqStart, 16); ledcAttachPin(PWM_PIN, LEDC_CHANNEL); } void loop() { float freq = freqStart; float ton = tonStart; float freqRange = freqStart - freqEnd; float tonRange = tonEnd - tonStart; int steps = freqRange / -freqStep; for (int i = 0; i <= steps; i++) { freq = freqStart + i * freqStep; ton = tonStart + (tonRange * i / steps); float period_us = 1000000.0 / freq; float duty_ratio = ton / period_us; uint32_t duty = duty_ratio * 65535; ledcSetup(LEDC_CHANNEL, freq, 16); ledcWrite(LEDC_CHANNEL, duty); Serial.printf("Freq: %.1f Hz | Ton: %.1f us | Duty: %.1f%%\n", freq, ton, duty_ratio * 100.0); delay(delayMs); } ledcWrite(LEDC_CHANNEL, 0); Serial.println("PWM OFF for 5 seconds"); delay(offDelayMs); }
Error:
...\sketch_jul2d.ino: In function 'void setup()':
...\sketch_jul2d.ino:23:3: error: 'ledcSetup' was not declared in this scope
23 | ledcSetup(LEDC_CHANNEL, freqStart, 16);
| ^~~~~~~~~
...\sketch_jul2d.ino:24:3: error: 'ledcAttachPin' was not declared in this scope; did you mean 'ledcAttach'?
24 | ledcAttachPin(PWM_PIN, LEDC_CHANNEL);
| ^~~~~~~~~~~~~
| ledcAttach
...\sketch_jul2d.ino: In function 'void loop()':
...\sketch_jul2d.ino:42:5: error: 'ledcSetup' was not declared in this scope
42 | ledcSetup(LEDC_CHANNEL, freq, 16);
| ^~~~~~~~~
exit status 1
Compilation error: 'ledcSetup' was not declared in this scope
Beta Was this translation helpful? Give feedback.
All reactions
-
Use the examples in our repository as a reference or documentation.
Your code is using old API from 2.x versions.
Beta Was this translation helpful? Give feedback.