Need to update timer within ISR

ESP32-S2 (Wemos/Lolin S2 mini)
Arduino 2.1.1 on Windows 11

Hello,

Thank you for taking the time to read this post and for any assistance you can provide. I'm a newbie when it comes to, what I consider, the more advanced aspects of microcontrollers.

My goal is to output two LED PWM channels using hardware timers to control the frequency and duty cycle of the strobing LEDs. Here are the specific requirements:
A. The LEDs need to be power-controlled (I've been using the ledc function)
B. The LEDs should be able to be strobed at specific intervals within the range of 0 to 1KHz. I'm using hardware timers so blocking functions such as non-DMA SPI updates of the display does not interfere with the strobe.
C. The duty cycle of the strobe needs to be adjustable as a float percentage ranging from 0.01 to 99.99% uptime per cycle.

Unfortunately, I've encountered an issue where I can either (unreliably) time the strobe and duty cycle or (somewhat more reliably) control the PWM power modulation, but not both simultaneously. I've tried various combinations of timer bindings, ledc channels, timing libraries, ISR handling, and rewriting the code from scratch multiple times.

I apologize for the messy state of the code, as it has undergone numerous modifications, and I'm fried at looking at the code. I would greatly appreciate any assistance or pointers you can provide to help resolve this issue.

This short segment of code seems to sometimes activate the timers, but it never seems to activate the shutoff timer reliably.

Thank you once again for your help!

#include <esp32-hal-timer.h>

// Pin assignments
#define LED_660    17
#define LED_850    16

// Intensity control variables (percentage)
int pwr_850 = 0;
int pwr_660 = 0;

// Strobe control variables
float led_hz = 1.0;   // Strobe cycles per second
float led_duty = 50.0;    // Duty cycle percentage

// Timing variables
volatile unsigned long on_time;
volatile unsigned long off_time;

hw_timer_t *timer_on = NULL;
hw_timer_t *timer_off = NULL;

bool leds_on = false;  // Flag to track the state of LEDs

void IRAM_ATTR onTimer() {
  ledcWrite(2, 254);
  ledcWrite(3, 254);
  timerAlarmDisable(timer_off);
  timerAlarmWrite(timer_on, on_time, true);
  timerAlarmEnable(timer_on);
}

void IRAM_ATTR offTimer() {
  ledcWrite(2, 0);
  ledcWrite(3, 0);
  timerAlarmDisable(timer_on);
  timerAlarmWrite(timer_off, off_time, true);
  timerAlarmEnable(timer_off);
}

void startLEDs() {
  ledcSetup(2, 4000, 8);  // 4 kHz PWM, 8-bit resolution
  ledcSetup(3, 4000, 8);  // 4 kHz PWM, 8-bit resolution
  ledcAttachPin(LED_660, 2);
  ledcAttachPin(LED_850, 3);
  if (led_hz == 0) {
    ledcWrite(2, (pwr_660 / 100.0) * 255);
    ledcWrite(3, (pwr_850 / 100.0) * 255);
    leds_on = true;
  } 
  else if (!leds_on && led_hz > 0.0) {
    // Calculate on/off times based on strobe parameters
    unsigned long cycle_time = 1000000.0 / led_hz;  // Convert Hz to microseconds
    off_time = ((cycle_time * led_duty) / 100);
    on_time = (cycle_time - off_time);

    // Update timer alarms
    timerAlarmWrite(timer_on, on_time, true);
    timerAlarmWrite(timer_off, off_time, true);

    // Enable timers
    timerAlarmEnable(timer_on);
    timerAlarmEnable(timer_off);  // Enable this at the same time as timerOn
    leds_on = true;
  }
}

void stopLEDs() {
  if (leds_on) {
    ledcWrite(1, 0);
    ledcWrite(0, 0);
    timerAlarmDisable(timer_on);
    timerAlarmDisable(timer_off);
    leds_on = false;
  }
}

void setup() {
  pinMode(LED_660, OUTPUT);
  pinMode(LED_850, OUTPUT);
  pinMode(15, OUTPUT);

  // Create timers
  timer_on = timerBegin(0, 80, true);  // Timer 0, prescaler 80 (1MHz)
  timer_off = timerBegin(1, 80, true);  // Timer 1, prescaler 80 (1MHz)

  // Attach interrupts
  timerAttachInterrupt(timer_on, &onTimer, true);
  timerAttachInterrupt(timer_off, &offTimer, true);
}

void loop() {

pwr_850 = 30;
pwr_660 = 30;
    digitalWrite(15, 1);
    led_hz = 1;   // Strobe cycles per second
    led_duty = 50;
    startLEDs();
    delay(5000);
    stopLEDs();

pwr_850 = 30;
pwr_660 = 30;
    digitalWrite(15, 0);
    led_hz = 2;   // Strobe cycles per second
    led_duty = 50;
    startLEDs();
    delay(5000);
    stopLEDs();

pwr_850 = 30;
pwr_660 = 30;
    digitalWrite(15, 1);
    led_hz = 5;   // Strobe cycles per second
    led_duty = 50;
    startLEDs();
    delay(5000);
    stopLEDs();

pwr_850 = 30;
pwr_660 = 30;
    digitalWrite(15, 0);
    led_hz = 10;   // Strobe cycles per second
    led_duty = 50;
    startLEDs();
    delay(5000);
    stopLEDs();
}

I haven't looked at it in much detail, but perhaps you can use the ESP32's Remote Control Transceiver (RMT) peripheral. You could run the PWM using the carrier signal and the strobing using the timing values from the RAM block. See Chapter 16 of the ESP32 Technical Reference Manual and the RMT API Definition.

Thank you for the reply! I will give it a look and see if i can get it working.

Well, lets first talk about your frequencies. Your LED Hz is WAAY too low, for either the strobe or the pwm of the LEDs themselves. The strobing effect is going to need to be timed between 50-70 Hz and the PWM output of the LEDs is going to need to be something like 1 kHz. But when you attach your timers, you're using a prescaler of 80?

timer_on = timerBegin(0, 80, true); // Timer 0, prescaler 80 (1MHz)
timer_off = timerBegin(1, 80, true); // Timer 1, prescaler 80 (1MHz)

That's 1 MHz. the reason it appears that the LEDs never turn off is because they are turning off for such a short period of time you can't see it.

Then in your loop you are waiting 5000 ms? Five full seconds? What are you trying to achieve there? turning the strobing on and off every 5 seconds?

Also, the LEDC library is going to give you a PWM output. There's no need to also run that through a timer. Yo assign the PWM frequency to the LEDC output and t handles the timing for you. You would just have to control turning the PWM output on and off to achieve the Strobe effect.

Issue found:
I was only writing to alarm values, not the actual timer which I would need to reset each time as auto reload will not work due to the "tick-tock" of the two timers. Also, there is a semantics issue in the Espressif reference:
Restart does not actually restart the timer from go, as implied, it resumes a stopped count. The only way to reset the timer without auto-reload is with a

timerAlarmDisable(timer); 
timerStop(timer);
timerWrite(timer, ticks starting point ie: 0);
timerStart(timer);
timerAlarmWrite(timer, ticks, autoreload bool);
timerAlarmEnable(timer); 

command set.
This does not need to be done all at once, and technically you may not have to stop the timer and disable the alarm, but the complete process is listed for reference.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.