ESP pulse output resolution

Hey everyone! I've had a good search but can't find an answer :frowning:
Using an ESP32 Wroom I need to generate three concurrent pulses with an "on" time of 5us with a frequency of around 1KHz. Programatically I don't see this is a problem (digWrite HIGH, delayMicrosec 5, digWrite Low, rinse and repeat 2 more times then wait about 1ms). Just another point, I would like the pulses to overlap by 1us).

So, I suppose the real question is, is this possible with the ESP32? Is the microsecond resolution good enough? Should I be looking at something other than digitalWrite and delayMicroseconds?

TIA :slight_smile:

Yes, but if the signal is supposed to be reliable/stable, you should look at either the RMT (Remote Control) or maybe the LEDC (LED Control) peripheral of the ESP32. I think RMT would be the most ideal for this.

1 Like

Could you draw the signal on a piece of paper ?
One output pin ? three pulses ? and then overlap ? and also with 1kHz ? :thinking:

Can you tell what your project is about ?

I was looking at the LEDC library - looks like it has a fixed 50% duty cycle whereas I need about 0.5% (5us on every 1ms). In saying that, perhaps the LEDC code would be a good place to start for ideas!
I do need the signal quite accurate. I could probably cope with a 5 to 10% error but not more.


This part of the project is to control a CD4066 bilateral switch to extract a small portion of a signal for analyses. In past iterations of the project we used various astable oscillators with a lot of phase shifting to control the switch but, as we were using the ESP32 for other parts of the circuit, I thought it would be a great idea to use it to control the three gates. Hope that makes sense :smiley:

Also @Koepel I will be using 3 output pins, one for each pulse

That was easy:

// Arduino Uno at 16MHz
// Best accuracy with an Arduino Uno that has a crystal instead of resonator.
// A NOP takes 62.5 ns.
// A change of a pin with writing to PINB also takes 62.5 ns.
// There are 16 NOP in a microsecond.
// Does the "while(true)" take time ?

void setup() 
  pinMode( 8, OUTPUT);                   // pin 8  = PORTB bit 0
  pinMode( 9, OUTPUT);                   // pin 9  = PORTB bit 1
  pinMode( 10, OUTPUT);                  // pin 10 = PORTB bit 2

  noInterrupts();                        // Stop Arduino services

  while( true)                           // forever
    PINB = bit( 0);                      // HIGH
    __builtin_avr_delay_cycles (63);     // 4 us = 64 NOP (-1 for PINB instruction)
    PINB = bit( 1);                      // HIGH
    __builtin_avr_delay_cycles (15);     // 1 us = 16 NOP
    PINB = bit( 0);                      // LOW
    __builtin_avr_delay_cycles (47);     // 3 us = 48 NOP
    PINB = bit( 2);                      // HIGH
    __builtin_avr_delay_cycles (15);     // 1 us = 16 NOP
    PINB = bit( 1);                      // LOW
    __builtin_avr_delay_cycles (63);     // 4 us = 64 NOP
    PINB = bit( 2);                      // LOW
    __builtin_avr_delay_cycles (15791);  // 16000 NOP minus all the other code

void loop() { }

The accuracy is 100%, it depends on the crystal or resonator of the Arduino Uno.
I have to admit that I only tested it in a simulation.
You can open the link to the Wokwi simulation, but don't start it unless you know how to use the Wokwi Logic Analyzer.

I don't think so. I can turn off all the Arduino services on a Arduino Uno, but you can not turn off the services of a ESP32.
I don't think that the LEDC is flexible enough for the overlap.
The RP2040 has programmable hardware blocks, those should be able to do that.

If you use the RMT peripheral with the APB clock (80Mhz) as source, then you can use 80 as channel divider, giving 1Mhz (1hz per micros). For each 5us pulse you would need the rmt_item32_t.duration[0|1] to be 5 and that makes it quite easy to do with an ESP32. To do the offsetting, the first rmt_item would just need to be low for 4us for the second and 8us for the third pin.

Look at the morse code example for a better understanding of how RMT works.

Stumbled upon this library.

Details: 3-pins (4,5,12), 5µs width, 1000Hz, 10-bit PWM, starts at µs 0, 4, 8.

This might work (untested). Just #include "analogWrite.h" and put these 3 lines of code in setup.

analogWrite(4, 5, 1000, 10, 0);
analogWrite(5, 5, 1000, 10, 4);
analogWrite(12, 5, 1000, 10, 8);

EDIT 1: If getting all 3 signals to synchronize properly is an issue, then using the ESP32's MCPWM hardware might be an option.

EDIT 2: ... or maybe increase the bit resolution up to 16, then if each signal is one bit-time out of sync, the % timing error wont' be as high as for 10-bit. You'll need to re-calculate the width and phase-shift values.

Really ? I think you wrote it yourself.

1 Like

Great, and now you need to ask your self if a frequency of 1000Hz actually gives you one microsecond per cycle.

Right ... the frequency's fine (1ms per cycle), but 10-bit resolution only breaks it down by 10 bit widths (I mistakenly was thinking 1024 steps).

Yes, and how does that help OP if 5µs pulses are needed?

It doesn't. Too bad we can't get 10-bit resolution at 100kHz, then the interrupts of a 1kHz fourth timer could control the pulse pattern every millisecond (the ESP32's max PWM frequency at 10-bit is 78.125KHz).

@alphabetix, how about using digitalWrite and delayMicroseconds to create a function that produces the required pattern ... it only lasts 13µs. Then use a timer's interrupt to call this function every millisecond.

And turn off other interrupts during those 13 µs, or is that already so ?

Yes, I meant to refer to your example in reply#8 ... like that, but have it in a small function called by a timer. I haven't worked with ESP32's timer interrupts yet, so can't elaborate much further.

I ended up using the MCPWM library with a sync delay between two timers

mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SELECT_SYNC_INT0, 96);
MCPWM0.timer[0].sync.out_sel = 1;
MCPWM0.timer[0].sync.out_sel = 0;

Where "96" is the delay between the first two pulses

Thank you everyone for your help. It definitely put me on the correct path.