A Place Where No Dreams Come True...

Current Topic: Multitasking is really a simple operation. Even for an Arduino AVR class processor. The real trick is designing the tasks so that they don't interfere with each other, and, that there is enough time for each task to do its work without falling behind.

A Simple Non-Preemptive Multitasking Design For A Simple 8-Bit Processor...

In order to multitask you need a few tasks. In this demonstration earlier projects have been combined, as individual tasks, to create a result where all tasks appear to operate simultaneously. When in reality each task gets a small window of processing time, on a cooperative level, at a very rapid rate. What is meant by cooperative is that each tasks is responsible for using only a small percent of processing time. This is accomplished using system calls to switch (yield) to the next task on a regular basis. For some tasks this means every so often while in long loops or for I/O based tasks, usually, anytime the interface is busy and/or every time an interface operation is completed.

In this example the multitasker is written in C based on the setjmp/longjmp model. It is not completely portable as the jmpbuf definition and the setjmp longjmp pair are always processor and environment specific. It is however easily modified to work in any C environment that provides setjmp.

The Actual Multitasker Is Based Entirely On C And Is Remarkably Simple...

In this example the Multitasker utilizes a single static variable which holds a single pointer to a linked list of task objects. The list is both forward and reverse linked allowing for insertion (link) and deletion (unlink) of tasks. Each task is created dynamically from the system heap and allows specifying the stack size.

The multitasker exposes three methods. A method for creating a task; A method for switching context to the next task; And a method for jumping from the startup environment into the multitasking environment. At this point it is not possible to return to the startup and its stack is additionally abandoned. Although it is possible to delete (unlink) a task no method has been provided yet.

//----------------------------------------------------------------------------
// file: pause.cpp
//
// Smegduino setjmp/longjmp based multitasker implementation for an Arduino AVR
//  class processor.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//----------------------------------------------------------------------------

#include <avr/pgmspace.h>
#include <stdlib.h>
#include <string.h>

#include "smegtasker.h"

//-----------------------------------------------------------------------------
// Root pointer to linked list tasks. Each new task is forward and reverse
//  linked so they can not only be added they can additionally be 'unlinked'
//  (removed) making for a very flexible implementation.

static struct _task *_root = (struct _task*)0;

//-----------------------------------------------------------------------------
// Use this for debugging... Really should not be exposed though.

uint8_t const *smegtask_get_root(void)
{
    return (uint8_t*)_root;
}

//-----------------------------------------------------------------------------
// Call this whenever a task would 'block' the call or periodically within
//  long loops to 'yield' to other tasks (cooperate).

void pause(void)
{
  if(_root)
  {
    if(!setjmp(_root->frame.jmpbuf))
    {
      _root = _root->next;
      longjmp(_root->frame.jmpbuf, 1);
    }
  }
}

//-----------------------------------------------------------------------------
// Create a task and link it into the list. The task is immediately available
//  when this function returns.

void create_task(uint16_t size, void(*func)(void), const char *name)
{
  // Allocate from the local heap.
  struct _task *task = (struct _task*)malloc(sizeof(struct _task) + size);
  if(task)
  {
    memset(task, 0, sizeof(struct _task) + size);
    if(_root == 0)
    {
      // First task.
      task->next = task;
      task->prev = task;
      _root = task;
    }
    else
    {
      // Link forward and reverse paths.
      task->next = _root->next;
      task->next->prev = task;
      task->prev = _root;
      _root->next = task;
    }
    // Debugging feature - assign name.
    strncpy_P(task->name, name, TASKNAMESIZE);
    // Create context buffer.
    if(!setjmp(task->frame.jmpbuf))
    {
      // Initial Frame-Pointer (parameter register overflow into the stack).
      task->frame.context.fp = (uint16_t)(&task->stack[size]);
      // Initial Stack-Pointer (Top-Of-Stack TOS).
      task->frame.context.sp = (uint16_t)&task->stack[size];
      // Return-Pointer at TOS to be executed when task starts.
      task->frame.context.rp = (uint16_t)func;
    }
  }
}

//-----------------------------------------------------------------------------
// Transfer processor context to the Multitasker.
// NOTE: There is no return from here and the calling stack is abandoned. 

void idle_task(void)
{
  longjmp(_root->frame.jmpbuf, 1);
}

//-----------------------------------------------------------------------------
// end: pause.cpp

There is an additional Header (definition) file for the multitasking interface.

//----------------------------------------------------------------------------
// file: smegtasker.h
//
// Smegduino setjmp/longjmp based multitasker definotion for an Arduino AVR
//  class processor.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//----------------------------------------------------------------------------

#include <stdint.h>
#include <setjmp.h>

#define TASKNAMESIZE 11

struct _context {
  uint8_t      r2;
  uint8_t      r3;
  uint8_t      r4;
  uint8_t      r5;
  uint8_t      r6;
  uint8_t      r7;
  uint8_t      r8;
  uint8_t      r9;
  uint8_t      r10;
  uint8_t      r11;
  uint8_t      r12;
  uint8_t      r13;
  uint8_t      r14;
  uint8_t      r15;
  uint8_t      r16;
  uint8_t      r17;
  uint16_t     fp;
  uint16_t     sp;
  uint8_t      sr;
  uint16_t     rp;
};

union _frame {
  jmp_buf         jmpbuf;
  struct _context context;
};

struct _task {
  struct _task *next;
  struct _task *prev;
  char         name[TASKNAMESIZE + 1];
  union _frame frame;
  uint8_t      stack[4];
};

//-----------------------------------------------------------------------------

extern uint8_t const *smegtask_get_root(void);
extern void pause(void);
extern void create_task(uint16_t, void(*)(void), const char*);
extern void idle_task(void);

//-----------------------------------------------------------------------------
//end: smegtasker.h

Designing The Tasks...

The first task is a Console (Serial) task. It is the primary task, in this example, and must be initialized first as other tasks rely on this interface for configuration and statistical data. Initialization is important because it is responsible for setting up the USART (serial) hardware and enabling the Interrupt Service Routines (ISR) that handle the actual transfer of data between the interface buffers and the hardware.

In this case inbound data is directed to a command interpreter which also shares its outbound data with status processing. Care had to be taken designing the other tasks so that the outbound (driver) buffer never overflows which would block the current task and and possibly corrupt the buffer with out-of-sequence data from another task. Care also had to be taken so that random events of this type were recoverable. This is accomplished through the use of a lightweight communication protocol that is tolerant but not statistically perfect.

//-----------------------------------------------------------------------------
// file: console.ino
//
// Smegduino custom interrupt driven serial interface demonstration.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <console.h>

//-----------------------------------------------------------------------------

static const char STR_PROMPT[] PROGMEM = "%s oK.\n";
static const char STR_ERROR[] PROGMEM = "%s Huh!\n";

#define CONSOLE_SIZE 40

//-----------------------------------------------------------------------------

static void console_get_line(char *buffer)
{
  int16_t c;
  uint8_t i;

  for(i = 0; i < CONSOLE_SIZE; i++)
  {
    while((c = console_read()) == -1)
    {
      pause();
    }
    if(c == '\n')
    {
      buffer[i] = 0;
      break;
    }
    buffer[i] = c;
  }
  buffer[CONSOLE_SIZE - 1] = '\0';
}

//-----------------------------------------------------------------------------

void console_send(const char *data)
{
  uint16_t i;
  uint16_t len = strlen(data);
  for(i = 0; i < len; i++)
  {
    while(console_write(data[i]) == -1)
    {
      pause();
    }
  }
}

//-----------------------------------------------------------------------------

void console_send_P(const char *data)
{
  uint16_t i;
  uint16_t len = strlen_P(data);
  for(i = 0; i < len; i++)
  {
    while(console_write(pgm_read_byte_near(&data[i])) == -1)
    {
      pause();
    }
  }
}

//-----------------------------------------------------------------------------

static void console_send_line(const char *data)
{
  console_send(data);
  while(console_write('\n') == -1)
  {
    pause();
  }
}

//-----------------------------------------------------------------------------

void console_open(void)
{
  console_init(BAUD_230400);
}

//-----------------------------------------------------------------------------

void console(void)
{
  char _console[CONSOLE_SIZE + 1];

  while(1)
  {
    pause();
    console_get_line(_console);
    console_send_line(_console);
    interpret((uint8_t*)_console);
  }
}

//-----------------------------------------------------------------------------
// end: console.ino

The console (serial) task needs an additional Interrupt Service Routine (ISR) that is written in AVR Assembly code language.

;-----------------------------------------------------------------------------
; file: console.S
;
; Smegduino USART serial interface driver example.
;
; Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
;
;  All Smegware software is free; you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation; either version 2 of the License, or
;  (at your option) any later version.
;
;  This software is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;  GNU General Public License for more details.
;
;-----------------------------------------------------------------------------
;
; History...
;
;   $Source$
;   $Author$
; $Revision$
;
; $Log$
;------------------------------------------------------------------------------
#define __SFR_OFFSET 0
#include <avr/io.h>

#include "avrbaud.h"
;-----------------------------------------------------------------------------
; Convenience macros.
#define RZERO R1
#define RIN R18
#define ROUT R19
#define RPARAM2L R22
#define RPARAM2H R23
#define RPARAM1L R24
#define RPARAM1H R25
#define RPARAM1 RPARAM1L
#define RPARAM2 RPARAM2L

;-----------------------------------------------------------------------------
; USART Control and Status Register-A bit-field definitions.
.equ BIT_MPCM,    0
.equ BIT_U2X,     1
.equ BIT_UPE,     2
.equ BIT_DOR,     3
.equ BIT_FE,      4
.equ BIT_UDRE,    5
.equ BIT_TXC,     6
.equ BIT_RXC,     7
.equ MASK_MPCM,   0B00000001
.equ MASK_U2X,    0B00000010
.equ MASK_UPE,    0B00000100
.equ MASK_DOR,    0B00001000
.equ MASK_FE,     0B00010000
.equ MASK_UDRE,   0B00100000
.equ MASK_TXC,    0B01000000
.equ MASK_RXC,    0B10000000
; USART Control and Status Register-B bit-field definitions.
.equ BIT_TXB8,    0
.equ BIT_RCB8,    1
.equ BIT_UCSZ2,   2
.equ BIT_TXEN,    3
.equ BIT_RXEN,    4
.equ BIT_UDRIE,   5
.equ BIT_TXCIE,   6
.equ BIT_RXCIE,   7
.equ MASK_TXB8,   0B00000001
.equ MASK_RCB8,   0B00000010
.equ MASK_UCSZ2,  0B00000100
.equ MASK_TXEN,   0B00001000
.equ MASK_RXEN,   0B00010000
.equ MASK_UDRIE,  0B00100000
.equ MASK_TXCIE,  0B01000000
.equ MASK_RXCIE,  0B10000000
; USART Control and Status Register-C bit-field definitions.
.equ BIT_UCPOL,   0
.equ BIT_UCSZ0,   1
.equ BIT_UCSZ1,   2
.equ BIT_USBS,    3
.equ BIT_UPM0,    4
.equ BIT_UPM1,    5
.equ BIT_UMSEL0,  6
.equ BIT_UMSEL1,  7
.equ MASK_UCPOL,  0B00000001
.equ MASK_UCSZ0,  0B00000010
.equ MASK_UCSZ1,  0B00000100
.equ MASK_USBS,   0B00001000
.equ MASK_UPM0,   0B00010000
.equ MASK_UPM1,   0B00100000
.equ MASK_UMSEL0, 0B01000000
.equ MASK_UMSEL1, 0B10000000
; USART0 register definitions.
.equ REG_UCSR0A,  0XC0
.equ REG_UCSR0B,  0XC1
.equ REG_UCSR0C,  0XC2
.equ REG_UBRR0L,  0XC4
.equ REG_UBRR0H,  0XC5
.equ REG_UDR0,    0XC6
;------------------------------------------------------------------------------
; Driver FIFO buffer size - Currently 8-bit so must be between 1 - 255.
#define FIFO_SIZE 96
;------------------------------------------------------------------------------
; Console receive status.
.lcomm rx_in_pnt,1          ; Receive FIFO input pointer - uint16_t.
.lcomm rx_out_pnt,1         ; Receive FIFO output pointer - uint16_t.
;------------------------------------------------------------------------------
; Console receive data - shared by interrupt service routine.
.lcomm rx_fifo,FIFO_SIZE    ; Receive input buffer - uint8_t[n].
;------------------------------------------------------------------------------
; Console transmit status.
.lcomm tx_in_pnt,1          ; Transmit FIFO input pointer - uint16_t.
.lcomm tx_out_pnt,1         ; Transmit FIFO output pointer - uint16_t.
;------------------------------------------------------------------------------
; Console transimt data - shared by interrupt service routine.
.lcomm tx_fifo,FIFO_SIZE    ; Transmit output buffer - uint8_t[n].
;------------------------------------------------------------------------------
; BAUD rate lookup table - Translate to BAUD generator divider table in PROM.
;  Note: Must be synchronized with avrbaud.h else Uh oh!
.text
baudrate: .word 832,416,207,138,103,68,51,34,25,16,8
;------------------------------------------------------------------------------
; Console initialize.
;  From C - void console_init(uint8_t)
;  where uint8_t = BAUD rate constant defined in avrbaud.h
.global console_init
console_init:
; Reset hardware.
    STS     REG_UCSR0B,RZERO        ; Disable USART RX, TX and interrupts.
    LDI     ROUT,(MASK_UCSZ1 | MASK_UCSZ0) ; 8 bits.
    STS     REG_UCSR0C,ROUT         ; Set - Asynchroneous, No parity, 1 Stop
    LDI     ROUT,MASK_U2X           ; BAUD generator divisor.
    STS     REG_UCSR0A,ROUT         ;
; Initialize variables (RAM).
    STS     rx_in_pnt,RZERO         ; Clear all FIFO index variables.
    STS     rx_out_pnt,RZERO        ;
    STS     tx_in_pnt,RZERO         ;
    STS     tx_out_pnt,RZERO        ;
; Initialize hardware.
    LDI     ZL,lo8(baudrate)        ; Baudrate lookup table
    LDI     ZH,hi8(baudrate)        ;  address.
    ADD     ZL,RPARAM1L             ; Point to baud init value.
    ADC     ZH,RZERO                ;  vector.
    LPM     ROUT,Z+                 ;
    STS     REG_UBRR0L,ROUT         ; Baudrate low-byte.
    LPM     ROUT,Z                  ;
    STS     REG_UBRR0H,ROUT         ; Baudrate high-byte.
 ; Enable RX and TX and Interrupts.
    LDS     ROUT,REG_UCSR0B         ;
    ORI     ROUT,(MASK_RXEN | MASK_TXEN | MASK_RXCIE)
    STS     REG_UCSR0B,ROUT         ; Enable receiver and transmitter.
    RET
;------------------------------------------------------------------------------
; Pull uint8_t data from receive FIFO - Returns int16_t -1 on failure.
;  From C - int16_t console_read(void)
; This is a non-blocking routine - If return != -1 the return value is uint8_t.
.global console_read
console_read:
    SER     RPARAM1L                ; Default return -1.
    SER     RPARAM1H                ;  16-bit.
    LDS     RIN,rx_in_pnt           ; Get top of FIFO point.
    LDS     ROUT,rx_out_pnt         ; Get output FIFO point.
    CP      RIN,ROUT                ; Check available.
    BREQ    .rx_empty               ; FIFO empty.
    LDI     ZL,lo8(rx_fifo)         ; Get FIFO pointer
    LDI     ZH,hi8(rx_fifo)         ;  address.
    ADD     ZL,ROUT                 ; Point to current FIFO slot.
    ADC     ZH,RZERO                ;  16-bit.
    LD      RPARAM1L,Z              ; Get FIFO data.
    INC     ROUT                    ; Consumed.
    CPI     ROUT,FIFO_SIZE          ; Check for overflow.
    BRNE    .rx_no_ov               ; Still linear.
    CLR     ROUT                    ; Wrap circular buffer.
.rx_no_ov:
    STS     rx_out_pnt,ROUT         ; Update output index pointer.
    CLR     RPARAM1H                ; Return true.
.rx_empty:
    RET
;------------------------------------------------------------------------------
; USART Receive Interrupt Service Routine.
.global USART0_RX_vect
USART0_RX_vect:
    PUSH    ZL                      ; Preserve linear state.
    IN      ZL,SREG                 ;
    PUSH    ZL                      ; Preserve Status register.
    PUSH    ZH                      ; Preserve linear state.
    PUSH    RIN                     ;
    PUSH    ROUT                    ;
    PUSH    RZERO                   ;
    CLR     RZERO                   ; Force.
; Check FIFO availability.
    LDS     RIN,rx_in_pnt           ; Input pointer index (local).
    LDS     ROUT,rx_out_pnt         ; Output pointer index (interrupt).
    LDI     ZL,lo8(rx_fifo)         ; Get FIFO pointer
    LDI     ZH,hi8(rx_fifo)         ;  address.
    ADD     ZL,RIN                  ; Point to next available slot
    ADC     ZH,RZERO                ;  16-bit.
    LDS     ROUT,REG_UDR0           ; Pull data from hardware (USART).
    ST      Z,ROUT                  ; Push data to FIFO.
    INC     RIN                     ; Point to next slot.
    CPI     RIN,FIFO_SIZE           ; Check buffer overflow.
    BRNE    .irx_in_ov              ; Still linear.
    CLR     RIN                     ; Overflow wrap circular buffer.
.irx_in_ov:
    CP      RIN,ROUT                ; Full if equal.
    BREQ    .irx_full               ; FIFO full exit - ERROR - Ignore.
    STS     rx_in_pnt,RIN           ; Update input pointer index.
.irx_full:
; Restore and return.
    POP     RZERO                   ; Restore pre-interrupt state.
    POP     ROUT                    ;
    POP     RIN                     ;
    POP     ZH                      ;
    POP     ZL                      ;
    OUT     SREG,ZL                 ; Restore status Register.
    POP     ZL                      ;
    RETI                            ; Re-Enter linear processing state.
;------------------------------------------------------------------------------
; Push uint8_t data to transmit FIFO - Returns int16_t -1 on failure.
;  From C - int8_t console_write(uint8_t)
; This is a non-blocking routine - Returns -1 on error otherwise 0.
.global console_write
console_write:
    SER     RPARAM2L                ; Default return -1.
    LDS     RIN,tx_in_pnt           ; Input pointer index (local).
    LDS     ROUT,tx_out_pnt         ; Output pointer index (interrupt).
    LDI     ZL,lo8(tx_fifo)         ; Get FIFO pointer
    LDI     ZH,hi8(tx_fifo)         ;  address.
    ADD     ZL,RIN                  ; Point to next available slot
    ADC     ZH,RZERO                ;  16-bit.
    ST      Z,RPARAM1L              ; Push data to FIFO.
    INC     RIN                     ; Point to next slot.
    CPI     RIN,FIFO_SIZE           ; Check buffer overflow.
    BRNE    .tx_in_ov               ; Still linear.
    CLR     RIN                     ; Overflow wrap circular buffer.
.tx_in_ov:
    CP      RIN,ROUT                ; Difference determines slot available.
    BREQ    .tx_full                ; FIFO full return default (error).
    STS     tx_in_pnt,RIN           ; Update input pointer index.
 ; Enable TX Interrupts.
    LDS     ROUT,REG_UCSR0B         ; Get current state.
    ORI     ROUT,(MASK_UDRIE)       ; Enable transmit interrupt.
    STS     REG_UCSR0B,ROUT         ; Update.
    CLR     RPARAM2L                ; Return success.
.tx_full:
    MOV     RPARAM1L,RPARAM2L       ; Return flag.
    RET
;------------------------------------------------------------------------------
; USART Transmitter Data-Register-Empty Interrupt Service Routine.
.global USART0_UDRE_vect
USART0_UDRE_vect:
    PUSH    ZL                      ; Preserve linear state.
    IN      ZL,SREG                 ;
    PUSH    ZL                      ; Preserve Status register.
    PUSH    ZH                      ; Preserve linear state.
    PUSH    RIN                     ;
    PUSH    ROUT                    ;
    PUSH    RZERO                   ;
    CLR     RZERO                   ; Force.
; Check FIFO status.
    LDS     RIN,tx_in_pnt           ; Get top of FIFO point.
    LDS     ROUT,tx_out_pnt         ; Get output FIFO point.
    CP      RIN,ROUT                ; Check available.
    BREQ    .itx_empty              ; FIFO empty.
    LDI     ZL,lo8(tx_fifo)         ; Get FIFO pointer
    LDI     ZH,hi8(tx_fifo)         ;  address.
    ADD     ZL,ROUT                 ; Point to current FIFO slot.
    ADC     ZH,RZERO                ;  16-bit.
    LD      RIN,Z                   ; Get FIFO data.
    STS     REG_UDR0,RIN            ; Push data to hardware (USART).
    INC     ROUT                    ; Consumed.
    CPI     ROUT,FIFO_SIZE          ; Check for overflow.
    BRNE    .itx_no_ov              ; Still linear.
    CLR     ROUT                    ; Wrap circular buffer.
.itx_no_ov:
    STS     tx_out_pnt,ROUT         ; Update output index pointer.
    JMP     .itx_rtn                ; Restore and return.
; FIFO empty - Disable transmit interrupts.
.itx_empty:
    LDS     RIN,REG_UCSR0B          ; Control register.
    CBR     RIN,MASK_UDRIE | MASK_TXCIE ; Clear UDRIE interrupt.
    STS     REG_UCSR0B,RIN          ; Disable TX interrupts.
; Restore and return.
.itx_rtn:
    POP     RZERO                   ; Restore pre-interrupt state.
    POP     ROUT                    ;
    POP     RIN                     ;
    POP     ZH                      ;
    POP     ZL                      ;
    OUT     SREG,ZL                 ; Restore status Register.
    POP     ZL                      ;
    RETI                            ; Re-Enter linear processing state.
;------------------------------------------------------------------------------
; USART Transmit-Complete Interrupt Service Register.
.global USART0_TX_vect
USART0_TX_vect:
    PUSH    ROUT                    ; Preserve linear state.
    IN      ROUT,SREG               ;
    PUSH    ROUT                    ; Preserve Status register.
; Spurious Interrupt - Disable TX interrupts.
    LDS     ROUT,REG_UCSR0B         ; Control register.
    CBR     ROUT,MASK_UDRIE | MASK_TXCIE ; Clear UDRIE interrupt.
    STS     REG_UCSR0B,ROUT         ; Disable TX interrupts.
; Restore and return.
    POP     ROUT                    ;
    OUT     SREG,ROUT               ; Restore status Register.
    POP     ROUT                    ;
    RETI                            ; Re-Enter linear processing state.
;------------------------------------------------------------------------------
; end: console.S

Additionally... The console interface, for inbound data, is redirected to a command line interpreter which is used to set modes and report status for the system.

//-----------------------------------------------------------------------------
// file: interpret.ino
//
// Smegduino Command Line Interpreter example.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

enum _space { SPACE_NONE = 0, SPACE_PGM, SPACE_RAM, SPACE_REG, SPACE_IO };

//-----------------------------------------------------------------------------

struct _command {
  const char *cmd;
  uint32_t (*funct)(uint8_t*);
};

//-----------------------------------------------------------------------------

static const char STR_PU[]          PROGMEM = "%u";
static const char STR_P04X_[]       PROGMEM = "%04X ";
static const char STR_P04X[]        PROGMEM = "%04X";
static const char STR_P02X_[]       PROGMEM = "%02X ";
static const char STR_P_02X[]       PROGMEM = " %02X";
static const char STR_PLU[]         PROGMEM = "%lu";
static const char STR_PUC_PUC_PU[]  PROGMEM = "%u:%u:%u";
static const char STR_PNEWLINE[]    PROGMEM = "\n";

static const char STR_CUR_TIME[]    PROGMEM = "Current time - %u:%u:%u.%u - %lu ms.\n";

static const char CMD_ACCEL[]       PROGMEM = "accel";
static const char CMD_CLOCK[]       PROGMEM = "clock";
static const char CMD_RATE[]        PROGMEM = "rate";
static const char CMD_TICK[]        PROGMEM = "tick";
static const char CMD_TIME[]        PROGMEM = "time";

//-----------------------------------------------------------------------------

static uint32_t cmd_accel(uint8_t *params)
{
  unsigned dmy = 0;

  sscanf_P((char*)params, STR_PU, &dmy);
  display_set_mode(DPY_MODE_ACCEL);
  return dmy;
}

//-----------------------------------------------------------------------------

static uint32_t cmd_rate(uint8_t *params)
{
  unsigned dmy = 0;

  sscanf_P((char*)params, STR_PU, &dmy);
  display_set_mode(DPY_MODE_RATE);
  return dmy;
}

//-----------------------------------------------------------------------------

static uint32_t cmd_clock(uint8_t *params)
{
  unsigned dmy = 0;

  sscanf_P((char*)params, STR_PU, &dmy);
  display_set_mode(DPY_MODE_CLOCK);
  return dmy;
}

//-----------------------------------------------------------------------------

static uint32_t cmd_tick(uint8_t *params)
{
  unsigned dmy = 0;

  sscanf_P((char*)params, STR_PU, &dmy);
  display_set_mode(DPY_MODE_TICK);
  return dmy;
}

//-----------------------------------------------------------------------------
// Translate and update local time offset.
// Format: time HRS:MIN:SEC - example: time 4:20:00
//         time ms          - example: time 15600
//         time             - returns current time to console.

static uint32_t cmd_time(uint8_t *params)
{
  uint16_t sec;
  uint16_t min;
  uint16_t hrs;
  uint32_t clk;
  uint32_t rtn = timer1_get_tick();
  char _result[48];

  if(sscanf_P((char*)params, STR_PUC_PUC_PU, &hrs, &min, &sec) == 3)
  {
    // Format - HRS:MIN:SEC.
    clk = (((uint32_t)hrs * 60 * 60) + (min * 60) + sec) * 1000;
    display_set_clock(clk);
  }
  else if(sscanf_P((char*)params, STR_PLU, &clk) == 1)
  {
    // Format - ms (1/1000 sec).
    display_set_clock(clk);
  }
  else
  {
    clk = (rtn / 1000) % ((uint32_t)24 * 60 * 60);
    snprintf_P(_result, sizeof(_result), STR_CUR_TIME,
               (unsigned)(clk / (60 * 60)), (unsigned)((clk / 60) % 60),
               (unsigned)(clk % 60), (unsigned)(rtn % 1000), rtn);
    console_send(_result);
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static const struct _command cmdtable[] PROGMEM = {
  {CMD_ACCEL,    cmd_accel},
  {CMD_CLOCK,    cmd_clock},
  {CMD_RATE,     cmd_rate},
  {CMD_TICK,     cmd_tick},
  {CMD_TIME,     cmd_time}
};

static const uint16_t size_cmdtable PROGMEM = sizeof(cmdtable) / sizeof(struct _command);

//-----------------------------------------------------------------------------

uint8_t interpret(uint8_t *command)
{
  uint8_t rtn = 0;
  uint16_t i;
  char *tok;
  uint8_t *params;

  tok = strtok_r((char*)command, " ", (char**)&params);
  for(i = 0; i < size_cmdtable; i++)
  {
    // Neccessary because the compiler can not resolve indirect FLASH
    //  addresses at compile time.
    // Effectively : strcmp_P(tok, cmdtable[i].cmd).
    if(strcmp_P(tok, (const char*)pgm_read_word_near(cmdtable + i)) == 0)
    {
      // Effectively : (cmdtable[i].funct)(params).
      ((uint8_t (*)(uint8_t*))pgm_read_word_near(((uint16_t)(cmdtable + i)) + 2))(params);
      rtn = 1;
    }
  }
  return rtn;
}

//-----------------------------------------------------------------------------
// end: interpret.ino

The second task utilizes the MPU6050 Inertial/Motion sensor. Primarily it is based on the standard 'public' implementation only slightly modified to work with the multitasker. It includes an initialization method that must be called before its task is created. The console must also be initialized before initialization as console output (status) is required for initialization to complete.

//-----------------------------------------------------------------------------
// file: mpu6050.ino
//
//  I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class
//  using DMP (MotionApps v2.0) 6/21/2012 by Jeff Rowberg <jeff@rowberg.net>
//
//  Modified by No Fun Farms A.K.A. smegware.com
//
/*=============================================================================
  I2Cdev device library code is placed under the MIT license
  Copyright (c) 2012 Jeff Rowberg

  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.
  ===========================================================================*/

#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"

//-----------------------------------------------------------------------------
// MPU register definitions; Should be defined by the MPU interface.

#define MPU_INT_MOTION 0X40
#define MPU_INT_OVERFLOW 0X10
#define MPU_INT_MASTER 0X08
#define MPU_INT_DATA_RDY 0X01

#define MPU_FIFO_OVERFLOW 1024

//-----------------------------------------------------------------------------
// MPU control/status local vars.

static MPU6050   mpu(0X68);    // Sensor Object (interface).
volatile uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
static uint8_t   devStatus;    // return status after each device operation (!0 = error)
static uint8_t   dmpReady;     // set true if DMP init was successful
static uint16_t  packetSize;   // expected DMP packet size (default is 42 bytes)
static uint16_t  fifoCount;    // Count of 'all' bytes currently in FIFO.

//-----------------------------------------------------------------------------
// Orientation/motion global vars.

VectorInt16 aa;         // [x, y, z] accel sensor measurements
VectorInt16 ag;         // [x, y, z] gyro sensor measurements
int16_t qraw[4];        // Raw DMP Quaternion.

//-----------------------------------------------------------------------------
// Constant Character string definitions in PROGRAM (FLASH) space.

static const char MSG_INIT[]        PROGMEM = "Initializing MPU6050 I2C device...\n";
static const char MSG_SWITCH[]      PROGMEM = "Init - Ready : Switching to packet mode...\n";
static const char MSG_INITDMP[]     PROGMEM = "Initializing DMP...\n";
static const char MSG_SZFIFO[]      PROGMEM = "DMP FIFO size - %i.\n";
static const char MSG_ENABLDMP[]    PROGMEM = "Enabling DMP...\n";
static const char MSG_DMPWAIT[]     PROGMEM = "DMP ready! Waiting for first interrupt...\n";
static const char MSG_CONTEST[]     PROGMEM = "Testing MPU6050 device connection...\n";
static const char MSG_CONFAIL[]     PROGMEM = "MPU6050 connection failed.\n";
static const char MSG_CONPASS[]     PROGMEM = "MPU6050 connection successful.\n";
static const char MSG_CONWAIT[]     PROGMEM = "Waiting for ready signal to continue.\n";
static const char MSG_ENABLINT[]    PROGMEM = "Enabling MPU6050 interrupt detection...\n";
static const char MSG_INTWAIT[]     PROGMEM = "MPU6050 DMP ready! Waiting for first interrupt...\n";
static const char MSG_FIFOERR[]     PROGMEM = "MPU6050 FIFO overflow!\n";
static const char ERR_DMPLOAD[]     PROGMEM = "MPU650 DMP program load failed.\n";
static const char ERR_DMPCONFIG[]   PROGMEM = "MPU6050 DMP configuration failed.\n";
static const char ERR_DMPFAIL[]     PROGMEM = "MPU6050 DMP unknown initialization error.\n";

static const char QUAT_RAW_PACKET[] PROGMEM = "qraw %i %i %i %i \n";
static const char GYRO_RAW_PACKET[] PROGMEM = "graw %i %i %i \n";
static const char ACCL_RAW_PACKET[] PROGMEM = "araw %i %i %i \n";
static const char TEMP_RAW_PACKET[] PROGMEM = "temp %i \n";
static const char END_PACKET[]      PROGMEM = ".\n";

//-----------------------------------------------------------------------------
// 'Cheesey' Interrupt Service Routine.

volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high

void dmpDataReady()
{
  mpuInterrupt = true;
}

//-----------------------------------------------------------------------------
// Initialize MPU6050 sensor using the internal DMP interface.

void mpu_init(void)
{
  Wire.begin();
  TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
  dmpReady = false;
  // Initialize MPU6050 device.
  console_send_P(MSG_INIT);
  mpu.initialize();
  // Verify connection.
  console_send_P(MSG_CONTEST);
  if(mpu.testConnection())
  {
    console_send_P(MSG_CONPASS);
  }
  else
  {
    console_send_P(MSG_CONFAIL);
  }
  // Wait for console ready signal.
  console_send_P(MSG_CONWAIT);
  while(console_read() != 0X1B);
  // Load and configure the DMP.
  console_send_P(MSG_INITDMP);
  devStatus = mpu.dmpInitialize();
  // Make sure it worked (returns 0 if so).
  if(!devStatus)
  {
  // supply your own gyro offsets here, scaled for min sensitivity
  //  mpu.setXGyroOffset(220);
  //  mpu.setYGyroOffset(76);
  //  mpu.setZGyroOffset(-85);
  //  mpu.setZAccelOffset(898); // 1688 factory default for my test chip

    // Turn on the DMP, now that it's ready.
    console_send_P(MSG_ENABLDMP);
    mpu.setDMPEnabled(true);
    // Enable Arduino interrupt detection.
    console_send_P(MSG_ENABLINT);
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();
    // set global DMP Ready flag so the main loop() function knows it's okay to use it
    dmpReady = true;
    console_send_P(MSG_INTWAIT);
    // get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();
    console_send_P(MSG_SWITCH);
    fifoCount = mpu.getFIFOCount();
  }
  else
  {
    // ERROR!
    // 1 = Initial memory load failed.
    // 2 = DMP configuration updates failed.
    // If it's going to break, usually the code will be 1.
    switch(devStatus)
    {
      case 1:
        console_send_P(ERR_DMPLOAD);
        break;

      case 2:
        console_send_P(ERR_DMPCONFIG);
        break;

      default:
        console_send_P(ERR_DMPFAIL);
        break;
    }
  }
}

//-----------------------------------------------------------------------------
// Acquire sensor data and report to console (serial) interface.

void mpu_acquire(void)
{
  int16_t temperature;  // Sensor die temperature.
  char    _motion[64];  // General buffer.

  if(mpuInterrupt || (fifoCount >= packetSize))
  {
    // Reset interrupt flag.
    mpuInterrupt = false;
    // Get interrupt status; Volatile - some bits cleared when read.
    mpuIntStatus = mpu.getIntStatus();
    // Get Die temperature.
    temperature = mpu.getTemperature();
    // Get current FIFO count
    fifoCount = mpu.getFIFOCount();
    // check for overflow (this should never happen unless our code is too inefficient)
    if((mpuIntStatus & MPU_INT_OVERFLOW) || fifoCount >= MPU_FIFO_OVERFLOW)
    {
      // Hopefully; Reset target so we can continue cleanly.
      mpu.resetFIFO();
      console_send_P(MSG_FIFOERR);
    }
    // otherwise, check for DMP data ready interrupt (this should happen frequently)
    else if(mpuIntStatus & MPU_INT_DATA_RDY)
    {
      // wait for correct available data length, should be a VERY short wait
      while(fifoCount < packetSize)
      {
        fifoCount = mpu.getFIFOCount();
      }
      // track FIFO count here in case there is > 1 packet available
      // (this lets us immediately read more without waiting for an interrupt)
      fifoCount -= packetSize;
      // Acquire a sensor packet from the DMP FIFO.
      mpu.getFIFOBytes((uint8_t*)_motion, packetSize);
      mpu.dmpGetAccel(&aa, (uint8_t*)_motion);
      mpu.dmpGetGyro(&ag, (uint8_t*)_motion);
      mpu.dmpGetQuaternion(qraw, (uint8_t*)_motion);
      // Report raw Acceleration values.
      sprintf_P(_motion, ACCL_RAW_PACKET, aa.x, aa.y, aa.z);
      console_send(_motion);
      // Report raw Gyro rates.
      sprintf_P(_motion, GYRO_RAW_PACKET, ag.x, ag.y, ag.z);
      console_send(_motion);
      // Report raw Quaternian orientation.
      sprintf_P(_motion, QUAT_RAW_PACKET, qraw[0], qraw[1], qraw[2], qraw[3]);
      console_send(_motion);
      // Report raw sensor DIE Temperature.
      sprintf_P(_motion, TEMP_RAW_PACKET, temperature);
      console_send(_motion);
      // Signal end of packet.
      console_send_P(END_PACKET);
    }
  }
}

//-----------------------------------------------------------------------------

void mpu6050(void)
{
  while(1)
  {
    if(dmpReady)
    {
      mpu_acquire();
    }
    pause();
  }
}

//-----------------------------------------------------------------------------

int16_t mpu_get_accel_x(void)
{
  return(aa.x);
}

//-----------------------------------------------------------------------------

int16_t mpu_get_accel_y(void)
{
  return(aa.y);
}

//-----------------------------------------------------------------------------

int16_t mpu_get_accel_z(void)
{
  return(aa.z);
}

//-----------------------------------------------------------------------------

int16_t mpu_get_rate_x(void)
{
  return(ag.x);
}

//-----------------------------------------------------------------------------

int16_t mpu_get_rate_y(void)
{
  return(ag.y);
}

//-----------------------------------------------------------------------------

int16_t mpu_get_rate_z(void)
{
  return(ag.z);
}

//-----------------------------------------------------------------------------
// end: mpu6050.ino

The final task for this demonstration uses an eight-digit seven-segment LED display module to provide local feedback for the configuration. The display is capable of displaying a clock or accelerator data or rate data. Control of the display mode is selected by the console interpreter task.

//-----------------------------------------------------------------------------
// file: display.ino
//
// Smegduino LED display demonstration.
//
//  A simple interface for a multi-digit 7-segment multiplexed LED display.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//          _____
// where:  |  A  |
//        F|     |B
//         |_____|
//         |  G  |
//        E|     |C
//         |_____|
//            D    (DP)
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <multiled.h>

//-----------------------------------------------------------------------------
// Define string constants in program memory (PROM).

static const char STR_PU_PU[] PROGMEM = "%u %u";

//-----------------------------------------------------------------------------
// Define LED segment placememnts for this implememtation.

#define SEGMENT_NONE 0X00
#define SEGMENT_A 0X20
#define SEGMENT_B 0X02
#define SEGMENT_C 0X80
#define SEGMENT_D 0X08
#define SEGMENT_E 0X10
#define SEGMENT_F 0X01
#define SEGMENT_G 0X04
#define SEGMENT_DP 0X40

#define NUMBER_ZERO (SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F)
#define NUMBER_ONE (SEGMENT_B | SEGMENT_C)
#define NUMBER_TWO (SEGMENT_A | SEGMENT_B | SEGMENT_D | SEGMENT_E | SEGMENT_G)
#define NUMBER_THREE (SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_G)
#define NUMBER_FOUR ( SEGMENT_B | SEGMENT_C | SEGMENT_F | SEGMENT_G)
#define NUMBER_FIVE ( SEGMENT_A | SEGMENT_C | SEGMENT_D | SEGMENT_F | SEGMENT_G)
#define NUMBER_SIX ( SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F | SEGMENT_G)
#define NUMBER_SEVEN ( SEGMENT_A | SEGMENT_B | SEGMENT_C)
#define NUMBER_EIGHT ( SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F | SEGMENT_G)
#define NUMBER_NINE ( SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_F | SEGMENT_G)
#define NUMBER_MINUS (SEGMENT_G)

//-----------------------------------------------------------------------------

static uint8_t const segments[10] PROGMEM = {
  NUMBER_ZERO,
  NUMBER_ONE,
  NUMBER_TWO,
  NUMBER_THREE,
  NUMBER_FOUR,
  NUMBER_FIVE,
  NUMBER_SIX,
  NUMBER_SEVEN,
  NUMBER_EIGHT,
  NUMBER_NINE
};

#define DPY_MODE_DEFAULT 0
#define DPY_MODE_CLOCK 1
#define DPY_MODE_ACCEL 2
#define DPY_MODE_TICK 3
#define DPY_MODE_RATE 4

static uint8_t _mode = DPY_MODE_DEFAULT;
static uint32_t _clock = 0;

//-----------------------------------------------------------------------------

void display_init(void)
{
  uint8_t i;

  led_init_multiseg();
  timer1_clear_compare();
  timer1_set_compare_a(15990); // ~1ms = .001/sec (this mega2560).
  for(i = 0; i < 8; i++)
  {
    led_set_digit(i, SEGMENT_NONE);
  }
  timer1_enable_ocie1a();
}

//-----------------------------------------------------------------------------

void display_set_clock(uint32_t clk)
{
  timer1_set_tick(clk);
  _clock = 0;
}

//-----------------------------------------------------------------------------

void display_set_mode(uint8_t mode)
{
  _mode = mode;
}

//-----------------------------------------------------------------------------

void display_set_off(void)
{
  _mode = DPY_MODE_DEFAULT;
}

//-----------------------------------------------------------------------------

void display_update_tick(void)
{
  uint8_t i;
  uint32_t tick = timer1_get_tick() / 10;

  for(i = 0; i < 8; i++)
  {
    led_set_digit(i, pgm_read_byte(&segments[tick % 10]));
    tick /= 10;
  }
}

//-----------------------------------------------------------------------------

#define ACCEL_1G 8192
#define ACCEL_GAIN 100

void display_update_accel(void)
{
  int16_t n;

  n = (((float)mpu_get_accel_x() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    n = 0 - n;
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(6, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(7, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_accel_y() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    led_set_digit(5, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(5, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(3, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(4, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_accel_z() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    led_set_digit(2, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(2, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(0, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(1, pgm_read_byte_near(&segments[n / 10]));
}

//-----------------------------------------------------------------------------

//#define RATE_SCALE 12976
#define RATE_1DEG 131
#define RATE_GAIN 10

void display_update_rate(void)
{
  int16_t n;

  n = (((float)mpu_get_rate_x() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    n = 0 - n;
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(6, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(7, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_rate_y() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    led_set_digit(5, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(5, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(3, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(4, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_rate_z() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    led_set_digit(2, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(2, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(0, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(1, pgm_read_byte_near(&segments[n / 10]));
}

//-----------------------------------------------------------------------------

void display_update_clock(void)
{
  uint16_t t;
  uint32_t tick = timer1_get_tick() / 100; // tenths of seconds.

  if(tick > _clock)
  {
    _clock = tick;
    led_set_digit(0, pgm_read_byte_near(&segments[tick % 10]));
    tick /= 10;
    t = tick % 60;
    led_set_digit(1, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(2, pgm_read_byte_near(&segments[t / 10]));
    tick /= 60;
    t = tick % 60;
    led_set_digit(3, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(4, pgm_read_byte_near(&segments[t / 10]));
    tick /= 60;
    t = tick % 24;
    led_set_digit(5, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(6, pgm_read_byte_near(&segments[t / 10]));
    tick /= 24;
    led_set_digit(7, pgm_read_byte_near(&segments[tick % 10]) | SEGMENT_DP);
  }
}

//-----------------------------------------------------------------------------

void display_update(void)
{
  switch(_mode)
  {
    case DPY_MODE_CLOCK:
      display_update_clock();
      break;

    case DPY_MODE_ACCEL:
      display_update_accel();
      break;

    case DPY_MODE_TICK:
      display_update_tick();
      break;

    case DPY_MODE_RATE:
      display_update_rate();
      break;

    default:
      break;
  }
}

//-----------------------------------------------------------------------------

void display(void)
{
  while(1)
  {
    display_update();
    pause();
  }
}

//-----------------------------------------------------------------------------

void display_set_literal(void)
{
  led_set_digit(0, NUMBER_ZERO);
}

//-----------------------------------------------------------------------------
//end: display.ino

There is also an additional Assembly language driver and interface including an Interrupt Service Routine (ISR) that does the actual display 'digit' multiplexing at a very high rate avoiding the typical flicker noticeable with displays synced to the line frequency (60Hz).

;-----------------------------------------------------------------------------
; file: multiled.S
;
; Smegduino Multi-Digit 7-Segment LED Display multiplexing example.
;
;  A simple hardware manipulation demonstration including a reasonably
;   efficient Interrupt Service Routine to manage the hardware in the
;   background with little impact on foreground linear processing. There is
;   also limited configuration support for Timer-1(A) used to generate the
;   actual strobe (multiplex) interrupt.
;
; Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
;
;  All Smegware software is free; you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation; either version 2 of the License, or
;  (at your option) any later version.
;
;  This software is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;  GNU General Public License for more details.
;
; In General - This code multiplexes a seven-segment multi-digit numeric
;  LED display. It can support common-anode as well as common-cathode
;  displays by updating the strobe array. It is not only a very efficient
;  method but necessary on the Arduino since the current draw for all the
;  LEDs (8 x 8) would exceed the maximum power (current) capabilities.
; Note: This code is written exclusively for the mega256 configuration and
;  its specific port definition but is easily reconfigured.
;
; Circuit:
;  The digit strobe is on PORTA where PA7 = digit-7 ... PA0 = digit-0 an is
;   by default set for Active-Low (common-cathode).
;
;  The segments are connected to PORTC and are Active-High (driven anode).
;  Where: PC7 = segment-C
;         PC6 = segment-DP (decimal point)
;         PC5 = segment-A
;         PC4 = segment-E
;         PC3 = segment-D
;         PC2 = segment-G
;         PC1 = segment-B
;         PC0 = segment-F
;         _____
;  And:  |  A  |
;       F|     |B
;        |_____|
;        |  G  |
;       E|     |C
;        |_____|
;           D   -(DP)
;
; Additionally: Each anode segment is limited through a 680-ohm resistor.
;-----------------------------------------------------------------------------
;
; History...
;
;   $Source$
;   $Author$
; $Revision$
;
; $Log$
;
;------------------------------------------------------------------------------
#define __SFR_OFFSET 0
#include <avr/io.h>
;-----------------------------------------------------------------------------
; Convenience macros.
#define RTEMP R0
#define RZERO R1
#define RL R18
#define RH R19
#define RPARAM2L R22
#define RPARAM2H R23
#define RPARAM1L R24
#define RPARAM1H R25
#define RPARAM1 RPARAM1L
#define RPARAM2 RPARAM2L

#define COUNTL RPARAM1L
#define COUNTH RPARAM1H
#define COUNT  COUNTLxx
;-----------------------------------------------------------------------------
; TCCRnA register bit definitions.
.equ BIT_WGM0,   0B00000001
.equ BIT_WGM1,   0B00000010
.equ BIT_COMC0,  0B00000100
.equ BIT_COMC1,  0B00001000
.equ BIT_COMB0,  0B00010000
.equ BIT_COMB1,  0B00100000
.equ BIT_COMA0,  0B01000000
.equ BIT_COMA1,  0B10000000
; TCCRnB register bit definitions.
.equ BIT_CS0,    0B00000001
.equ BIT_CS1,    0B00000010
.equ BIT_CS2,    0B00000100
.equ BIT_WGM2,   0B00001000
.equ BIT_WGM3,   0B00010000
.equ BIT_ICES,   0B01000000
.equ BIT_ICNC,   0B10000000
; TCCRnC register bit definitions.
.equ BIT_FOCC,   0B00100000
.equ BIT_FOCB,   0B01000000
.equ BIT_FOCA,   0B10000000
; TIMSKn register bit definitions.
.equ BIT_TOIE,   0B00000001
.equ BIT_OCIEA,  0B00000010
.equ BIT_OCIEB,  0B00000100
.equ BIT_OCIEC,  0B00001000
.equ BIT_ICIE,   0B00100000
; TIFRn register bit definitions.
.equ BIT_TOV,    0B00000001
.equ BIT_OCFA,   0B00000010
.equ BIT_OCFB,   0B00000100
.equ BIT_OCFC,   0B00001000
.equ BIT_ICF,    0B00100000
; Timer1 register definitions.
.equ REG_TIFR1,  0X16
.equ REG_TIMSK1, 0X6F
.equ REG_TCCR1A, 0X80
.equ REG_TCCR1B, 0X81
.equ REG_TCCR1C, 0X82
.equ REG_TCNT1L, 0X84
.equ REG_TCNT1H, 0X85
.equ REG_ICR1L,  0X86
.equ REG_ICR1H,  0x87
.equ REG_OCR1AL, 0X88
.equ REG_OCR1AH, 0X89
;-----------------------------------------------------------------------------
; LED digit sync active low.
.equ REG_PORTA, 0X02
.equ REG_DDRA,  0X01
.equ REG_PINA,  0X00
; LED segment source active high.
.equ REG_PORTC, 0X08
.equ REG_DDRC,  0X07
.equ REG_PINC,  0X06
;-----------------------------------------------------------------------------
; Interrupt tick (count) register - 32-bits.
.lcomm timer1_count,4               ; Timer1 compare A interrupt clock tick.

; LED digit counter.
.lcomm digcnt,1                     ; Strobe digit vector.

; LED segment array.
.lcomm led_segments,8               ; Digit segment data.

; LED digit strobe array in PROM - Active-low (common-cathode).
.text
strobe: .byte 0X7F,0XBF,0XDF,0XEF,0XF7,0XFB,0XFD,0XFE

;-----------------------------------------------------------------------------
; LED interface port initialization.
.global led_init_multiseg
led_init_multiseg:
    LDI     RL,0X00                 ; Initial - No Segments - Active-high.
    OUT     REG_PORTC,RL            ; Set Segments.
    LDI     RL,0XFF                 ; Initial - No Digits - Active-low.
    OUT     REG_PORTA,RL            ; Set Digits.
    OUT     REG_DDRA,RL             ; Output port.
    OUT     REG_DDRC,RL             ; Output port.
    RET
;-----------------------------------------------------------------------------
; LED interface port initialization.
.global led_set_digit
led_set_digit:
    LDI     ZL,lo8(led_segments)    ; Segment variable data
    LDI     ZH,hi8(led_segments)    ;  address.
    ANDI    RPARAM1L,7              ; Limit range to protect local data.
    ADD     ZL,RPARAM1L             ; Point to next digit segments
    ADC     ZH,RZERO                ;  address.
    ST      Z,RPARAM2L              ; Segment data.
    RET
;-----------------------------------------------------------------------------
; Set timer 1 to CTC mode 2.
.global timer1_clear_compare
timer1_clear_compare:
    LDI     RL,0                    ; Mode 4 - CTC - No pin output. 
    STS     REG_TCCR1A,RL
    LDI     RL,(BIT_CS0 | BIT_WGM2) ; Mode 4 - CTC - No prescaler.
    STS     REG_TCCR1B,RL 
    RET
;-----------------------------------------------------------------------------
; Set timer 1 counter register.
.global timer1_set_count
timer1_set_count:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 16-bit register coherency.
    STS     REG_TCNT1H,COUNTH       ; Update timer counter register
    STS     REG_TCNT1L,COUNTL       ;  with 16 bit count.
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Set timer 1 compare A register.
.global timer1_set_compare_a
timer1_set_compare_a:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 16-bit register coherency.
    STS     REG_OCR1AH,COUNTH       ; Update compare capture register
    STS     REG_OCR1AL,COUNTL       ;  with 16 bit count.
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Enable timer 1 compare A interrupt enable.
.global timer1_enable_ocie1a
timer1_enable_ocie1a:
    LDS     RL,REG_TIMSK1           ; Current contents.
    SBR     RL,BIT_OCIEA            ; Set output compare A compare bit.
    STS     REG_TIMSK1,RL           ; Enable interrupt.
    RET
;-----------------------------------------------------------------------------
; Timer 1 get tick count (32-bit).
.global timer1_get_tick
timer1_get_tick:
    LDS     RPARAM2L,timer1_count   ;
    LDS     RPARAM2H,timer1_count+1 ;
    LDS     RPARAM1L,timer1_count+2 ;
    LDS     RPARAM1H,timer1_count+3 ;
    RET
;-----------------------------------------------------------------------------
; Timer 1 set tick count (32-bit) Needed when using as a clock.
.global timer1_set_tick
timer1_set_tick:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 32-bit register coherency.
    STS     timer1_count+0,RPARAM2L ;
    STS     timer1_count+1,RPARAM2H ;
    STS     timer1_count+2,RPARAM1L ;
    STS     timer1_count+3,RPARAM1H ;
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Timer 1 Output-Compare-A interrupt service routine.
.global TIMER1_COMPA_vect
TIMER1_COMPA_vect:
    PUSH    ZL                      ; Preserve linear state.
    IN      ZL,SREG                 ;
    PUSH    ZL                      ; Preserve Status register.
    PUSH    ZH                      ; Preserve linear state.
    PUSH    RL                      ;
    PUSH    RZERO                   ;
; Update Tick counter.
    CLR     RZERO                   ; Force.
    LDS     ZL,timer1_count         ; Tick count low-byte.
    LDS     ZH,timer1_count+1       ; Tick count high-byte.
    ADIW    ZL,1                    ; Increment timer Tick counter
    STS     timer1_count,ZL         ; Update Tick count.
    STS     timer1_count+1,ZH       ;
    LDS     RL,timer1_count+2       ; Tick count higher-byte.
    ADC     RL,RZERO                ;  32-bit operation.
    STS     timer1_count+2,RL       ;
    LDS     RL,timer1_count+3       ; Tick count highest-byte.
    ADC     RL,RZERO                ;  32-bit operation.
    STS     timer1_count+3,RL       ;
; Strobe next digit.
    LDI     ZL,lo8(strobe)          ; Digit strobe constants
    LDI     ZH,hi8(strobe)          ;  address.
    LDS     RL,digcnt               ; Current digit.
    ADD     ZL,RL                   ; Point to current digit
    ADC     ZH,RZERO                ;  vector.
    OUT     REG_PORTC,RZERO         ; Void current digit segments.
    LPM     RZERO,Z                 ; Next digit strobe.
    OUT     REG_PORTA,RZERO         ; Update digit strobe.
; Set digit segments.
    CLR     RZERO                   ; Force.
    LDI     ZL,lo8(led_segments)    ; Segment variable data
    LDI     ZH,hi8(led_segments)    ;  address.
    ADD     ZL,RL                   ; Point to current digit.
    ADC     ZH,RZERO                ; Next digit segments.
    LD      RZERO,Z                 ; Next digit.
    OUT     REG_PORTC,RZERO         ; Update digit segments.
; Calculate digit for next strobe.
    INC     RL                      ; Point to next digit.
    CPI     RL,8                    ; Check overflow.
    BRCS    .no_roll                ; Still valid.
    CLR     RL                      ; Roll over (clip at 8).
.no_roll:
    STS     digcnt,RL               ; Cache next digit.
; Restore and return.
    POP     RZERO                   ; Restore pre-interrupt state.
    POP     RL                      ;
    POP     ZH                      ;
    POP     ZL                      ;
    OUT     SREG,ZL                 ; Restore status Register.
    POP     ZL                      ;
    RETI                            ; Re-Enter linear processing state.
;-----------------------------------------------------------------------------
;end: multiled.S

Putting It All Together As A System...

The first step is to initialize the interfaces which will in turn configure any associated hardware. Because the MPU6050 sensor task requires console communication to complete it is necessary to initialize the console interface first. The remaining initialization order is unimportant. Next the actual tasks are created. It is important to note here that NO method of any task created can be executed from this point on. When all tasks are created the final step is to jump into the mutitasker context. This operation has no return path. At least one task must be running at all times to prevent this.

//-----------------------------------------------------------------------------
// file: smegsense.ino
//
// Smegduino MPU6050 motion sensor demonstration.
//
//  A simple method for handling of several operations (processes)
//   simultaneously using a cooperative (non-preemptive multitasker.
//
//  In this case an MPU6050 motion sensor relays data to a remote host over
//   a custom serial interface, while, updating a multi-digit multiplexed LED
//   display using a command line interpreter to set the display mode.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <smegtasker.h>

//-----------------------------------------------------------------------------

static const char STR_TASK_CONSOLE[] PROGMEM = "console";
static const char STR_TASK_DISPLAY[] PROGMEM = "display";
static const char STR_TASK_MPU[] PROGMEM = "mpu";

//-----------------------------------------------------------------------------
// Arduino setup vector.

void setup(void)
{
  // Initialize the hardware.
  console_open();
  mpu_init();
  display_init();
  // Create the tasks.
  create_task(40, display, STR_TASK_DISPLAY);
  create_task(200, console, STR_TASK_CONSOLE);
  create_task(200, mpu6050, STR_TASK_MPU);
}

//-----------------------------------------------------------------------------
// Arduino 'main' entry vector.

void loop(void)
{
  // Jump into multitasker abandoning the current processing context including
  //  its stack.
  idle_task();
}

//-----------------------------------------------------------------------------
// end: smegsense.ino

In Conclusion...

Demonstrated is a very feasible method for multitasking on an AVR class processor. It is efficient however it requires special consideration when designing the tasks. It also has NO realtime capabilities.

11001