Skip to content

Instantly share code, notes, and snippets.

@HappyCodingRobot
Last active August 22, 2019 19:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HappyCodingRobot/a441d72e24b6a42cae86b69de9f060c9 to your computer and use it in GitHub Desktop.
Save HappyCodingRobot/a441d72e24b6a42cae86b69de9f060c9 to your computer and use it in GitHub Desktop.
Very basic Round Robin scheduler for AVR
/*
* 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 */
/**
* 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
#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 */
#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