ESP32 using one periodic timer to reset multiple one shot timers

I am working with an ESP32 Dev Kit C and the Arduino framework.
I want to use timers to output a sequence where in a 2 ms period an interrupt is fired after 150 µs and another interrupt after 1850 µs. Then there is some work to be done at 2 ms and the cycle will repeat.

I am trying to achieve this with 2 timers in one shot mode (alarm at 150µs and 1850µs) and a third timer with an alarm at 2000 µs and auto-reload.
In the ISR of the third timer both one shot timers will be reset.

After some back and forth with the arduino library I got one one shot timer working together with the periodic timer, but never both one shot timers regardless of the combination of timer channels used. I think the timers should be independent from each other but every time I add the third timer, things go south.

Now I tried to manipulate the registers directly using TIMERG0 and TIMERG1. But all I get from this is some spikes on the GPIO Pins and not the desired square wave. The spikes have a frequency of 2 ms so I think something is wrong with the interrupt handling.

I have no knowledge about the FreeRTOS so every input on how I can set this up is highly appreciated!

Current MWE producing spikes on both GPIO pins (GPIO action is a place holder for some slightly more complex logic which I want to implement later):

#include <Arduino.h>
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"

hw_timer_t *timerA, *timerB, *timerO = NULL;
const uint32_t alarmValues[2] = {150, 1850};
void IRAM_ATTR timer_isr_A();
void IRAM_ATTR timer_isr_B();
void IRAM_ATTR timer_isr_O(); // "overflow"

const uint8_t pinA = 19;
const uint8_t pinB = 18;

void setup()
{
  pinMode(pinA, OUTPUT);
  pinMode(pinB, OUTPUT);

  const uint16_t prescale = 80; // timer frequency = 1 MHz -> 1 µs
  timerA = timerBegin(0, prescale, true);
  timerB = timerBegin(1, prescale, true);
  timerO = timerBegin(2, prescale, true);
  timerAttachInterrupt(timerA, &timer_isr_A, true);
  timerAttachInterrupt(timerB, &timer_isr_B, true);
  timerAttachInterrupt(timerO, &timer_isr_O, true);
  timerAlarmWrite(timerA, alarmValues[0], false);
  timerAlarmWrite(timerB, alarmValues[1], false);
  timerAlarmWrite(timerO, 2000, true);
  timerAlarmEnable(timerA);
  timerAlarmEnable(timerB);
  timerAlarmEnable(timerO);
}

void IRAM_ATTR timer_isr_A()
{
  digitalWrite(pinB, 1);
}

void IRAM_ATTR timer_isr_B()
{
  digitalWrite(pinA, 1);
}

void IRAM_ATTR timer_isr_O()
{
  digitalWrite(pinA, 0);
  digitalWrite(pinB, 0);
  // Reset timerA and timerB (group 0) directly with registers, because API seems limited when called from timer0 wich is on the other timer group 1.
  TIMERG0.hw_timer[0].config.enable = 0;
  TIMERG0.hw_timer[1].config.enable = 0;

  TIMERG0.hw_timer[0].cnt_low = 0; // reset counter
  TIMERG0.hw_timer[0].cnt_high = 0;
  TIMERG0.hw_timer[0].alarm_low = alarmValues[0]; // reload alarm, necessary in one shot mode
  TIMERG0.hw_timer[0].alarm_high = 0;
  TIMERG0.hw_timer[0].config.alarm_en = 1; // re-enable alarm

  TIMERG0.hw_timer[1].cnt_low = 0; // reset counter
  TIMERG0.hw_timer[1].cnt_high = 0;
  TIMERG0.hw_timer[1].alarm_low = alarmValues[1]; // reload alarm, necessary in one shot mode
  TIMERG0.hw_timer[1].alarm_high = 0;
  TIMERG0.hw_timer[1].config.alarm_en = 1; // re-enable alarm

  TIMERG0.hw_timer[0].config.enable = 1;
  TIMERG0.hw_timer[1].config.enable = 1;
}

void loop()
{
}

Addendum 1
Before using TIMERGx I tried to use all three timers with auto-load and just reset the timers for 150µs and 850µs in the 2ms ISR:

...
timerAlarmWrite(timerA, alarmValues[0], true);
timerAlarmWrite(timerB, alarmValues[1], true);
...

and

void IRAM_ATTR timer_isr_O()
{
  digitalWrite(pinA, 0);
  digitalWrite(pinB, 0);

  timerAlarmDisable(timerA); // Disable the timer
  timerAlarmDisable(timerB); // Disable the timer
  timerWrite(timerA, 0);     // Reset the timer counter to 0
  timerWrite(timerB, 0);     // Reset the timer counter to 0
  timerAlarmEnable(timerA);
  timerAlarmEnable(timerB);
}

How accurate does the timing need to be? I've done similar tasks using a Nano, UNO, and WeMos D1 R1, where I count ticks and decrement counters down to zero. When the counter reaches zero, the timer is considered "timed out." In my implementation, I set the timer and check it during each pass through the main loop. There is some jitter and error, of course, since there's no highly accurate time base, like a crystal.

If this approach might be helpful to you, I can share the code.

just use one ESP32 timer on interrupt change interrupt period, e.g.

// ESP32 timer inteerrupts -   150uSec pulse every 2mSec

#define pin 19

hw_timer_t *timer = NULL;

// timer ISR invert pin level
volatile int counter = 0;
void ARDUINO_ISR_ATTR onTimer() {
  static int timer1=1850;
  static uint32_t level = 0;
  counter++;
  // digitalWrite(pin, !digitalRead(pin));
  gpio_set_level((gpio_num_t)pin, level);  //!gpio_get_level((gpio_num_t)pin));
  level = !level;
  // set next interrupt time
  timerAlarm(timer, timer1, true, 0);
  if(timer1==150)timer1=1850; else timer1=150;
}

void setup() {
  Serial.begin(115200);
  pinMode(pin, OUTPUT);
  timer = timerBegin(1000000);            // Set timer frequency Mhz
  timerAttachInterrupt(timer, &onTimer);  //  Attach onTimer function to our timer.
  // Set alarm to call onTimer function(value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(timer, 150, true, 0);        // start with 150uSec pulse
}

void loop() {
  // display counter every second
  static long timer1 = millis();
  if (millis() - timer1 > 1000) {
    timer1 = millis();
    Serial.println(counter);
    counter = 0;
  }
}

pulse every 2mSec
image

pulse is 150uSec wide
image

Thank you for your input, @horace. I can't get your code to work because timerAlarm(timer, timer1, true, 0); ist not defined. Are you using a special library?
Also, don't you need to restart the timer or reset the counter? Or is this done in timerAlarm automatically?

@gilshultz, every input is welcome! Although, the timing needs to be pretty accurate because it will be used to multiplex a visiual display component and it does not like too much jitter.

you could be running ESP32 core Version 2
there have been major changes from V2 to V3 - see Migration from ESP32 Arduino Core 2.x to 3.0 in particular the timer - see Arduino-ESP32 Timer API

to check what version you have installed click Tools>Board then Board Manager and enter ESP32, e.g. I have V3.0.5 installed

no, the code runs as it is

EDIT: this works under ESP32 core V2.0.17

// ESP32 timer interrupts -   150uSec pulse every 2mSec

#define pin 19

hw_timer_t *timer = NULL;

// timer ISR invert pin level
volatile int counter = 0;
void ARDUINO_ISR_ATTR onTimer() {
  static int timer1 = 1850;
  static uint32_t level = 0;
  counter++;
  // digitalWrite(pin, !digitalRead(pin));
  gpio_set_level((gpio_num_t)pin, level);  //!gpio_get_level((gpio_num_t)pin));
  level = !level;
  // set next interrupt time
  timerAlarmWrite(timer, timer1, true);
  if (timer1 == 150) timer1 = 1850;
  else timer1 = 150;
}

void setup() {
  Serial.begin(115200);
  pinMode(pin, OUTPUT);
  timer = timerBegin(0, 80, true);              // Set timer frequency Mhz
  timerAttachInterrupt(timer, &onTimer, true);  //  Attach onTimer function to our timer.
  // Set alarm to call onTimer function(value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarmWrite(timer, 150, true);  // start with 150uSec pulse
  timerAlarmEnable(timer);            // enable timer
}

void loop() {
  // display counter every second
  static long timer1 = millis();
  if (millis() - timer1 > 1000) {
    timer1 = millis();
    Serial.println(counter);
    counter = 0;
  }
}

@horace, awesome!

I am using PlatformIO and was not aware that there is an ongoing dispute about ESP32 Arduino 3.0+. Eventually, I got it working thanks to this and that.

Also thanks for sharing the V2 version, although I did not try it.

I got the following working with ESP32 Arduino 3.0.5 with the pattern I need (0µs ---150µs --- 1700µs --- 150 µs = 2ms):

#include <Arduino.h>

hw_timer_t *timer = NULL;

const uint32_t timerAlarmVals[] = {150, 1700, 150};
uint8_t timerCyc = 0;

void ARDUINO_ISR_ATTR timer_isr();

const uint8_t pinA = 19;

void setup()
{
  Serial.begin(115200);

  pinMode(pinA, OUTPUT);

  timer = timerBegin(1000000);
  timerAttachInterrupt(timer, &timer_isr);
  timerAlarm(timer, timerAlarmVals[timerCyc], true, 0);
}

void ARDUINO_ISR_ATTR timer_isr()
{
  uint8_t timerCycCurr = timerCyc;

  if (timerCycCurr == 0)
  {
    digitalWrite(pinA, 1);
  }
  else if (timerCycCurr == 1)
  {
    digitalWrite(pinA, 0);
  }
  timerCyc = (++timerCyc) % 3;
 
  timerAlarm(timer, timerAlarmVals[timerCyc], true, 0);
}

void loop()
{
}