I am using a ESP32S3 in Arduino IDE.
I have an interrupt that arrives on a GPIO and from that one I need to run two functions to enable other two GPIOS with different timings, the problem is that the accuracy of the execution of my two functions has to be within 1 usec. I do not need that the function starts in 1 usec, I can accept a delay, but the delay should be fixed with a maximum error of 1 usec. My current code is the following and it works but the error on the start of the signal is within 5 usec, so I should reduce of a factor 5 this.
I am open to change completely this architecture with something completely different, but I haven't found a better approach so far.
OK, so why not just perform these tasks in the same routine and use some kind of (blocking) delay between setting the two GPIO outputs? Seems like this could be done on a single core with simple code. What's wrong with such a basic approach?
I tried it but failed. If I put the content of one of the two routines (like updatex using the volatile variables) into the interrupt handler I have the signal generated within the desired 1usec precision, but I haven’t found a way to handle both.
If you can give me some code suggestion of what could work it would be very helpful, thanks.
I ended up splitting in two tasks because I failed in running everything within the interrupt handler. Could you please help me a bit more how I can do on a single routine in the handler?
How much time do you want/need to have between the incoming signal (interrupt) and starting to flip the GPIO's in response? How much time between the GPIO flips (x and y routines)? I understand you have +/-1 usec margin between the x and y parts, but how much play do you have in the interrupt-to-action_x timing?
The exact sequence of event that should happens is:
the trigger arrives
in parallelI have to wait two variable delays, one for each GPIO and then rise both, each one after his own delay. The delay can be between 250 and 500 usec. The jitter in rising cannot be larger than 1usec.
after 512 usec from the trigger, I turn off both pins.
The two variable delay are set by another asynchronous routine (imagine in the loop or somewhere else) but once the delay is set the timing should be reproducible within 1usec.
So let’s say that I set a 310 usec for one GPIO and 400 for the second, then until I change the delay both signals should be flipped at 310±1 and 400±1 usec
from the trigger.
The two pin can also have the same delay, for example 310 and 310 usec.
I add an image on the oscilloscope for 1 GPIO rise. The yellow signal is the trigger, the blue is one of the two GPIO rise. In this case the end time of the GPIO is not exactly 512 usec but ends a bit earlier.
OK, so in this case, I'd start with the simplest possible option, which is to simply delay (i.e. blocking) your core in the ISR for 250-500usec. Yes, that sounds filthy, but locking up the core for 0.512ms is probably sufferable and keeps things simple. Then raise the one GPIO, wait some more (blocking), raise the other GPIO. Delay until the 512us are up.
If that doesn't work, try a more complicated approach.
To make the delay happen, either let the core count with nop's or use delayMicroseconds.
I used this approach at the beginning. The problem is that once I do the first delay and I rise the first GPIO I can pass already the delay of the second GPIO if those are very close (or even the same), so I do not have the time to do one after the other, that is why I was implementing a parallel solution.
Looks like you should be able to flip a GPIO within 1 usec on an ESP32 platform. Maybe try to cut down the Arduino code overhead by some sort of low-level coding? I've never done this on ESP32 because I feel its strengths are in different areas.
The flip happens within 1 usec but when I restart the counter (I do with a for loop of nop) the creation of the for is quite long it seems. With the Arduino exists a solution with two timers, but I need the USB part of the ESP32S3. (Life is complicated sometime..)
How much time does the overhead of the for-loop involve? You might find that an alternative like a while-loop somehow compiles differently and ends up being slightly faster.
Then, check if you have enough time for the loop and if not, manage the delay in a different way; since you'll be dealing with times <2us or so (my guess) I think you can get away with having the controller do something like a silly division of two floats or something.
interesting suggestion, I will do some tentative here. Anyway I am happy that the solution is not so trivial otherwise I would feel very stupid.
Thanks for your help, if I solve it I will write here the solution.
Disclaimer: I'm not familiar with ESPs nor with multitasking on them.
Below is normal Arduino code; you will need to adjust for your ESP. The code uses a for-loop for the total number of nop cycles; in that for-loop, it checks if an output must be made high or low.
I used a struct to store the pin, the number of nop cycles (from 0) before the output must go high and the number of nop cycles (from 0) before it must go low again.
The function calcMaxNops() calculates the number of nops needed for the complete cycle (both outputs).
The function controlOutputs() will be your ISR / Task.
#define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))
struct OUTPIN
{
const uint8_t pin;
uint32_t highStart; // when to set output high
uint32_t highEnd; // when to end output high
};
OUTPIN pins[]
{
{6, 100, 200},
{7, 100, 200},
};
uint32_t maxNops = 0;
void setup()
{
Serial.begin(115200);
for (uint8_t cnt = 0; cnt < NUMELEMENTS(pins); cnt++)
{
pinMode(pins[cnt].pin, OUTPUT);
}
}
void loop()
{
calcMaxNops();
controlOutputs();
}
void calcMaxNops()
{
maxNops = 0;
for (uint8_t cnt = 0; cnt < NUMELEMENTS(pins); cnt++)
{
if (maxNops > pins[cnt].highEnd)
{
maxNops = pins[cnt].highEnd;
}
}
}
void controlOutputs()
{
// nop counter
for (uint32_t nopCnt = 0; nopCnt < maxNops; nopCnt++)
{
// control each pin
for (uint8_t cnt = 0; cnt < NUMELEMENTS(pins); cnt++)
{
if (nopCnt > pins[cnt].highStart && nopCnt <= pins[cnt].highEnd)
{
digitalWrite(pins[cnt].pin, HIGH);
}
else
{
digitalWrite(pins[cnt].pin, LOW);
}
}
}
}
I hope that this gives a different perspective. Note that I used digitalWrite(); you'll have to change that. Also note that it should be reasonably easy to change this to normal non-blocking code.
thanks I see your point, I think to loop over pins and do the if at each step of the nop adds quite a lot of jitter in the proper time when the state is risen, but I will give a try, thanks a lot!
basically what I do here is to set 4 timers. When the interrupt occurs I start the two timers for the initial delay, then I turn on the GPIOs and start the second timers to wait before shut down the GPIOs. I think that I can improve a bit using hardware timers instead of the API that I am using, but this is already within 1 usec of delay (barely).