Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changing timer via serial usb? #1

Closed
OhSoGood opened this issue Mar 23, 2018 · 14 comments
Closed

changing timer via serial usb? #1

OhSoGood opened this issue Mar 23, 2018 · 14 comments
Assignees
Labels
good first issue Good for newcomers question Further information is requested

Comments

@OhSoGood
Copy link

OhSoGood commented Mar 23, 2018

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:

  • writes to gpio on a high frequency (every 20-30 microsec a batch of 3-5 writes on as many pins) and,
  • from time to time, the host computer connected via serial (actually serial USB) will ask the sketch to change the high/low state of these pins.

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

  • You lib would be perfectly fitted for this purpose, isn't it?
  • Could you advise me on how the sktech should "listen" to the host computer for the change? serialevent?
    You advice would be welcome, as I'm really starting with arduino/stm32.
@Naguissa
Copy link
Owner

  • You lib would be perfectly fitted for this purpose, isn't it?

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.

  • Could you advise me on how the sktech should "listen" to the host computer for the change? serialevent?
    You advice would be welcome, as I'm really starting with arduino/stm32.

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 Naguissa self-assigned this Mar 23, 2018
@Naguissa Naguissa added good first issue Good for newcomers question Further information is requested labels Mar 25, 2018
@Naguissa
Copy link
Owner

Was that useful? Any more question?

@OhSoGood
Copy link
Author

OhSoGood commented Mar 25, 2018 via email

@OhSoGood
Copy link
Author

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
Copy link
Owner

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
Copy link
Owner

Oh, you also loose USB serial when Overclocking it!

@OhSoGood
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

OhSoGood commented Mar 26, 2018 via email

@Naguissa
Copy link
Owner

Naguissa commented Mar 26, 2018

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
Copy link
Owner

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:

  • Serial and systimer producing delays to uTimerLib interrupt. It can happen quite easily, but it's very strange that delay is always same number of microseconds.

  • Systimer not updating correctly when we are at our interrupts, so micos/milis functions can give some wrong value from time to time. It's quite strange but it could explain why we always have same delay (1000us) but still getting correct execution number by period.

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

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

@Naguissa
Copy link
Owner

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
Copy link
Author

OhSoGood commented Mar 29, 2018

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
Copy link
Owner

Naguissa commented Mar 30, 2018

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....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants