A number of skeleton templates for embedded systemsOn 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, …)
#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() {}
2. Tasks sharing data protected by a critical region (semaphore)We have two independent tasks:
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stakH[STK], stakL[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, stakH, STK);
pT2 = k_crt_task(lowTask, 20, stakL, STK);
k_start(1);
}
void loop() {}
3. Critical region now by ceiling protocolThe example is based on the previous example. The ceiling protocol is a way to avoid starvation of high pririty tasks..
Example
CONCLUSION: highPrio was waiting on lowPrio Task The ceiling solution
CONCLUSION: works better :-) But
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stakH[STK], stakL[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, stakH, STK);
pT2 = k_crt_task(lowTask, 20, stakL, STK);
k_start(1);
}
void loop() {}
4. Critical region .. wont wait invariantThe 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
#include <krnl.h>
struct k_t *sem, *mutexSem, *pT1, *pT2;
#define STK 100
char stakH[STK], stakL[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, stakH, STK);
pT2 = k_crt_task(lowTask, 20, stakL, STK);
k_start(1);
}
void loop() {}
5. wait-signal construction from interrupt to a taskSystems 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 6. Message passing or bufferingWe use a message buffering system - sometimes called a ringbuffer system Some normal characteristics for a message system
The code is a little complicated :-)
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.
#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 s1[STK_SIZE]; // stak for t1 ... and t2
char s2[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, s1, STK_SIZE);
p_t2 = k_crt_task(t2, 9, s2, STK_SIZE);
// Ringbugffer init. You have to come with date dataarea for the ringbuffe.
//mar and mar2 is each an array with 10 slots of 2 bytes
pMsg = k_crt_send_Q(10,2,mar); // create a circular buffer with 10 elements each of size 2 byte
pMsg2 = k_crt_send_Q(10,2,mar2); // and one more
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 :-) */
|