Naguissa / uTimerLib

Arduino tiny and cross-device compatible timer library
https://www.foroelectro.net/electronica-digital-microcontroladores-f8/utimerlib-libreria-arduino-para-eventos-temporizad-t191.html
GNU Lesser General Public License v3.0
20 stars 9 forks source link

Small timeout trigger callback_function immediately (ATMEGA328) #6

Closed evandrorech closed 5 years ago

evandrorech commented 5 years ago

I have noticed that when using the TimerLib.setTimeout_us(callback_function, microseconds) function with a very small time (I tested up to 8000 us), the callback function is called immediately after executing the command. This occurs both when calling the execute function in setup() and within an ISR. My initial application is a trigger pulse generator for SCRs in controlled rectifiers. I have not tested the maximum time that this occurs and even if other functions have similar behavior.

A workaround is to make the first callback function call a second (which occurs immediately), and the second call the correct function at the expected time, ie a callback function calling another operates normally. Below is a program to check for this failure, modify the commented lines accordingly to test the workaround. I tested it on an Arduino Nano v3 with ATMEGA328.

#include <uTimerLib.h>

const byte interruptPin = 2;
volatile unsigned long prevMicros;

void setup() {
  Serial.begin(9600);
  pinMode(interruptPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(interruptPin), pinInt, RISING);
  prevMicros=micros();
  TimerLib.setTimeout_us(timer, 8000); // change to trigger to workaround
}

void loop() {
}

void pinInt() {
  Serial.print("Interrupt set: ");
  Serial.println(micros()-prevMicros);
  prevMicros=micros();
  TimerLib.setTimeout_us(timer, 8000); // change to trigger to workaround
}

void trigger(void){ // that occurs immediately, but calls the next at correct time
  TimerLib.setTimeout_us(timer, 8000);
}

void timer(void){ // if that is called first, it occurs immediately
  Serial.print("Timer time: ");
  Serial.println(micros()-prevMicros);
}

The workaround isn't the best solution because it causes overhead, but I didn't figure out in the library why this occurs.

Naguissa commented 5 years ago

Hello,

Found and solved. AVRs are firing an interrupt just after re-enabling interrupts, even after clearing the flag, so I've added an extra internal loop (_overflow) to compensate.

Take in mind that there's a small delay while processing the function, something around 128us, and this will affect to very small timings.

Checked with this code:

#include <uTimerLib.h>

volatile unsigned long int prevMicros = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Start");

  prevMicros = micros();
  TimerLib.setTimeout_us(timer, 20000);
  delay(2000);

  prevMicros = micros();
  TimerLib.setTimeout_us(timer, 15000);
  delay(2000);

  prevMicros = micros();
  TimerLib.setTimeout_us(timer, 10000);
  delay(2000);

  prevMicros = micros();
  TimerLib.setTimeout_us(timer, 5000);
  delay(2000);

  prevMicros = micros();
  TimerLib.setTimeout_us(timer, 1000);
}

void loop() {
}

void timer(void){ // if that is called first, it occurs immediately
  Serial.print("Timer time: ");
  Serial.println(micros() - prevMicros);
}

Thanks for reporting!

Naguissa commented 5 years ago

Oh! Solved and released! You can use Arduino lubrary manager updates or find it here:

https://github.com/Naguissa/uTimerLib/releases/tag/1.1.2

evandrorech commented 5 years ago

Thanks! That solved the problem! But based in your analisys, I think I figured out why this occurs, is a similar problem that I had in the TimerOne.h library. Clearing the counter generates a interrupt, but if you modify its contents when it is running, appearly it sets the interrupt flag too.

Based on this, I made some modifications on the 1.1.1 library version, that solves the problem too, and the timing will be like of your solution, but I think that is more deterministic. I attach the files here, basically, I modified your cleartimer() to stop the clock during the timer setup (and after the timeout), clear its flag and only set the prescaler before reenabling interrupts. I commented all modifications and added a "****" to you locate it. Sorry if I ended up introducing any other bug in the code.

I'll still look at ways to prevent this overhead for very small times, if I come up with something, I'll let you know.

Thank you!

src.zip

Naguissa commented 5 years ago

Thanks! I'll check it and integrate the solution.

Naguissa commented 5 years ago

I've checked the code but I still see a problem: If you change from a fast timing to a slow one that may cause an overflow interruption.

Being this, I still prefer forcing that interrupt and adding it to overflows. This way it stays compensated in any case, and also I can simplify some code (this way I ALWAYS have overflows, so I don't need to check this on attachment).