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

changing timer via serial usb? #1

Closed OhSoGood closed 6 years ago

OhSoGood commented 6 years ago

Hi

Thank you for your lib (and your time on it!).

A long-time developer but new to arduino world, I wish to create a sketch which:

All this is done on a maple mini board (stm32), connected via usb to a PC.

Naguissa commented 6 years ago

Yes, it's just what I made it for :-)

This library uses a non-abundant resource, a hardware timer, in order to provide the ability to accurately call a function in a timed manner.

Very important: You need to keep callback function (timed_function in this example) as fast as possible, specillay when using short intervals.

The way this library works is to manage only one callback function at a time, so to change interval you only need to call any attach function again; the library will clean old function and data and will use the new provided data.

This is a possible Skeleton:

#include "Arduino.h"
#include "uTimerLib.h"

// Initial timer value, in microseconds:
uint32_t us = 20000; // 20 milliseconds as default.

volatile bool GPIODataOutPutPinA = 0;
int pinA = 0;
// [...]
volatile bool GPIODataOutPutPinZ = 0;
int pinZ = 999;

// For Serial:
byte incomingByte;

void timed_function() {
    // GPIODataOutPutPinA ... GPIODataOutPutPinZ calculations, as needed. Maybe on loop
    digitalWrite(pinA, GPIODataOutPutPinA);
    // [...]
    digitalWrite(pinZ, GPIODataOutPutPinZ);
}

void setup() {
    Serial.begin(57600);
    TimerLib.setInterval_us(timed_function, us);
    prevMillis = millis();
}

void loop() {
    // GPIODataOutPutPinA ... GPIODataOutPutPinZ calculations, as needed. Maybe on timed_function

    if (Serial.available() > 0) {
        // read the incoming byte:
        incomingByte = Serial.read();
        // Do whatever....

        // You have a new timing? Apply it:
        us = 30000; // example of new timing, 30 milliseconds
        TimerLib.setInterval_us(timed_function, us);

    }

}
Naguissa commented 6 years ago

Was that useful? Any more question?

OhSoGood commented 6 years ago

Hi,

Thanks a lot for the reply, I had actually written the same code in the meantime and yours greatly helped me by making me feeling at ease (remember it's my first times with Arduino...).

Yet, I ran a small test sketch to check how strict the timer would be and I was surprised by the result. From what I understood, the timer were minimum values between two calls to the callback, and the duino would try to do its best. Yet, in my test where I use a timer of 33us (microsec) and where I record the min and the max between two calls, I observe that the calls may be done between 10 and 1034 us...

For memo, my board is a Maple Mini, which runs at 72MHz.

Is my code or my analysis wrong?

Thanks again for your help!

Here is my sketch:

#include "uTimerLib.h"

// How many microseconds between two timer calls, ie two GPIO batchs.
#define TIMER_INTERVAL_us 33
// The speed of the serial communication line with the host, in bauds.
#define SERIAL_BAUDS 9600

volatile int totalOccurences = 0;
volatile int minWaitTime = 100000000000;
volatile int maxWaitTime = 0;
volatile int prevStart = 0;
int launchTime_ms;

void setup() {
   // Initialize serial communications with host
   Serial.begin(SERIAL_BAUDS);

   // Initialize timer
   TimerLib.setInterval_us(timed_function, TIMER_INTERVAL_us);
   launchTime_ms = millis();
}

void loop() {
   // The loop listens to the host and takes order from it
   static bool isFirst = true;

   char c;
   while (Serial.available()) {
     c = Serial.read();
     if(isFirst)
     {
       isFirst = false;
       Serial.println("MinWait\tMaxWait\tCalls/sec\t#");
     }
     int nowTime_ms = millis();
     float ellapsed_s = (nowTime_ms - launchTime_ms) / 1000.0;

     // Beware: do not reuse twice volative variables (they may
change), copy them locally instead.
     Serial.print(minWaitTime);
     Serial.print("\t");
     Serial.print(maxWaitTime);
     Serial.print("\t");
     Serial.print(totalOccurences / ellapsed_s);
     Serial.print("\t");
     Serial.println(ellapsed_s);

     // Reset min/max values.
     minWaitTime = 100000000000;
     maxWaitTime = 0;
   }
}

/*
  * The timer callback.
  */
void timed_function() {
   unsigned int startTime = micros();

   unsigned int prevStartTime = prevStart;
   prevStart = startTime;
   unsigned int currentWait = startTime - prevStartTime;

   totalOccurences++;

   if(currentWait < 100000000) { // Tough way to manage overflow of
'micros'
     if(currentWait < minWaitTime) {
       minWaitTime = currentWait;
     } else if (currentWait > maxWaitTime) {
       maxWaitTime = currentWait;
     }
   }
}

Le 25/03/2018 à 19:20, Naguissa a écrit :

Was that useful? Any more question?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Naguissa/uTimerLib/issues/1#issuecomment-375987226, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuayotu1yNd2-hEfMUxFsI6H2Bmu3Ivks5th9HrgaJpZM4S4ZUw.

OhSoGood commented 6 years ago

Well, from test & trial, it seems the minimum interval between two calls (ie the value of TIMER_INTERVAL_us) is around 500us for the Maple Mini. Given the few instructions your code and mine do, and given the 72MHz cpu, that surprises me a lot.

Any thought about this?

Naguissa commented 6 years ago

It depends on callback function and program.

In your example, you're using Serial. Serial, when using STM32Duino with Maple Mini, also uses interrupts:

http://www.stm32duino.com/viewtopic.php?t=1139

This means interrupts may overlap.

Also there's an interrupt to maintain micros, milis, etc. language functionality. If you want more efficiency and/or shorter periods you need to disable that.

And, to sum up, you have all function calls in timer processing and interrupt processing, that sums-up a bunch of instructions.

So, at the end, you have 33*72 = 2376 max instructions in a 33us period interrupt, and it's quite little room to do complex operations (included uTimerLib operations). And will be affected by other interrupts (HardwareSerial, micros/milis/delay, etc).

If you want to disable time interrupts you need to disable systick timer: http://docs.leaflabs.com/static.leaflabs.com/pub/leaflabs/maple-docs/0.0.12/timers.html#jitter

systick_disable();
systick_enable();

And maybe not the best option, but you can overclock the maple mini if really needed, at expenses of loosing Serial AutoReset when programming (well, it fails a lot for me, on Linux...): http://storage8.static.itmages.com/i/18/0326/h_1522044675_8430203_d93488ee55.png

Naguissa commented 6 years ago

Oh, you also loose USB serial when Overclocking it!

OhSoGood commented 6 years ago

Thank you for the link about serial interrupts. I have actually inverted the logic: I put my high-rate code in the loop function and there, I managed to execute it every few microsec, which is nice. Even with the smallest code, timer interrupts were too slow (maybe still my fault and my bad code?).

I have yet to implement serial interrupts. I'll keep you informed if you wish.

Naguissa commented 6 years ago

No, no! You don't need to implement that, it's on the core.

What I was referring to is that all these interrupts are already running in your maple, as they are part of Arduino and STM32duino cores.

You can disable them if you want but they run by default (well, HardwareSerial only if you init the serial port).

OhSoGood commented 6 years ago

Oh... Maybe I incorrectly read http://www.stm32duino.com/viewtopic.php?f=18&t=1139&sid=3d73d30c8b87d6b46d6b9ba3b9714748&start=10 . I was thinking it could be possible to have a callback to manage input from SerialUsb (ie input from the host to the arduino), instead of polling SerialUsb in my loop method with a 'if(Serial.available() {...}' which is rather inefficient. Is that the case?

Le 26/03/2018 à 22:01, Naguissa a écrit :

No, no! You don't need to implement that, it's on the core.

What I was referring to is that all these interrupts are already running in your maple, as they are part of Arduino and STM32duino cores.

You can disable them if you want but they run by default (well, HardwareSerial only if you init the serial port).

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Naguissa/uTimerLib/issues/1#issuecomment-376292894, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuayuG3TKuxT8TpB7BO64xjrX3MtYqCks5tiUkHgaJpZM4S4ZUw.

Naguissa commented 6 years ago

You CAN write your own ISR for Serial but you don't need it.

The problem is all ISRs, as one ISR cannot interrupt other one, so millis/micros or Serial can delay UtimerLib ISR.

Naguissa commented 6 years ago

I have doing some tests, but still nothing conclusive.

As your sketch reports, there're some huge max times, like 1.000 us delayed. Ttested with different periods, always around 1000us delay.

But if you check interrupts per second number is correct.

That said, I manage two scenarios, unfortunately I can only prove them with a logic analyzer and I have no time to do whole setup now:

Explanation here: https://arduino.stackexchange.com/questions/22212/using-millis-and-micros-inside-an-interrupt-routine

I'll try an idea now.....

Naguissa commented 6 years ago

YES!!!

It's the "micros()" call on timed_function!

Test this modification!!

    #include "uTimerLib.h"

    // How many microseconds between two timer calls, ie two GPIO batchs.
    #define TIMER_INTERVAL_us 33
    // The speed of the serial communication line with the host, in bauds.
    #define SERIAL_BAUDS 9600

    volatile int totalOccurences = 0;
    volatile int minWaitTime = 100000000000;
    volatile int maxWaitTime = 0;
    volatile int prevStart = 0;
    int launchTime_ms;

    volatile unsigned int currentMicros = 0;

    void setup() {
       // Initialize serial communications with host
       Serial.begin(SERIAL_BAUDS);

       // Initialize timer
       TimerLib.setInterval_us(timed_function, TIMER_INTERVAL_us);
       launchTime_ms = millis();
    }

    void loop() {
       // The loop listens to the host and takes order from it
       static bool isFirst = true;
    currentMicros = micros();

       char c;
       while (Serial.available()) {
         c = Serial.read();
         if(isFirst)
         {
           isFirst = false;
           Serial.println("MinWait\tMaxWait\tCalls/sec\t#");
         }
         int nowTime_ms = millis();
         float ellapsed_s = (nowTime_ms - launchTime_ms) / 1000.0;

         // Beware: do not reuse twice volative variables (they may change), copy them locally instead.
         Serial.print(minWaitTime);
         Serial.print("\t");
         Serial.print(maxWaitTime);
         Serial.print("\t");
         Serial.print(totalOccurences / ellapsed_s);
         Serial.print("\t");
         Serial.println(ellapsed_s);

         // Reset min/max values.
         minWaitTime = 100000000000;
         maxWaitTime = 0;
       }
           currentMicros = micros();

    }

    /*
      * The timer callback.
      */
    void timed_function() {
       unsigned int startTime = currentMicros;

       unsigned int prevStartTime = prevStart;
       prevStart = startTime;
       unsigned int currentWait = startTime - prevStartTime;

       totalOccurences++;

       if(currentWait < 100000000) { // Tough way to manage overflow of 'micros'
         if(currentWait < minWaitTime) {
           minWaitTime = currentWait;
         } else if (currentWait > maxWaitTime) {
           maxWaitTime = currentWait;
         }
       }
    }
OhSoGood commented 6 years ago

Hi @Naguissa, You're kind of crazy man, as crazy as I think I am :) Good job, you had a nice analysis and/or intuition. I confirm that works on my maple mini (re :) ). Thanks!

I still wonder what's best, knowing that I'd need to write on several GPIOs (with BRSS) once every 33µS with as much regularity as possible,

  1. put the big / high-frequency GPIO-write job in the loop, and make my own UsbSerial UART handler
  2. put the big / high-frequency GPIO-write job in the loop, and listen to SerialUSB (aka Serial on STM32) within the loop.
  3. put the big / high-frequency GPIO-write job in your timer, and listen to SerialUSB in the loop function Do you have an opinion on it?
Naguissa commented 6 years ago

Yes, more or less... ;-) You are welcome!

I have one similar project (but with longer period) and I will go with option 3.

But, for writing GPIO, I would consider using direct PORT writing in your case, because digitalWrite adds a lot of extra instructions and can make it skip cycles. You can always use digitalWrite and change it later if you observe problems:

https://jeelabs.org/2010/01/06/pin-io-performance/

In short, digitalRead / digitalWrite does a lot of checks and operations that makes it slower. It's safier, but slower....