A number of skeleton templates for embedded systems
See also model2.html which a variation in the examples in the bottom
On this page is a number of generic task constructions usefull in embedded systems
The is hold on a generic level but is easy convertable to krnl, FreeRtos, … There are some examples in bewteen written for krnl. You can easily get them up and running.
1. Fixed samplingsfrequency (task synchronized by timed semaphore)
In many systems it is critical to have a rock stable samplingsfrequency for obvious purposes (control, signal processing, …)
endless loop where you are wairting by a semaphore which is signalled by the kernel with strict regular intervals
if you come to late the signal will be accumulated. Please note limit imposed by max limit set in k_crt_sem
Task shall normally be among the higheste prioritized on a preemptive kernel so
Task will get the CPU when it is started
If you dont have highest task priority you might be delay in execution bq the CPU is taken by some one else.
Task can easy check if its behind (there is already a signal waiting at the semaphore when the
task enter the wait call in the top of the loop)
if behind k_wait returns 1. If we have been waited it returns 0. If timeout (no signal) it returns < 0.
(fixedsampling.ino: click for open/close)
#include <krnl.h>
struct k_t *pSem, *pTask;
#define STK 100
char stak[STK];
void doSamplingAlgorithmsAndControl() {
static boolean light = false;
k_eat_time(5); // to simulate code running
if (light) { // debug :-) blinking
light = false;
digitalWrite(13, HIGH);
}
else {
light = true;
digitalWrite(13, LOW);
}
}
void task()
{
k_set_sem_timer(pSem, 1000); // let OS send a signal to semaphore every 1000th kernel timer tick
while (1) {
k_wait(pSem, 0); // semaphore: pSem. 0: we will wait forever if no signal comes
doSamplingAlgorithmsAndControl();
}
}
void setup()
{
pinMode(13, OUTPUT); // for debugging only
k_init(1, 1, 0); // 1 task, 1 semaphore, 0 message boxes
pSem = k_crt_sem(0, 5); // startvalue 0. Clipvalue 5 meaning only 5 "signals" can be accumulated
pTask = k_crt_task(task, 10,stak,STK);
k_start(1);
// coming here ?? then something went wrong
// check k_err_cnt - counts nouber of errors in init of system
}
void loop() {}
(fixedsampling.ino as raw)
2. Tasks sharing data protected by a critical region (semaphore)
We have two independent tasks:
task1 is saving data in a shared variable struct data
task2 is retrieving data from the same struct
we do protect the data so only one task at a time can access the data
priority is in this example no issue
A low prioritized task lowTask and high prioritized task controlTask
We are using a critical region protected the shared data
We are using a standard semaphore to ensure atomic access to the shared data
controlTask is running on a fixed samplingsfrequency
lowTask is running now and then and might be looping faster than controlTask.
We upcount a shared variable every time we deliver data so controlTask can see if any updates has been lost
Important rule is minimize your time in critical as much as possible
Pittfall
lowTask can ofcourse also pick up data instead of delivering data - see next example
(mutex.ino: click for open/close)
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stk1[STK], stk2[STK];
// HOW TO DEBUG ??? :-)
struct dataTp {
int i, j, k;
};
struct dataTp data;
void initData() // must be called bef krnl is started
{
data.i = data.j = data.k = 33;
}
void lowTask()
{
int a, b, c;
while (1) {
k_wait(mutexSem, 0); // wait forever if needed
// >>>>>>> enter critical region
data.i++ ; data.j--; data.k; data.k = data.i + data.j;
// <<<<<<< leaving critical region
k_signal(mutexSem);
k_sleep(100);
}
}
void controlTask()
{
int mya, myb, myc;
k_set_sem_timer(sem, 10); // let krnl send a signal to semaphore every 10th kernel timer tick
while (1) {
k_wait(sem, 0); // wait forever if ...
k_wait(mutexSem, 0); // = so wait forever - might be dangerous
{
// >>>>>>> enter critical region
mya = data.i; myb = data.j; myc = data.k;
// <<<<<<< leaving critical region
}
k_signal(mutexSem);
// do control stuff here with new data :-)
k_eat_time(5); // fake for doSamplingAlgorithmsAndControl();
}
}
void setup()
{
initData();
k_init(2, 2, 0); // 2 tasks 2 semaphores
sem = k_crt_sem(0, 10);
mutexSem = k_crt_sem(1, 10);
pT1 = k_crt_task(controlTask, 10, stk1,STK);
pT2 = k_crt_task(lowTask, 20, stk2,STK);
k_start(1);
}
void loop() {}
(mutex.ino as raw)
3. Critical region now by ceiling protocol
The example is based on the previous example.
We do use a mutex (mutual exclusion) primitive instead of a semaphore.
It is basicly the same example but just show how you use a mutex call.
It is a semaphore in behind but when you enter the region your priority is set to the value given in k_mut_ceil_set
And you revert to your previous priority when leaving the region
If your incoming priority is higher than the ceiling priority for the region just dont get the region
So check for access like in the example
But
A set of mutex call can normally detect if they are paired (an enter should be together with a leave
Inheritance or ceiling protocols might be part of the mutex. The two protocols try to maximize real time behavior og reduce or remove deadlock
The krnl example uses the mutex calls which also implement immediate priority ceiling protocols when a ceiling priority is added prior to k_start wirh k_mut_ceil_set.
So run it without setting ceiling and the code runs just like using pairs of wait and signal
Set ceiling prority by k_mut_ceil_set and it will be a immediate ceiling protocol.
NB NB NB: if running ceiling protocol a task with priority HIGHER than the ceiling priority will not get the critical region and return value CEILINGFAIL (pt -3)
So as usual - dont do as me - please check your return values
(mutex2.ino: click for open/close)
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stk1[STK], stk2[STK];
// HOW TO DEBUG ??? :-)
struct dataTp {
int i, j, k;
};
struct dataTp data;
void initData() // must be called bef krnl is started
{
data.i = data.j = data.k = 33;
}
void lowTask()
{
int a, b, c;
while (1) {
k_mut_ceil_enter(mutexSem, 0); // instead of k_wait(mutexSem, 0); // wait forever if needed
// >>>>>>> enter critical region
data.i++ ; data.j--; data.k; data.k = data.i + data.j;
// <<<<<<< leaving critical region
k_mut_ceil_leave(mutexSem); // instead of k_signal(mutexSem);
k_sleep(100);
}
}
void controlTask()
{
int mya, myb, myc;
k_set_sem_timer(sem, 10); // let krnl send a signal to semaphore every 10th kernel timer tick
while (1) {
k_wait(sem, 0); // wait forever if ...
k_mut_ceil_enter(mutexSem, 0); // instead of k_wait(mutexSem, 0); // = so wait forever - might be dangerous
{
// >>>>>>> enter critical region
mya = data.i; myb = data.j; myc = data.k;
// <<<<<<< leaving critical region
}
k_mut_ceil_leave(mutexSem); // instead of k_signal(mutexSem);
// do control stuff here with new data :-)
k_eat_time(5); // fake for doSamplingAlgorithmsAndControl();
}
}
void setup()
{
initData();
k_init(2, 2, 0); // 2 tasks 2 semaphores
sem = k_crt_sem(0, 10);
mutexSem = k_crt_sem(1, 10);
k_mut_ceil_set(mutexSem,8); // Set ceiling priority
pT1 = k_crt_task(controlTask, 10, stk1,STK);
pT2 = k_crt_task(lowTask, 20, stk2,STK);
k_start(1);
}
void loop() {}
(mutex2.ino as raw)
4. Critical region .. wont wait invariant
The example is based on example 2
The high prority control Task use a wait-check call to see if the critical region is vacant and if so then take it.
Drawback is
controlTask might never enter the region
could have been solved by ceiling as shown just above …
So high priority control task will not be delayed but
might never (very bad luck ) enter the critical region
(mutex3.ino: click for open/close)
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stk1[STK], stk2[STK];
// HOW TO DEBUG ??? :-)
struct dataTp {
int i, j, k;
};
struct dataTp data;
void initData() // must be called bef krnl is started
{
data.i = data.j = data.k = 33;
}
void lowTask()
{
int a, b, c;
while (1) {
k_wait(mutexSem, 0); // wait forever if needed
// >>>>>>> enter critical region
data.i++ ; data.j--; data.k; data.k = data.i + data.j;
// <<<<<<< leaving critical region
k_signal(mutexSem);
k_sleep(100);
}
}
void controlTask()
{
int mya, myb, myc;
k_set_sem_timer(sem, 10); // let krnl send a signal to semaphore every 10th kernel timer tick
while (1) {
k_wait(sem, 0); // wait forever if ... for sampling
if (0 <= k_wait(mutexSem, -1) ) // = so no wait - might be dangerous bq you might miss region
{
// >>>>>>> enter critical region
mya = data.i; myb = data.j; myc = data.k;
// <<<<<<< leaving critical region
k_signal(mutexSem);
}
// do control stuff here with new data :-)
k_eat_time(5); // fake for doSamplingAlgorithmsAndControl();
}
}
void setup()
{
initData();
k_init(2, 2, 0); // 2 tasks 2 semaphores
sem = k_crt_sem(0, 10);
mutexSem = k_crt_sem(1, 10);
pT1 = k_crt_task(controlTask, 10, stk1, STK );
pT2 = k_crt_task(lowTask, 20, stk2, STK );
k_start(1);
}
void loop() {}
(mutex3.ino as raw)
5. wait-signal construction from interrupt to a task
Systems often interact with the enviroment.
You can
It might be more easy just to let your task sleep and then be started by an interrupt.
Normally and interrupt is pulling a pin on the PCU high or low (depending on configuration)
The code is for an Arudino UNO and a MEGA. Take a look in the initISR function and the head of the interrupt servcie routine
(isem.ino: click for open/close)
#include <krnl.h>
// External triggered ISR
// An Interrupt Service Routine is attached to pin 2
// So when pin2 is drived to ground (by a wire) an interrupt is generated.
// The ISR increment a counter and send it to a message Q
// naming ki_send .... "i" indicates it can be used in an ISR and demand interrupt to be disabled prio to call
// and that no task shift takes place in the call
// demonstrates ISR with message Q and preemption(task shift) in the ISR
// NB Take a look on the ISR. For 1280 and 2560 it is INT4 but for 168,328,.. it's INTO
// It is taken care by a compile flag
// (c) beerware license JDN 2013
// AS USUAL Serial.print is not krnl safe !!! cant break your program
struct k_t * p_t1, *sem1;
#define STK_SIZE 200
char stak[STK_SIZE];
volatile int icnt=0;
void doBlink(void) {
static char flag = 0;
flag = ! flag;
digitalWrite(13,flag);
}
void t1(void) {
while (1) {
int i;
k_wait(sem1,0); // wait forever
doBlink();
}
}
#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__)
ISR(INT4_vect,ISR_NAKED) {
#else
ISR(INT0_vect, ISR_NAKED) {
#endif
// no local vars ?!? ! I think
PUSHREGS();
if (!k_running)
goto exitt ;
icnt++;
ki_signal(sem1);
K_CHG_STAK();
exitt:
POPREGS();
RETI();
}
void initISR()
{
DI();
pinMode(2,INPUT); // set som input
digitalWrite(2,HIGH); // enable pullup resistor
#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__)
// 1280/2560 mega pin2 intr:4, pin5 intr:5
EIMSK |= (1 << INT4); // enable external intr
EICRB |= (1 << ISC41); // trigger INT4 on falling edge
#else
EIMSK |= (1 << INT0); // enable external intr
EICRA |= (1 << ISC01); // trigger INT0 on falling edge
#endif
EI();
}
void setup() {
Serial.begin(9600);
pinMode(13,OUTPUT);
k_init(1,1,0); // from now you can crt task,sem etc
sem1 = k_crt_sem(0,10); //
p_t1 = k_crt_task(t1, 10, stak,STK);
initISR();
Serial.println("just bef");
k_start(10); // now we are runnning with timer 10 msev
//Serial.println("oev");
// main will not come back and will sleep rest of life
}
void loop(void) {
// just for compilation - will never be called
}
/* QED :-) */
(isem.ino as raw)
6. Message passing or buffering
We use a message buffering system - sometimes called a ringbuffer system
Some normal characteristics for a message system
has internal buffering of the messages
No of elements in the buffer and their size is set by you
you have to come with the memory for the buffer (just a char array)
if buffer is full you cant deliver any messages - so check your return code (negative if no succes)
not like a critical region - you just deliver data
The code is a little complicated :-)
This version for an UNO due to integration of interrupt
ISR installed on pin 2. Its normally HIGH due to pull up.
Activate interrupt by pulling pin2 to to ground
ISR (interrupt service routine) will send an integer to message buf named pMsg (ki_send)
task t2 is waiting on data coming to this buffer in k_receive
task t2 will just forward the received integer to another messagebuf named pMsg2
task t1 is waiting on pMsg2 message buffer and will blink with LED upon receiving.
SO here we have a demonstration of simple message passing and integration of interrupt.
The interrupt routine could be for handling a serial prot, a network interface etc.
The idea is fast efficient service of the hardware by the interrupt and then a little later
the rest of the work carried out by a task. In this way we can ensure short interrupt time
which is nice because when we are in the interrupt routine we normally has disabled the interrupt
and therefore blocking for all other interrupts that might occur.
(msg1.ino: click for open/close)
#include <krnl.h>
// External triggered ISR
// An Interrupt Service Routine is attached to pin 2
// So when pin2 is drived to ground (by a wire) an interrupt is generated.
// The ISR increment a counter and send it to a message Q
// naming ki_send .... "i" indicates it can be used in an ISR and demand interrupt to be disabled prio to call
// and that no task shift takes place in the call
// demonstrates ISR with message Q and preemption(task shift) in the ISR
// NB Take a look on the ISR. For 1280 and 2560 it is INT4 but for 168,328,.. it's INTO
// It is taken care by a compile flag
// (c) beerware license JDN 2013
struct k_t * tSm1, *tSm2;
struct k_t * p_t1, *p_t2;
struct k_msg_t *pMsg,*pMsg2;
char mar[10*2];
char mar2[10*2];
#define STK_SIZE 200
char stk1[STK_SIZE], stk2[STK_SIZE];
volatile int icnt=0;
void doBlink(void) {
static char flag = 0;
flag != flag;
digitalWrite(13,flag);
}
void t1(void) {
int i;
while (1) {
delay(100);
if (0 <= k_receive(pMsg2,&i,-1,NULL) ) {
doBlink();
}
}
}
void t2(void) {
int i;
i = 0;
while (1) {
if (0 <= k_receive(pMsg,&i,10,NULL) ) {
Serial.println(i);
k_send(pMsg2,&i);
}
else {
Serial.println("-1");
}
}
}
#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__)
ISR(INT4_vect,ISR_NAKED) {
#else
ISR(INT0_vect, ISR_NAKED) {
#endif
// no local vars ?!? ! I think
PUSHREGS();
if (!k_running)
goto exitt ;
icnt++;
ki_send(pMsg,(void *)&icnt);
K_CHG_STAK();
exitt:
POPREGS();
RETI();
}
void installISR2()
{
DI();
pinMode(2,INPUT); // set som input
digitalWrite(2,HIGH); // enable pullup resistor
#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__)
// 1280/2560 mega pin2 intr:4, pin5 intr:5
EIMSK |= (1 << INT4); // enable external intr
EICRB |= (1 << ISC41); // trigger INT4 on falling edge
#else
EIMSK |= (1 << INT0); // enable external intr
EICRA |= (1 << ISC01); // trigger INT0 on falling edge
#endif
EI();
}
void setup() {
Serial.begin(9600);
k_init(2,5,2); // from now you can crt task,sem etc
tSm1 = k_crt_sem(0,10); //
tSm2 = k_crt_sem(0,10); //
p_t1 = k_crt_task(t1, 10, stk1,STK_SIZE);
p_t2 = k_crt_task(t2, 9, stk2,STK_SIZE);
pMsg = k_crt_send_Q(10,2,mar);
pMsg2 = k_crt_send_Q(10,2,mar2);
pinMode(13,OUTPUT);
Serial.print("start ");
Serial.println(KRNL_VRS);
delay(2000);
installISR2();
Serial.println("bef gogo");
k_start(10); // now we are runnning with timer 10 msev
Serial.println("shit - should not come here");
// main will not come back and will sleep rest of life
}
void loop(void) {/* just for compilation - will never be called*/
}
/* QED :-) */
(msg1.ino as raw)
(msg2.ino: click for open/close)
#include <krnl.h>
// External triggered ISR
// An Interrupt Service Routine is attached to pin 2
// So when pin2 is drived to ground (by a wire) an interrupt is generated.
// The ISR increment a counter and send it to a message Q
// naming ki_send .... "i" indicates it can be used in an ISR and demand interrupt to be disabled prio to call
// and that no task shift takes place in the call
// demonstrates ISR with message Q and preemption(task shift) in the ISR
// NB Take a look on the ISR. For 1280 and 2560 it is INT4 but for 168,328,.. it's INTO
// It is taken care by a compile flag
// (c) beerware license JDN 2013
struct k_t * p_t1, *p_t2;
struct k_msg_t *pMsg2;
char mar2[10 * 2]; // 10 ints each 2 bytes could instead write 10 *sizeof(int)
#define STK_SIZE 200
char stk1[STK_SIZE], stk2[STK_SIZE];
volatile int icnt = 0;
void doBlink(void) {
static char flag = 0;
flag != flag;
digitalWrite(13, flag);
}
void t1(void) {
int i;
while (1) {
delay(100);
if (0 <= k_receive(pMsg2, &i, -1, NULL) ) {
doBlink();
}
}
}
void t2(void) {
int i;
i = 0;
while (1) {
k_sleep(20); // just ZZZZZZZZZZZZZZZZ
k_send(pMsg2, &i); //just send 0,1,2,3,4....
i++;
}
}
void setup() {
Serial.begin(9600);
k_init(2, 0, 1); // from now you can crt task,sem etc
p_t1 = k_crt_task(t1, 10, stk1,STK_SIZE);
p_t2 = k_crt_task(t2, 9, stk2,STK_SIZE);
pMsg2 = k_crt_send_Q(10, 2, mar2);
pinMode(13, OUTPUT);
Serial.print("start ");
Serial.println(KRNL_VRS);
delay(2000);
Serial.println("bef gogo");
k_start(10); // now we are runnning with timer 10 msev
Serial.println("shit - should not come here");
// main will not come back and will sleep rest of life
}
void loop(void) {/* just for compilation - will never be called*/
}
/* QED :-) */
(msg2.ino as raw)
|