Skip to content

jmparatte/jm_Scheduler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jmP

jm_Scheduler - A Cooperative Scheduler Library for Arduino

2019-01-03: v1.1.0 - Modifying start() which now fails if coroutine already started.
2018-04-30: v1.0.10 - library.properties adjustement.
2018-04-29: v1.0.9 - jm_Scheduler is now compatible with EPS32. 5 new Blink examples added.
2018-04-19: v1.0.8 - yield() function corrected.
2018-03-27: v1.0.7 - A Cooperative Scheduler Library for Arduino.
2018-02-08: v1.0.6 - Minor adjustments.
2017-10-17: v1.0.5 - Minor adjustments.
2017-05-08: v1.0.4 - Minor adjustments.
2017-05-08: v1.0.4 - Minor adjustments.
2017-05-05: v1.0.3 - Adding yield(),sleep(),rearm_async(). Removing void rearm(timestamp_t time, timestamp_t ival);
2017-04-26: v1.0.2 - Adding void rearm(timestamp_t time, timestamp_t ival);
2017-03-29: v1.0.1 - Minor adjustments.
2016-07-08: v1.0.0 - Initial commit.

Concept

jm_Scheduler schedules repeated and intervaled coroutines like the JavaScript setInterval() function does, but with some improvements:

  • By default, jm_Scheduler starts immediately the coroutine and repeats it periodically.
  • The first execution can be differed.
  • The repeated executions can be voided.
  • The interval between executions can be dynamically changed.
  • The scheduled coroutine function can be dynamically changed.
  • The scheduled coroutine can be stopped and later restarted.

jm_Scheduler doesn't schedule like the official Scheduler Library for Arduino DUE and ZERO does, yield() function which suspends a task is implemented, but startLoop() function which allocates a stack to the new task is not implemented.

jm_Scheduler schedules tasks sequentially on the stack processor. The rules to yield and resume are:

  • yield comes out when coroutine leaves at end of function or by an explicit return instruction.
  • resume to a next state can be done with a variable and a switch instruction. Or:
  • resume to a next state can be done by switching to another function.
  • Persistent variables must be implemented global or local with the pragma static.

Basic Example

// This example schedules a coroutine every second

#include <jm_Scheduler.h>

jm_Scheduler scheduler;

void coroutine()
{
	Serial.print('.');
}

void setup(void)
{
	Serial.begin(9600);
	
	scheduler.start(coroutine, TIMESTAMP_1SEC); // Start immediately coroutine() and repeat it every second
}

void loop(void)
{
	yield();
}

Study Plan

  • NEW 2018-04-29: Begin with the 5 Blink progressive examples. They demonstrate how to blink a led by replacing delay() Arduino function with rearm() jm_Scheduler method.
  • Begin with example Clock1.ino. This example demonstrates the advantage to start immediately a time display coroutine and periodically repeat it.
  • Follow with examples Clock2.ino and Clock3.ino which present other timing ways.
  • Clock4.ino example presents a usefully jm_Scheduler technical: changing dynamically the function to execute.
  • Beat1.ino and Beat2.ino examples present interaction between 2 scheduled coroutines.
  • Wakeup1.ino example demonstrates the possible interaction between an interrupt and a scheduled coroutine, implementing a timeout.

Timestamp

The timestamp is read from the Arduino function micros(). By design, the micros() function of Arduino UNO and Leonardo running at 16MHz returns a [us] timestamp with a resolution of [4us].

micros() declaration is:

unsigned long micros();

Look at https://www.arduino.cc/en/Reference/Micros for details.

timestamp is a 32bit [us] counter and overflows about every 70 minutes (precisely 1h+11m+34s+967ms+296us).

Timestamp declaration and constants

typedef uint32_t timestamp_t;

#define timestamp_read() ((timestamp_t)micros())

#define TIMESTAMP_DEAD (0x01CA0000) // coroutine dead time [30s + 15ms + 488us]
#define TIMESTAMP_TMAX (0xFE35FFFF) // [1h + 11m + 4s + 951ms + 808us - 1]

#define TIMESTAMP_1US	(1UL)					// [1us]
#define TIMESTAMP_1MS	(1000*TIMESTAMP_1US)	// [1ms]
#define TIMESTAMP_1SEC	(1000*TIMESTAMP_1MS)	// [1s]
#define TIMESTAMP_1MIN	(60*TIMESTAMP_1SEC)		// [1 minute]
#define TIMESTAMP_1HOUR	(60*TIMESTAMP_1MIN)		// [1 hour]

timestamp_t defines the type of all timestamp values.

timestamp_read() returns the instantaneous timestamp. This function can also be used by interrupt coroutines to timestamp they data.

TIMESTAMP_DEAD is the maximum allowed execution time of a coroutine to guarantee right scheduling. If the coroutine doesn't end before, the scheduler could miss very long scheduling (see next).

TIMESTAMP_TMAX is the maximum allowed scheduling time of a coroutine. In practice, don't use timestamp values greater than 1 hour.

jm_Scheduler methods

// start coroutine immediately
void start(voidfuncptr_t func);

// start coroutine immediately and repeat it at fixed interval
void start(voidfuncptr_t func, timestamp_t ival);

// start coroutine on time and repeat it at fixed interval
void start(voidfuncptr_t func, timestamp_t time, timestamp_t ival);

// stop coroutine, current or scheduled, remove it from chain
void stop();

// rearm current coroutine and set or reset interval
void rearm(timestamp_t ival);

// rearm current coroutine, change coroutine function and set or reset interval
void rearm(voidfuncptr_t func, timestamp_t ival);

> `start()` initiates a scheduler variable, starts a coroutine function, immediately or on time, with or without repetitions.
`start()` is invoked once. Next `rearm()` allows changing scheduler values.

> `stop()` cancels further execution of a scheduled coroutine. 
`stop()` can be invoked from inside coroutine or elsewhere.
If invoked from inside _coroutine_, `stop()` doesn't exit the function, just cancels further execution.

> `rearm()` changes values of a scheduler variable.
The new values are evaluated on exit coroutine function.
The main usage is to change _interval_ or _function_ or both or else cancel further execution.

jm_Scheduler loop

static void cycle();

cycle() is the cornerstone of the scheduler and must be invoked as often as possible. Note that cycle() is a static method. The right place is in Arduino loop() function. Example:

void loop(void)
{
	yield();
}

cycle() can also be invoked in Arduino setup() function. Example:

void setup(void)
{
	// here, some jm_Scheduler variables initialized...
	
	Serial.begin(9600);
	while (!Serial)
	{
		// wait for USB Serial ready...
		
		yield();
	}
	
	// split long setup()...

	yield();
	
	// continue setup()...
}

cycle() can't be invoked from inside a coroutine function.

Good scheduling practices

  • To guarantee a good scheduling of all tasks, the execution time of each function must be as short as possible.

  • Avoid Arduino delay() function, use jm_Scheduler rearm() method with appropriate arguments to split the coroutine in some serialized functions.

  • Use same technical for long calculations.

Changing of Timestamp

Here are some hacks that can be implemented by modifying the file jm_Scheduler.h.

  • Another source for the timestamp could be the [ms] read from the Arduino function millis().
  • Gain speed during timestamp comparison by shortening the size to 16bit.
  • Obtain very long periodicity by implementing a 64bit timestamp.