Last active
August 22, 2019 19:32
-
-
Save HappyCodingRobot/a441d72e24b6a42cae86b69de9f060c9 to your computer and use it in GitHub Desktop.
Very basic Round Robin scheduler for AVR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* per application scheduler configuration header | |
* | |
*/ | |
#ifndef SCHED_CONFIG_H | |
#define SCHED_CONFIG_H | |
/* max. number of tasks */ | |
#define NUM_TASKS (5) | |
/* system tick period in us */ | |
#define SYS_TICK (10000) | |
/* optional config */ | |
/* use sleep mode during idle task */ | |
#define USE_IDLE_SLEEP | |
/* select timer 0 or 1 (default Timer1 if not set) */ | |
//#define SYS_USE_TIMER 0 | |
/* Limitations: | |
* Timer 0 only supports a few F_CPU and SYS_TICK combinations | |
* F_CPU[MHz]: 8, 12, 16, 20 | |
* SYSTICK[ms]: 1, 2.5, 10, 100 | |
*/ | |
/* use watchdog and reset it in idle task, default 2s if no value set */ | |
//#define USE_WDT WDTO_4S | |
//#define USE_WDT | |
#endif /* SCHED_CONFIG_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* AVR Round Robbin Scheduler | |
* based on: | |
* https://sites.google.com/site/avrtutorials2/scheduler | |
*/ | |
#include <avr/interrupt.h> | |
#include <avr/sleep.h> | |
#include <avr/wdt.h> | |
#include <inttypes.h> | |
#include "scheduler.h" | |
// the task list | |
tcb_t task_list[NUM_TASKS]; | |
// keeps track of number of timer interrupts | |
volatile uint8_t count = 0; | |
/* scheduler function definitions */ | |
/* | |
* initialises the task list | |
*/ | |
void initScheduler(void) { | |
// HW init | |
cli(); | |
#if SYS_USE_TIMER==1 | |
TCCR1A = 0; | |
TCCR1B = T1MODE4_CTC | TIMER1_PRESCALER_MASK; | |
OCR1A = TIMER1_TOP; | |
TCNT1 = 0; | |
TIMSK1 = (1<<OCIE1A); | |
#endif | |
#if SYS_USE_TIMER==0 | |
TCCR0A = T0MODE2_CTC; | |
TCCR0B = TIMER0_PRESCALER_MASK; | |
OCR0A = TIMER0_TOP; | |
TCNT0 = 0; | |
TIMSK0 = (1<<OCIE0A); | |
#endif | |
#ifdef USE_IDLE_SLEEP | |
set_sleep_mode(SLEEP_MODE_IDLE); | |
#endif | |
// struct init | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
task_list[i].id = 0; | |
task_list[i].task = (task_t) 0x00; | |
task_list[i].delay = 0; | |
task_list[i].period = 0; | |
task_list[i].status = STOPPED; | |
} | |
#ifdef USE_WDT | |
wdt_enable(USE_WDT); | |
#endif | |
} | |
/* | |
* adds a new task to the task list | |
* scans through the list and places the new task data where it finds free space | |
*/ | |
uint8_t addTask(uint8_t id, task_t task, uint16_t period) { | |
uint8_t idx = 0, done = 0x00; | |
while (idx < NUM_TASKS) { | |
if (task_list[idx].status == STOPPED) { | |
task_list[idx].id = id; | |
task_list[idx].task = task; | |
task_list[idx].delay = period; | |
task_list[idx].period = period; | |
task_list[idx].status = RUNNABLE; | |
done = 0x01; | |
} | |
if (done) return xSUCCESS; | |
idx++; | |
} | |
return xERROR; | |
} | |
/* | |
* suspend task in task list | |
*/ | |
uint8_t suspendTask(uint8_t id) { | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if (task_list[i].id == id) { | |
task_list[i].status = SUSPENDED; | |
return xSUCCESS; | |
} | |
} | |
return xERROR; | |
} | |
/* | |
* resume suspended task in task list | |
* note: has no effect if task is still runnable | |
*/ | |
uint8_t resumeTask(uint8_t id) { | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if (task_list[i].id == id) { | |
task_list[i].status = RUNNABLE; | |
return xSUCCESS; | |
} | |
} | |
return xERROR; | |
} | |
/* | |
* remove task from task list | |
* note: STOPPED is equivalent to removing a task | |
*/ | |
void deleteTask(uint8_t id) { | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if (task_list[i].id == id) { | |
task_list[i].id = 0; | |
task_list[i].status = STOPPED; | |
break; | |
} | |
} | |
} | |
/* | |
* gets the task status | |
* returns ERROR if id is invalid | |
*/ | |
uint8_t getTaskStatus(uint8_t id) { | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if (task_list[i].id == id) | |
return task_list[i].status; | |
} | |
return xERROR; | |
} | |
/* | |
* dispatches tasks when they are ready to run | |
*/ | |
void dispatchTasks(void) { | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
// check for a valid task ready to run | |
if (!task_list[i].delay && task_list[i].status == RUNNABLE) { | |
// task is now running | |
task_list[i].status = RUNNING; | |
// call the task | |
(*task_list[i].task)(); | |
// reset the delay | |
task_list[i].delay = task_list[i].period; | |
// task is runnable again | |
task_list[i].status = RUNNABLE; | |
} | |
} | |
#ifdef USE_IDLE_SLEEP | |
sleep_mode(); | |
#endif | |
#ifdef USE_WDT | |
wdt_reset(); | |
#endif | |
} | |
/* | |
* infinite task loop as inline function | |
*/ | |
/* #define runTasks() .. */ | |
/* | |
* generates a system "tick" | |
*/ | |
#if SYS_USE_TIMER==1 | |
// Timer1 overflow | |
ISR(TIMER1_COMPA_vect, ISR_NOBLOCK) { | |
// cycle through available tasks | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if ((task_list[i].status == RUNNABLE) && (task_list[i].delay > 0)) | |
task_list[i].delay--; | |
} | |
} | |
#elif SYS_USE_TIMER==0 | |
// Timer0 overflow | |
ISR(TIMER0_COMPA_vect, ISR_NOBLOCK) { | |
count++; | |
if (count >= T0CNT_TOP) { | |
count = 0; | |
// cycle through available tasks | |
for (uint8_t i = 0; i < NUM_TASKS; i++) { | |
if ((task_list[i].status == RUNNABLE) && (task_list[i].delay > 0)) | |
task_list[i].delay--; | |
} | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SCHEDULER_H | |
#define SCHEDULER_H | |
/* Possible configuration defines for scheduler (sched_config.h) | |
* | |
* #define NUM_TASKS -> max. number of task | |
* #define SYS_TICK -> system tick period in us | |
* #define SYS_HW_TICK -> system HW tick period in us (optional) | |
* equals SYS_TICK if not defined (opt.) TODO! | |
* #define USE_IDLE_SLEEP -> use sleep mode during idle task | |
* #define USE_WDT -> use watchdog and reset it in idle task | |
* #define SYS_USE_TIMER [0|1] -> select timer 0 or 1 (default Timer1) | |
* | |
*/ | |
/* Some macro magic to test if define is empty.. */ | |
#define NO_OTHER_MACRO_STARTS_WITH_THIS_NAME_ | |
#define IS_EMPTY(name) defined(NO_OTHER_MACRO_STARTS_WITH_THIS_NAME_ ## name) | |
#define EMPTY(name) IS_EMPTY(name) | |
#include "sched_config.h" | |
#ifndef NUM_TASKS // max. number of tasks | |
#define NUM_TASKS (5) | |
#endif | |
#ifndef SYS_TICK // system tick period in us | |
#define SYS_TICK (10000) | |
#endif | |
#ifndef SYS_HW_TICK // timer interrupt period, normally same as SYS_TICK | |
#define SYS_HW_TICK SYS_TICK | |
#endif | |
#ifndef SYS_USE_TIMER // select default Timer 1 | |
#define SYS_USE_TIMER 1 | |
#endif | |
#if defined USE_WDT && EMPTY(USE_WDT) | |
#undef USE_WDT | |
#define USE_WDT WDTO_2S | |
#warning USE_WDT has no value: set to 2s | |
#endif | |
// task states | |
enum task_states { | |
RUNNABLE, | |
RUNNING, | |
SUSPENDED, | |
STOPPED, | |
ERROR | |
}; | |
#define xSUCCESS (0x01) | |
#define xERROR (0x00) | |
// a task "type", pointer to a void function with no arguments | |
typedef void (*task_t)(void); | |
// basic task control block (TCB) | |
typedef struct __tcb_t { | |
uint8_t id; // task ID | |
task_t task; // pointer to the task | |
// delay before execution | |
uint16_t delay, period; | |
uint8_t status; // status of task | |
} tcb_t; | |
// prototype scheduler functions | |
void initScheduler(void); | |
uint8_t addTask(uint8_t, task_t, uint16_t); | |
uint8_t suspendTask(uint8_t); | |
uint8_t resumeTask(uint8_t); | |
void deleteTask(uint8_t); | |
uint8_t getTaskStatus(uint8_t); | |
void dispatchTasks(void); | |
#define runTasks() { \ | |
sei(); \ | |
for (;;) { \ | |
dispatchTasks(); \ | |
} \ | |
} | |
// helper macros | |
#define T1MODE4_CTC (1 << WGM12) | |
#define T1MODE12_CTC (1 << WGM13)|(1 << WGM12) | |
#define T0MODE2_CTC (1 << WGM01) | |
#define TDIV1 1U | |
#define TDIV8 2U | |
#define TDIV64 3U | |
#define TDIV256 4U | |
#define TDIV1024 5U | |
#define xMS2TICK(_ms) (_ms*1000UL/SYS_TICK) | |
#define xUS2TICK(_us) (_us/SYS_TICK) | |
// -> warning: no check if not multiple of SYS_TICK | |
#define TIMER_FREQUENCY (1000000UL / SYS_HW_TICK) | |
#define SCHED_FREQUENCY (1000000UL / SYS_TICK) | |
#if ((TIMER_FREQUENCY % SCHED_FREQUENCY) > 0) | |
#error SCHED_FREQUENCY must be a multiple of TIMER1_FREQUENCY | |
#endif | |
#define SCHED_CNT (SYS_TICK / SYS_HW_TICK) | |
/* Timer1 calculation */ | |
#if ((F_CPU / 0x10000UL) < TIMER_FREQUENCY) | |
#define TIMER1_PRESCALER 1U | |
#define TIMER1_PRESCALER_MASK TDIV1 | |
#elif ((F_CPU / 8UL / 0x10000UL) < TIMER_FREQUENCY) | |
#define TIMER1_PRESCALER 8U | |
#define TIMER1_PRESCALER_MASK TDIV8 | |
#elif ((F_CPU / 64UL / 0x10000UL) < TIMER_FREQUENCY) | |
#define TIMER1_PRESCALER 64U | |
#define TIMER1_PRESCALER_MASK TDIV64 | |
#elif ((F_CPU / 256UL / 0x10000UL) < TIMER_FREQUENCY) | |
#define TIMER1_PRESCALER 256U | |
#define TIMER1_PRESCALER_MASK TDIV256 | |
#elif ((F_CPU / 1024UL / 0x10000UL) < TIMER_FREQUENCY) | |
#define TIMER1_PRESCALER 1024U | |
#define TIMER1_PRESCALER_MASK TDIV1024 | |
#else | |
#error Wrong TIMER_FREQUENCY | |
#endif | |
#define TIMER1_TOP ((F_CPU / TIMER1_PRESCALER / TIMER_FREQUENCY) - 1) | |
#define TIMER1_FREQUENCY_REAL \ | |
((double)F_CPU / (double)(TIMER1_PRESCALER * (1+TIMER1_TOP))) | |
/* Timer0 configuration only uses fixed values at the moment */ | |
// scheduler [ms]: 1 2,5 10 100 : [Hz] 1000 400 100 10 | |
#if F_CPU==8000000UL | |
#if TIMER_FREQUENCY==1000 | |
#define TIMER0_TOP 125 | |
#define TIMER0_PRESCALER_MASK TDIV64 | |
#define T0CNT_TOP 1 | |
#elif TIMER_FREQUENCY==400 | |
#define TIMER0_TOP 250 | |
#define TIMER0_PRESCALER_MASK TDIV8 | |
#define T0CNT_TOP 5 | |
#elif TIMER_FREQUENCY==100 | |
#define TIMER0_TOP 250 | |
#define TIMER0_PRESCALER_MASK TDIV64 | |
#define T0CNT_TOP 5 | |
#elif TIMER_FREQUENCY==10 | |
#define TIMER0_TOP 125 | |
#define TIMER0_PRESCALER_MASK TDIV256 | |
#define T0CNT_TOP 25 | |
#else | |
#error no TIMER0 settings for actual SYS_TICK | |
#endif | |
#elif F_CPU==12000000UL | |
#if TIMER_FREQUENCY==1000 | |
#define TIMER0_TOP 250 | |
#define TIMER0_PRESCALER_MASK TDIV8 | |
#define T0CNT_TOP 6 | |
#elif TIMER_FREQUENCY==400 | |
#define TIMER0_TOP 117 | |
#define TIMER0_PRESCALER_MASK TDIV256 | |
#define T0CNT_TOP 1 | |
#elif TIMER_FREQUENCY==100 | |
#define TIMER0_TOP 234 | |
#define TIMER0_PRESCALER_MASK TDIV64 | |
#define T0CNT_TOP 8 | |
#elif TIMER_FREQUENCY==10 | |
#define TIMER0_TOP 234 | |
#define TIMER0_PRESCALER_MASK TDIV1024 | |
#define T0CNT_TOP 5 | |
#else | |
#error no TIMER0 settings for actual SYS_TICK | |
#endif | |
#elif F_CPU==16000000UL | |
#if TIMER_FREQUENCY==1000 | |
#define TIMER0_TOP 250 | |
#define TIMER0_PRESCALER_MASK TDIV64 | |
#define T0CNT_TOP 1 | |
#elif TIMER_FREQUENCY==400 | |
#define TIMER0_TOP 125 | |
#define TIMER0_PRESCALER_MASK TDIV64 | |
#define T0CNT_TOP 5 | |
#elif TIMER_FREQUENCY==100 | |
#define TIMER0_TOP 125 | |
#define TIMER0_PRESCALER_MASK TDIV256 | |
#define T0CNT_TOP 5 | |
#elif TIMER_FREQUENCY==10 | |
#define TIMER0_TOP 223 | |
#define TIMER0_PRESCALER_MASK TDIV1024 | |
#define T0CNT_TOP 7 | |
#else | |
#error no TIMER0 settings for actual SYS_TICK | |
#endif | |
#elif F_CPU==20000000UL | |
#if TIMER_FREQUENCY==1000 | |
#define TIMER0_TOP 78 | |
#define TIMER0_PRESCALER_MASK TDIV256 | |
#define T0CNT_TOP 1 | |
#elif TIMER_FREQUENCY==400 | |
#define TIMER0_TOP 195 | |
#define TIMER0_PRESCALER_MASK TDIV256 | |
#define T0CNT_TOP 1 | |
#elif TIMER_FREQUENCY==100 | |
#define TIMER0_TOP 39 | |
#define TIMER0_PRESCALER_MASK TDIV1024 | |
#define T0CNT_TOP 5 | |
#elif TIMER_FREQUENCY==10 | |
#define TIMER0_TOP 217 | |
#define TIMER0_PRESCALER_MASK TDIV1024 | |
#define T0CNT_TOP 9 | |
#else | |
#error no TIMER0 settings for actual SYS_TICK | |
#endif | |
#else | |
#error no TIMER0 settings for actual F_CPU | |
#endif | |
#endif /* SCHEDULER_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <avr/io.h> | |
#include <avr/interrupt.h> | |
#include <inttypes.h> | |
#include "scheduler.h" | |
// Task definitions | |
#define nTask1 1 | |
#define nTask2 2 | |
void Task1(void) { | |
PORTB ^= _BV(PB0); | |
} | |
void Task2(void) { | |
static uint8_t status = 0x00; | |
if (status) { | |
PORTB |= _BV(PB1); | |
suspendTask(nTask1); | |
} else { | |
PORTB &= ~(_BV(PB1); | |
resumeTask(nTask1); | |
} | |
status = !status; | |
} | |
int main(void) { | |
// set PORTB bit0 and bit1 as outputs | |
DDRB |= (1<<PB0) | (1<<PB1); | |
// set up the task list | |
initScheduler(); | |
// add tasks, id is arbitrary | |
// task1 runs every 500 ms | |
addTask(nTask1, Task1, xMS2TICK(500)); | |
// task2 runs every 4 seconds | |
addTask(nTask2, Task2, xMS2TICK(4000)); | |
// enable all interrupts and run scheduler | |
runTasks(); | |
return 0; // will never reach here | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment