Skip to content

Instantly share code, notes, and snippets.

@HappyCodingRobot
Last active August 22, 2019 19:32

Revisions

  1. HappyCodingRobot renamed this gist Sep 11, 2016. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. HappyCodingRobot created this gist Sep 11, 2016.
    46 changes: 46 additions & 0 deletions main.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    #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
    }
    36 changes: 36 additions & 0 deletions sched_config.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,36 @@
    /*
    * 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 */
    190 changes: 190 additions & 0 deletions scheduler.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,190 @@
    /**
    * 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
    224 changes: 224 additions & 0 deletions scheduler.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,224 @@

    #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 */