OctOS revisited: minimalist multi-tasking for AVR-0

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

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

  1. - OctOS can support eight tasks, each with a unique priority.
  2. - Tasks are disabled and enabled via idle and wake calls, respectively.
  3. - The lowest priority task is dedicated to (sleep and) spin and cannot be idled.
  4. - OctOS is coded in assembly.
  5. - Flash size is about 400 bytes.  RAM size is about 20 bytes.
  6. OctOS assumes that the compiler takes care of saving caller-saves registers, we only swap callee-saves and  the status register.

 

API:

  1. - Task IDs are `OCT_TASK0` to `OCT_TASK7`.
  2. - `OCT_TASK0` is the highest priority task; `OCT_TASK7` is the lowest.
  3. - `oct_os_init(id)` (usually in `main`) initializes OctOS, where `id` is the task id for the main task
  4. - `oct_attach_task(id, ftn, stk, siz)` will attached a task
  5.   - id is the ``OCT_TASK0`` .. ``OCT_TASK7``
  6.   - ftn is the task function
  7.   - stk is data for stack
  8.   - siz is the stack size
  9. - `oct_wake_task(id-set)`: wake-up tasks where `id-set` is bitwise-or'd list of task-ids to wake up
  10. - `oct_idle_task(id-set)`: sleep tasks where id-set` is bitwise or'd list of tasks to put to sleep
  11. - `oct_detach_task(id)` removes task `id`

 

Notes on the design:

  1. - There is one user main task, which can be any one of Task0 through Task6 which may use all registers.
  2. - Interrupts are not masked during the bulk the task swap operation.
  3. - 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;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Here are the header and assembly files:

/* octos.h - 
 * 
 * Copyright (C) 2019-2022 Matthew R. Wette
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#ifndef OCTOS_H_
#define OCTOS_H_

#ifndef __ASSEMBLER__
#ifndef OCT_TMPREG
#define OCT_TMPREG GPIOR0
#endif
#ifndef OCT_PORT
#define OCT_PORT PORTE
#endif
#ifndef OCT_PIN_bm
#define OCT_PIN PIN0_bm
#endif
#ifndef OCT_SWISR
#define OCT_SWISR() asm(	\
  "  out %0,r22\n"		\
  "  ldi r22,%1\n"		\
  "  sts %2,r22\n"		\
  "  ldi r22,lo8(%3)\n"		\
  "  push r22\n"		\
  "  ldi r22,hi8(%3)\n"		\
  "  push r22\n"		\
  "  in r22,%0\n"		\
  "  reti\n"			\
  :: "m"(OCT_TMPREG), "n"(OCT_PIN_bm), \
     "n"(_SFR_MEM_ADDR(OCT_PORT.INTFLAGS)), "m"(oct_swap_task))
// The above code basically makes the ISR look like the
// executing non-interrupt code made a call to oct_swap_task.
// After RETI is executed, the previous PC is on the stack.
#endif
#endif

#define OCT_TASK0 0x01
#define OCT_TASK1 0x02
#define OCT_TASK2 0x04
#define OCT_TASK3 0x08
#define OCT_TASK4 0x10
#define OCT_TASK5 0x20
#define OCT_TASK6 0x40
#define OCT_TASK7 0x80

#ifndef __ASSEMBLER__
#include <stdint.h>

void oct_os_init(uint8_t id);
void oct_attach_task(uint8_t id, void (*fn)(void), uint8_t *sa, uint16_t sz);
void oct_detach_task(uint8_t id);

void oct_idle_task(uint8_t id_set) __attribute__((noinline));
void oct_wake_task(uint8_t id_set) __attribute__((noinline));

void oct_spin();
void oct_rest();
void oct_swap_task();

static inline uint8_t oct_cur_task(void) {
  extern uint8_t oct_curmask;
  return (oct_curmask + 1);
}

#else /* __ASSEMBLER__ */

.global oct_init
.global oct_attach_task
.global oct_detach_task
.global oct_idle_task
.global oct_wake_task
.global oct_spin, oct_rest
.global oct_swap_task

#endif /* __ASSEMBLER__ */

#endif
/* --- last line --- */

 

;;; octos.S -

;;; Copyright (C) 2019-2022 Matthew R. Wette

;;; Permission is hereby granted, free of charge, to any person obtaining a copy
;;; of this software and associated documentation files (the "Software"), to
;;; deal in the Software without restriction, including without limitation the
;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
;;; sell copies of the Software, and to permit persons to whom the Software is
;;; furnished to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be included in
;;; all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
;;; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
;;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.

;;; notes:
;;; 1) ISR convention is
;;;    + disabled when an interrupt occurs and
;;;    + enabled when RETI is executed
;;; 2) ABI (gcc, avr-libc)
;;;    + caller saves: r18-r27,r30-r31
;;;    + callee saves: r2-r17,r28-r29
;;;    + callee clear: r1
;;;    + unsaved temp: r0
;;;    + ftn args in r25:r24 .. r9:8
;;;    + ftn ret in r25:r24 .. r19:r18
;;; 3) minimum stack: ret-addr + sreg + mask + NSAVREG + interrupts
;;; 4) difference between push/pop and sts,lds:
;;;    push is post-decrement (SP), sts is pre-decrement (XYZ)
;;;    pop is pre-increment (SP), lds is post-decrement (XYZ)

;;; TODOs
;;; 1) check for opportunities to save insn's using CPI, CPSE
;;; 2) in resched, break if swap-in TCB is 0x0000

#ifndef OCT_PORT_OUTTGL
#define OCT_PORT_OUTTGL 0x487
#endif
#ifndef OCT_PIN_bm
#define OCT_PIN_bm 0x01
#endif
#ifndef OCT_SWINT
#define OCT_SWINT ldi r24,OCT_PIN_bm $ sts OCT_PORT_OUTTGL,r24
#endif
	
;;; === OctOS =================================================================

#define OCT_TASK7 0x80
	
;;; If you feel brave and you asm-code all non-main tasks to use a reduced set
;;; of registers, you can reduce callee reg set to make context switches faster.

#define PUSH_CALLER_REGS \
	push r18 $ push r19 $ push r20 $ push r21 $ push r22 $ push r23 $ \
	push r24 $ push r25 $ push r26 $ push r27 $ push r30 $ push r31

#define POP_CALLER_REGS \
	pop  r31 $ pop  r30 $ pop  r27 $ pop  r26 $ pop  r25 $ pop  r24 $ \
	pop  r23 $ pop  r22 $ pop  r21 $ pop  r20 $ pop  r19 $ pop  r18

#define PUSH_CALLEE_REGS \
	push r0  $ push r1  $ push r2  $ push r3  $ push r4  $ push r5  $ \
	push r6  $ push r7  $ push r8  $ push r9  $ push r10 $ push r11 $ \
	push r12 $ push r13 $ push r14 $ push r15 $ push r16 $ push r17 $ \
	push r28 $ push r29

#define POP_CALLEE_REGS \
	pop r29  $ pop r28  $ pop r17  $ pop r16  $ pop r15  $ pop r14  $ \
	pop r13  $ pop r12  $ pop r11  $ pop r10  $ pop r9   $ pop r8   $ \
	pop r7   $ pop r6   $ pop r5   $ pop r4   $ pop r3   $ pop r2   $ \
	pop r1   $ pop r0

#define PUSH_REGS  PUSH_CALLEE_REGS
#define POP_REGS   POP_CALLEE_REGS
#define NSAVREG    20

	.equ SPL,0x3D
	.equ SPH,0x3E
	.equ SREG,0x3F
	.equ TCB_SIZE,2		; TCB holds stack pointer only

	;; octos functions
	.global oct_os_init
	.global oct_attach_task
	.global oct_detach_task
	.global oct_idle_task
	.global oct_wake_task
	.global oct_spin
	.global oct_rest
	.global oct_swap_task

	.section .progmem
	.asciz "OctOS v220622c"

	.section .data
	.global oct_curmask
	.global oct_rdylist

oct_curmask: .skip 1		; current mask, e.g., 0x07 for id 0x08
oct_rdylist: .skip 1		; list of ready tasks, one per bit
do_swap: .skip 1		; task swap needed
swap_ip: .skip 1		; swap in progress
	.align 4
tcbtabl: .skip 8*TCB_SIZE	; TCB table (stack pointer only)

	.section .text

	;; idle tasks
	.type oct_spin,function
oct_spin:
0:	rjmp 0b
	.size oct_spin, .-oct_spin

	.type oct_rest,function
oct_rest:
0:	sleep
	rjmp 0b
	.size oct_rest, .-oct_rest

	;; void oct_os_init(uint8_t id)
	;; id: main task id (r24)
	.type oct_os_init,function
oct_os_init:
	push r29
	push r28
	mov r25,r24		; load task-id arg
	;; init TCB for TASK7..TASK0
	ldi r27,hi8(tcbtabl)
	ldi r26,lo8(tcbtabl)
	adiw X,7*TCB_SIZE
	ldi r24,OCT_TASK7	; r24 has loop task id
	ldi r23,OCT_TASK7-1	; r23 has loop task mask
	clr r20			; init SPH,SPL to zero
0:	lsr r23			; shift task mask
	lsr r24			; shift task id
	breq 2f			; done?
	cp r24,r25		; curr-id == caller-id ?
	brne 1f
	sts oct_curmask,r23	; log caller if so
1:	st -X,r20		; unallocated SPL
	st -X,r20		; unallocated SPH
	rjmp 0b
	;; log caller
	st -X,r20		; 0 => SPL in TCB
	st -X,r20		; 0 => SPH in TCB
	rjmp 0b
2:	;; update vars
	ldi r21,0x80
	or r21,r25		; add task-id arg
	sts oct_rdylist,r21	; updated ready list
	sts do_swap,r20		; for swap during
	sts swap_ip,r20		;   interrupts
	;; return
	pop r28
	pop r29
	ret
	.size oct_os_init, .-oct_os_init

	;; void oct_register_task(uint8_t id, void (*fn)(void),
	;;                        uint8_t *sp, uint16_t sz)
	;; id: task id (r24)
	;; fn: function (r23:r22)
	;; sp: stack memory pointer (r21:r20)
	;; sz: stack size (r19:r18)
	.type oct_attach_task,function
oct_attach_task:
	;; from task id, generate oct_curmask
	mov r25,r24
	subi r25,1
	;; load Z with TCB table end addr
	ldi r30,lo8(tcbtabl)
	ldi r31,hi8(tcbtabl)
0:	adiw Z,TCB_SIZE
	lsr r24
	brcc 0b
	;; stack top addr in r21:r20
	add r20,r18
	adc r21,r19
	;; load X with stack pointer
	movw r26,r20
	sbiw X,1
	;; save return address
	st -X,r22
	st -X,r23
	;; room for registers plus sreg
	sbiw X,NSAVREG+1
	;; save mask
	st -X,r25
	;; adjust for pop pre increment
	sbiw X,1
	;; save stack pointer to TCB
	st -Z,r27
	st -Z,r26
 	ret
	.size oct_attach_task, .-oct_attach_task

oct_detach_task:
	;; load Z with TCB table end addr
	ldi r30,lo8(tcbtabl)
	ldi r31,hi8(tcbtabl)
0:	adiw Z,TCB_SIZE
	lsr r24
	brcc 0b
	;; zero out the TCB
	st -Z,r24
	st -Z,r24
	ret
	.size oct_detach_task, .-oct_detach_task
	
	;; void oct_wake_task(uint8_t id_set)
	;;   id: or'd ids of tasks to wake (r24)
	;; Set task-id bits (id_set) in oct_rdylist.
	;; Interrupts blocked during this, restored on exit.
	.type oct_wake_task,function
oct_wake_task:
	in r23,SREG
	cli
	lds r25,oct_rdylist
	or r25,r24
	sts oct_rdylist,r25
	lds r24,oct_curmask
	and r25,r24
	breq 1f
	OCT_SWINT
1:	out SREG,r23
	ret
	.size oct_wake_task, .-oct_wake_task

	;; oct_idle_task(uint8_t id_set)
	;;   id: task ID to idle (r24)
	;; Clear task-id bits (id_set) in oct_rdylist.
	;; Interrupts blocked during this, restored on exit.
	.type oct_idle_task,function
oct_idle_task:
	andi r24,~OCT_TASK7	; never idle task7
	com r24			; ones complement
	in r23,SREG
	cli
	lds r25,oct_rdylist
	and r25,r24
	sts oct_rdylist,r25
	OCT_SWINT
	out SREG,r23
	ret
	.size oct_idle_task, .-oct_idle_task

	;; void oct_swap_task()
	;; This assumes no CALLER_SAVES registers need to be saved/restored.
	;; Rationale: This is only called from SWISR after idle or wake has
	;; been called and those will be interrupted (oh, unless CEI!)
	.type oct_swap_task,function
oct_swap_task:
	;; move following to SWINT?
	in r23,SREG
	cli
	;; if running, flag and return
	lds r25,swap_ip
	tst r25
	breq swap_body
	sts do_swap,r25		; flag to re-run
	out SREG,r23
	ret
swap_body:
	lds r24,oct_curmask
	ldi r25,1
	sts swap_ip,r25
	;; interrupts enabled in this section
	sei
	push r23		; push SREG
	PUSH_REGS 	; save registers
	push r24		; push curmask
	;; incoming TCB addr => Y (TODO: if Y is zero, abort)
	lds r25,oct_rdylist
	ldi r28,lo8(tcbtabl)
	ldi r29,hi8(tcbtabl)
0:	lsr r25
 	brcs 1f
	adiw Y,TCB_SIZE
	rjmp 0b
1:	ld r22,Y+		; load incoming SPL from TCB
	ld r23,Y+		; load incoming SPH from TCB
	;; outgoing TCB addr => Y
	ldi r28,lo8(tcbtabl)
	ldi r29,hi8(tcbtabl)
0:	lsr r24
	brcc 1f
	adiw Y,TCB_SIZE
	rjmp 0b
	;; swap stack pointers
1:	cli
	in r24,SPL
	in r25,SPH
	out SPL,r22
	out SPH,r23
	sei
	st Y+,r24		; save outgoing SPL to TCB
	st Y+,r25		; save outgoing SPH to TCB
	pop r24			; load curmask
	sts oct_curmask,r24	;   and restore 
	POP_REGS		; restore registers
	pop r23			; load SREG, restore later
	;; trailer
	cli
	lds r25,do_swap
	tst r25
	breq 1f
	;; recheck:
	;; 1) done if (rdylist & curmask) == 0
	lds r25,oct_rdylist
	and r25,r24
	breq 1f
	;; 2) done if rdylist & (curmask+1) != 0
	lds r25,oct_rdylist
	inc r24
	and r25,r24
	brne 1f
	;; 
	;; rerun task swap
	sts do_swap,r1
	rjmp swap_body		; redo w/ r23=SREG, r24=curmask
1:	;; done
	sts swap_ip,r1		; r1 == 0 here
	out SREG,r23		; restore SREG
	ret
	.size oct_swap_task, .-oct_swap_task

;;; --- last line ---

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Work flows that are quasi-parallel in time can be managed better/simpler using/inside a simple timer interrupt.

Maybe you can explain what concrete advantage (given the weak 8-bit hardware) an additional multitasking OS (push/pop everything) has here. 

"Simple-to-use" That's still pretty unclear to me personally, you could describe this better in words instead of code...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

GermanFranz wrote:

Work flows that are quasi-parallel in time can be managed better/simpler using/inside a simple timer interrupt.

Maybe you can explain what concrete advantage (given the weak 8-bit hardware) an additional multitasking OS (push/pop everything) has here. 

"Simple-to-use" That's still pretty unclear to me personally, you could describe this better in words instead of code...

 

I agree in general, the "cooperative" tasking, in which you chop up parallel computation and poll for conditions to continue is most often the better choice at this level, but if you can't chop up your computation the right way then you need to stop computation.  I don't buy the argument that pre-emptive multi-taking is never needed.

 

By simple, I mean small API that should be simple to comprehend.   The implementation is not simple.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Against the background of many of my own measurement/control projects with 8-bit controllers, I try to imagine where I would have needed such pre-emptive multitasking. Unfortunately I can't think of anything.

 

MattRW wrote:
I don't buy the argument that pre-emptive multi-taking is never needed.

 

A specific example would be helpful here.

I would like to understand which useful functionality outweighs the severe disadvantage of an additional OS with processing time/memory consumption on a weak 8-bit controller. "Poll for conditions to continue" - this is daily bread in general, is possible everywhere and should be done (on a weak 8-bit controller) instantly on the spot. 

 

The question behind everything: You keep the small controller busy with administrative tasks. What is it really worth?

An operating system makes sense when it simplifies things. Does it?

 

MattRW wrote:
By simple, I mean small API that should be simple to comprehend

 

This "word-less" explanation just doesn't make it any clearer for me. Please try to explain without code: How are my functions provided to the OS, what services does the OS provide for my functions and why is it better to let a OS decide priorities instead of my tasks themselves.

 

Last Edited: Thu. Jun 23, 2022 - 01:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MattRW wrote:
By simple, I mean small API that should be simple to comprehend.
C's run-time? (rhetorical : not simple)

Reasons :

  • multiple computer languages have a tasking interface
  • portability (ideally change RTOS or framework without changing the application, application -> computer language run-time -> RTOS or framework)

 

ISO/IEC 9899:201x

Programming languages — C

[page 394]

7.26 Threads

C++ Programming/Threading - Wikibooks, open books for an open world

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

GermanFranz wrote:
An operating system makes sense when it simplifies things.
An RTOS or framework can increase composability.

https://youtu.be/o3eyz1gEqGU?t=561 for 20s in Beyond the RTOS - Part 1: Concurrency & "spaghetti" as main challenges of professional developers - YouTube (18m56s) by Miro Samek

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

gchapman wrote:
An RTOS or framework can increase composability.

 

Composability can also be achieved without RTOS.

And there is a fine line between RTOS benefit and harm @ 8Bit controllers.

The more powerful the hardware, the more can be invested in the organizing

function of an operating system, even if it costs performance and memory.

 

I find the evaluation of "spaghetti code" and what "professional" means here interesting.

First and foremost, a "professional" solution is one that works flawlessly with minimal effort.

For all its bad reputation, "spaghetti code" would often be the most professional solution.

It is a solution of direct and shortest ways. But, if a project takes on larger dimensions,

unfortunately there is quickly a lack of overview and the bug rate increases. 

Then there is no efficient, bug-free and therefore professional solution.

 

In my opinion, it is important to consider what hardware level you are at.

In a 32-bit PC environment, completely different principles apply.

 

Last Edited: Thu. Jun 23, 2022 - 07:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think this is pretty neat...a different way of organizing things, albeit with some processing overhead.

Many times the processor is just wasting cycles "doing nothing anyway"...so why not use this to give an impression of parallelism, with out having to worry as much about it.

 

One  thought---it would read much nicer to get rid of "oct" in the API---it sorta juts out like a sore thumb, unclean

  1.  Task IDs are `TASK0` to `TASK7`.
  2. - `os_init(id)` (usually in `main`) initializes OctOS, where `id` is the task id for the main task
  3. - `attach_task(id, ftn, stk, siz)` will attached a task
  4.  
  5. - `wake_task(id-set)`: wake-up tasks where `id-set` is bitwise-or'd list of task-ids to wake up
  6. - `idle_task(id-set)`: sleep tasks where id-set` is bitwise or'd list of tasks to put to sleep
  7. - `detach_task(id)` removes task `id`

now reads like English--nice!

 

 

Now I do agree with German Franz ...you probably don't need to use OS (and very very rarely have to), but this is fun!  It's a little magical to see such things in action.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Jun 23, 2022 - 04:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:

you probably don't need to use OS (and very very rarely have to), but this is fun!  It's a little magical to see such things in action.

 

Please don't get me wrong: I don't want to spoil the fun!

Unfortunately, even the greatest effort (doing such a project) does not necessarily / automatically result in great benefit.

But it shows what is possible and may give suggestions in other directions .

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:

I think this is pretty neat...a different way of organizing things, albeit with some processing overhead.

Many times the processor is just wasting cycles "doing nothing anyway"...so why not use this to give an impression of parallelism, with out having to worry as much about it.

 

One  thought---it would read much nicer to get rid of "oct" in the API---it sorta juts out like a sore thumb, unclean

  1.  Task IDs are `TASK0` to `TASK7`.
  2. - `os_init(id)` (usually in `main`) initializes OctOS, where `id` is the task id for the main task
  3. - `attach_task(id, ftn, stk, siz)` will attached a task
  4.  
  5. - `wake_task(id-set)`: wake-up tasks where `id-set` is bitwise-or'd list of task-ids to wake up
  6. - `idle_task(id-set)`: sleep tasks where id-set` is bitwise or'd list of tasks to put to sleep
  7. - `detach_task(id)` removes task `id`

now reads like English--nice!

 

This can be easily accommodated using #define (in octos.h).  The usual purpose of the prefix is to avoid name clashes.  But for AVRs that's not a big risk.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

GermanFranz wrote:
Composability can also be achieved without RTOS.
Concur (framework)

GermanFranz wrote:
Then there is no efficient, bug-free and therefore professional solution.
If cannot convince managers to allow refactoring.

 


Efficiency | QP/C: Overview

 

 

"Dare to be naïve." - Buckminster Fuller