This is a followup to the original post., and a followup. to that. curtvm pointing out issue with interrupts. It took me a bit of time to get back to this.
I've rewritten much of the code to deal with AVR-0 series interrupt structure. Now I just use a low-priority PORT interrupt as a software interrupt triggered by the "idle" or "wake" calls. A lot of work was in updating and debugging the simulator.
The objective of this post is to announce the update and invite review. The code and demo are available here. More thank to curvm for helping with using PORT interrupts as a software interrupt.
Anyhow, here's an overview:
OctOS is intended to be a simple-to-use tasking system for AVR CPUs. It consists of a single assembly file, octos.S, and a single header
file, octos.h. Features of OctOS are
- - OctOS can support eight tasks, each with a unique priority.
- - Tasks are disabled and enabled via idle and wake calls, respectively.
- - The lowest priority task is dedicated to (sleep and) spin and cannot be idled.
- - OctOS is coded in assembly.
- - Flash size is about 400 bytes. RAM size is about 20 bytes.
- OctOS assumes that the compiler takes care of saving caller-saves registers, we only swap callee-saves and the status register.
API:
- - Task IDs are `OCT_TASK0` to `OCT_TASK7`.
- - `OCT_TASK0` is the highest priority task; `OCT_TASK7` is the lowest.
- - `oct_os_init(id)` (usually in `main`) initializes OctOS, where `id` is the task id for the main task
- - `oct_attach_task(id, ftn, stk, siz)` will attached a task
- - id is the ``OCT_TASK0`` .. ``OCT_TASK7``
- - ftn is the task function
- - stk is data for stack
- - siz is the stack size
- - `oct_wake_task(id-set)`: wake-up tasks where `id-set` is bitwise-or'd list of task-ids to wake up
- - `oct_idle_task(id-set)`: sleep tasks where id-set` is bitwise or'd list of tasks to put to sleep
- - `oct_detach_task(id)` removes task `id`
Notes on the design:
- - There is one user main task, which can be any one of Task0 through Task6 which may use all registers.
- - Interrupts are not masked during the bulk the task swap operation.
- - Internally, OctOS uses a single byte to maintain the ready-list. If a bitwise and of the ready-list and the task-id is non-zero the corresponding task is ready. Task7 has id `0x80`; Task0 has id `0x01`. The task-i minus 1, when bitwise-and'd with the ready-list, indicates higher-prioirty ready tasks.
Here is a demo program for the ATmega4809 Curiosity Nano. Here the main task runs a while loop to make the LED dim. Every 1.5 seconds TCA0 OVF ISR triggers to enabel Task2 and Task3, Task2 will make the LED bright for .25 seconds and idle itself, then Task3 will make the LED medium bright for 0.5 seconds and idle itself.
#ifdef F_CPU #undef F_CPU #endif #define F_CPU 5000000 #include <avr/io.h> #include <avr/interrupt.h> #include <avr/wdt.h> #include <util/delay.h> #include "octos.h" ISR(PORTE_PORT_vect, ISR_NAKED) { OCT_SWISR(); } ISR(TCA0_OVF_vect) { TCA0.SINGLE.INTFLAGS = TCA_SINGLE_ENABLE_bm; oct_wake_task(OCT_TASK2 | OCT_TASK3); /* calling => push CALLER_SAVES */ PORTE.OUTTGL = PIN0_bm; } /* ---------------------------------------------------------------------------*/ #define T2_ITER 25000u /* iterations before idle */ #define T3_ITER 50000u /* iterations before idle */ #define T2_STKSZ 0x40 /* task2 stacksize */ #define T3_STKSZ 0x40 /* task3 stacksize */ #define T7_STKSZ 0x20 /* task7 (idle) stacksize */ /* Alignment on stacks is to aid my debugging. */ uint8_t task2_stk[T2_STKSZ] __attribute__((aligned(16))); /* Make LED bright. */ void __attribute__((OS_task)) task2(void) { sei(); /* since SREG is per task */ while (1) { oct_idle_task(OCT_TASK2); for (uint16_t i = 0; i < T2_ITER; i++) { PORTF.OUTCLR = PIN5_bm; _delay_us(8); PORTF.OUTSET = PIN5_bm; _delay_us(1); } } } uint8_t task3_stk[T3_STKSZ] __attribute__((aligned(16))); /* Make LED medium. */ void __attribute__((OS_task)) task3(void) { sei(); while (1) { oct_idle_task(OCT_TASK3); for (uint16_t i = 0; i < T3_ITER; i++) { PORTF.OUTCLR = PIN5_bm; _delay_us(2); PORTF.OUTSET = PIN5_bm; _delay_us(7); } } } uint8_t task7_stk[T7_STKSZ] __attribute__((aligned(16))); /* ---------------------------------------------------------------------------*/ void nano_button_wait(); void init_sys_clk() { _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc); _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm); while ((CLKCTRL.MCLKSTATUS & 0x11) != 0x10); /* wait for stable OSC20M */ } void init_wdt() { _PROTECTED_WRITE(WDT.CTRLA, WDT_PERIOD_8KCLK_gc); /* 8 sec watchdog */ } void init_tca() { TCA0.SINGLE.PER = 3*9745; TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc | TCA_SINGLE_ENABLE_bm; } void main(void) { if (SYSCFG.REVID < 0xFF) nano_button_wait(); /* not sim: wait for button */ oct_os_init(OCT_TASK6); oct_attach_task(OCT_TASK7, oct_spin, task7_stk, T7_STKSZ); /* required */ oct_attach_task(OCT_TASK3, task3, task3_stk, T3_STKSZ); oct_attach_task(OCT_TASK2, task2, task2_stk, T2_STKSZ); /* SW intr via PORTE PIN0 */ PORTE.DIRSET = PIN0_bm; PORTE.PIN0CTRL = PORT_ISC_BOTHEDGES_gc; init_sys_clk(); /* init clock to 5 MHz */ init_wdt(); /* init WDT to 8 sec */ init_tca(); /* init TCA to 3 sec */ PORTF.DIRSET = PIN5_bm; /* LED pin as output */ PORTF.OUTSET = PIN5_bm; /* LED off */ sei(); oct_wake_task(OCT_TASK3 | OCT_TASK2); /* let tasks initialize */ TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESTART_gc; /* reset timer */ TCA0.SINGLE.INTCTRL = TCA_SINGLE_ENABLE_bm; /* start timer */ /* Make LED dim. */ while (1) { wdt_reset(); PORTF.OUTCLR = PIN5_bm; _delay_us(1); PORTF.OUTSET = PIN5_bm; _delay_us(20); } } /* ---------------------------------------------------------------------------*/ /* Flash LED and wait for button press to start application code. */ void nano_button_wait() { uint8_t st = 1; /* switch state */ uint8_t lc; /* led counter */ TCB0.CCMP = 333; /* debounce time */ TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; PORTF.DIRSET = PIN5_bm; /* LED output */ while (st) { switch (st) { case 1: if ((PORTF.IN & PIN6_bm) == 0) st = 2; break; case 2: if ((PORTF.IN & PIN6_bm) != 0) st = 0; break; } if (lc++ == 0) PORTF.OUTTGL = PIN5_bm; /* toggle LED */ while ((TCB0.INTFLAGS & 0x01) == 0); /* wait for bounce */ TCB0.INTFLAGS = 0x01; /* reset flag */ } /* Restore MCU state. */ PORTF.DIRCLR = PIN5_bm; TCB0.CTRLA = 0x00; TCB0.CCMP = 0; TCB0.INTFLAGS = 0x01; }