a data-centric event-oriented rtos for mcus
DESCRIPTION
A data-centric event-oriented RTOS for MCUs. Simplify multithreaded programming And Boost Performance. by Dirk Braun. The Concept of the Data-Centric RTOS 3 Ideas combined SW-Interrupts switch tasks – performance up, overhead down - PowerPoint PPT PresentationTRANSCRIPT
A data-centric event-oriented RTOS for MCUs
Simplify multithreaded programming And Boost Performance
by Dirk Braun
Contents
2. The DCEO-RTOS in practise• Example Application• Define Task-Groups, Mini-Tasks & Data Objects• Performance Measurements
1. The Concept of the Data-Centric RTOS3 Ideas combined• SW-Interrupts switch tasks – performance up, overhead down• Publisher-Subscriber Mechanism distributes data – easy & safe
data use in multithreaded environment• Design Task-Group Priorities – avoid need for synchronization
3. Summary & Outlook
Goal 1: Fast Real Time Reaction
• Real-Time Reaction – objective: fast & deterministic
• Cause of a Reaction is an Event
• Event– change of state of connected HW– Time has elapsed– New: Data item has changed
Event
Reaction• Reaction– SW reaction– Ultimately – HW reaction
Reaction & Task Switches• DCEO – RTOS directly uses the processors built in
way to React: the Interrupt– Processor status saved on interrupt entry automatically– Interrupt controllers priority management used– Mini-tasks (reactions) called directly from designated
ISRs. They are implemented by the application programmer and execute at interrupt priority.
– A „task switch“ involves backing up the current task. (copy processor state vars once)
– Only one stack - no stack management during task switch
• Benefits / costs– No. of save/restore cycles for processor status reduced– Task priority management done by HW (instead of software)– Number of separate priorities is limited by interrupt-hardware
Goal 2: Reduce Programming Overhead – use SW Interrupts
Common Event-Reaction Implementation
1 main()2 {3 CreateTask(MyTask);4 ...5 while (TRUE);6 }78 MyTask()9 { // implements reaction10 while (TRUE)11 {12 WaitForEvent(&myEvent);13 // react to myEvent here14 // todo: sync access to g_input15 reaction = g_input * xxx16 };17 }1819 MyISR() __irq20 { // do some HW stuff to 21 // retrieve input22 // todo: sync access to g_input23 g_input = HW;24 SetEvent(&myEvent);25 }
Event-Reaction Implementation using SW-Interrupts
1 main()2 {3 // setup ISRs4 while (TRUE);5 }67 MyTask() __irq8 { // implements reaction9 // react to myEvent here10 reaction = g_input * xxx11 }1213 MyISR() __irq14 { // do some HW stuff to 15 // retrieve input16 g_input = HW;17 myTaskTrigger = 1;18 }
• No infinite loop• No “active” waiting• No synchronization
Goal 3: Publisher-Subscriber Mechanism puts Focus on Data
• Data triggers itself through the application
• The changing of data itself is and replaces the event
ADC-ISR
Generic Scaler
Realize Output
Bus Network
DeviceReaction
raw AD value
scaled AD value
output
Display
Scaling range
DCEO-RTOS
Data-Publisher
safe data copy passed into callbacks
change data object
Subscriber 1 (callback function)at low priority
Subscriber n (callback function)at priority m
Publisher Subscriber engine
Goal 4: Module Independency Increased by Data Centric View …
ADC-ISR
raw AD value
Check value
ADC Test App
ADC-ISR
Data Logger writes to file
raw AD value
scaled AD value
Generic Scaler
Scaling range
Data Logger Random Generator
Check scaling
raw value
scaled value
Generic Scaler
Scaling range
Scaler Test App
Ref. scaled value
Reuse Modules
… by using simpler interfaces …
// adc.h
// ADC value, a WORD
extern DWORD obIdAdcVal;
void AdcInit();
void AdcConvStart();
Simple Interface
// adc.h
// ADC value, a WORD
WORD g_adcVal;
EVENT g_adcValAvailEvent;
MUTEX g_adcValAccMutex;
void AdcInit();
void AdcConvStart();
Interface using Data-Object
Interface using global data & event
Module independency easier to maintain
… by avoiding global variables
// myCode.c
void UseVarValueFunction() {
MyType myVar;
myVar = GetVar();
}
void WriteVarValueFunction() {
MyType myVar;
...
SetVar(myVar);
}
„Avoid global variables in interfaces, i.e. header files. Provide access functions instead.“ (Rule for modular programming)
Separate get/set access functions for each variable
// myCode.c
void UseVarValueFunction() {
MyType myVar;
GetDataObject(&myVar, obIdMyVar);
}
void WriteVarValueFunction() {
MyType myVar;
...
SetDataObject(&myVar, obIdMyVar);
}
void OnMyVarChanged(BYTE* pData, …) {
MyType* pNewMyVar = pData;
...
}
Universal access function for all data-objects + notification
Instead of providing a separate set of access functions just publish a Data-Objects ID (a handle).
Mutual Dependency -> non reusable code
One module cannot compile without the other
Module MyCode1
// myCode1.c
#include “myCode2.h”
WORD myVar1;
...
// myCode1.h
#include “myCode2.h”
Extern WORD myVar1;
...
Module MyCode2
// myCode2.c
WORD myVar2;
...
// myCode2.h
#include “myCode2.h”
Extern WORD myVar2;
...
modules cannot be reused without each other cannot be used in a layered model
#include “myCode2.h”
Synchronization• Purpose
– Safety measure to avoid inconsistent data and possible crashes when two process threads interrupt each other at the wrong moment
• Unneccessary most often– This simultaneous occurence is unlikely and rare
• Costly– Entering and leaving synchronization objects involve function
calls and consume processor performance.– Causes expensive task-switches when really needed
• Multithreaded SW-development more difficult– Often difficult to identify the resources that require
synchronization
low prio
high prio
Critical Section 1
Task 1 enters Mutex A Section 1
OS calls higher prio task 2
Process flow
Task 1
Task 2
Task 2 tries to enter Mutex A. This causes a priority inversion and Task 1 is resumed at HIGH priority.
inactive Task running Task suspended Task
Task 1 leaves Mutex A and is suspended to low priority again. Task 2 is resumed.
Task 2 leaves Mutex A
Task 2 finished
Task 1 finished
spare processor time ;-)
1
2
3 4
5
Mutexes rarely cause task switches
void Task1() {
// do something
EnterMutex(g_mutexA);
// use shared resource
.
.
.
.
LeaveMutex(g_mutexA);
.
.
// do something
}
void Task2() {
// do something
EnterMutex(g_mutexA);
// use shared resource
LeaveMutex(g_mutexA);
}
suspended
suspended
1
2
3
4
5
suspended
Supersede the need for Synchronization
• Idea– Tasks with same priority cannot interrupt each other -> so
they cannot execute concurrently– Other tasks at higher priority still pre-empt lower priority tasks
• Realization:– Timed-Mini-Tasks and Event-Mini-Tasks (Subscribers)
grouped into Task-Groups. – A Task-Group has a single priority for all of its mini-tasks.
Tasks at same priority
Three tasks „Generic Scaler“ „Device Reaction“ and „Realize Output“ are subscriber mini-tasks of one task-group at a single priority.
ADC-ISR Generic
Scaler
Realize Output
Bus Network
DeviceReaction
raw AD value
scaled AD value
output
Display
Scaling range
LowPrio
MedPrio
HighPrioHWIRQ
ADC-ISR
Generic Scaler
Realize Output
Bus Network
DeviceReaction
Display
ADC-ISR
Generic Scaler
Realize Output
Bus Network
DeviceReaction
Displaycontinued
Display pre-empted
Time
Low Prio
Software - Interrupt
Publisher – Subscriber•Manages Data-Objects and subcriber mini-tasks•Subscribers invoked when data-of-interest changed
Time – Engine
•Timed mini-task invoked when time has come
•Cyclic and one-time (time out) timed tasks supported
Med Prio
Software - Interrupt
Publisher – Subscriber•Manages Data-Objects and subcriber mini-tasks•Subscribers invoked when data-of-interest changed
Time – Engine
•Timed mini-task invoked when time has come
•Cyclic and one-time (time out) timed tasks supported
Timed & Subscriber Mini-Tasks
• Multiple Timer Reactions and multiple Data Event Reactions can share the same priority.
• All mini-tasks are called from Interrupt Service Routines, run at predefined priorities and use the processors preemptive interrupt logic.
High Prio
Software - Interrupt
Publisher – Subscriber•Manages Data-Objects and subcriber mini-tasks•Subscribers invoked when data-of-interest changed
Time – Engine
•Timed mini-task invoked when time has come
•Cyclic and one-time (time out) timed tasks supported
End of Theory
Example Application
Objective: Create an analog-to-frequency converter with user-interface. This involves
• doing cyclic AD-conversions with an adjustable cycle period to be set via user-interface (RS232 command)
• setting the output frequency depending on the current analogue input
• reporting AD-values via RS232 (which represents the user interface)
• implementing a simple clock to report time to RS232
Todo’s:• Design modules, their relations, interfaces, data-objects• Plan Tasks – timed and subscriber mini-tasks• Identify Synchronization Requirements – Group mini-tasks into
task-groups
Modules
ADC
ADC• Function for triggering conversions
• Provide Data-Object for conversion result
// adc.h
// ADC value, a WORD
extern DWORD obIdAdcVal;
void AdcInit();
void AdcConvStart();
Description Interface Module
AscString
AscString• Send complete strings via RS232
using function call
• Provide Data-Object for new received string
• Provide Data-Object for event when string-sending has completed
// ascString.h
extern DWORD obIdRecvString;
extern DWORD obIdSentString;
void AscStrInit(DWORD gapTime, char endChar);
BOOL AscStrSend(BYTE* pSendString, WORD len);
MainMain application• React on new AD values
• Set Output Reaction
• React to UI
• Time Output
Empty
More Modules
ASC
ASC• Byte-wise RS232 communication
// adc.h
void AscInit(DWORD baudrate);
void AscSendByte(BYTE sendByte);
// register callbacks
BOOL AscSetRxCallBack(
AscRXCbPtr pRxCallback);
BOOL AscSetTxCallBack(
AscTXCbPtr pTxCallback);
Description Interface Module
CmdInterpreter
CmdInterpreter (helper module)• Provides functions to interpret
commands in a string
// CmdInterpreter.h
typedef enum CmdCodeEnum { CmdHelp, // general CmdPeriod, CmdCodeInvalid, CmdNull } CmdCode;
CmdCode CiGetCommand(char* cmdString, int len);
FrequencyOutput
FrequencyOutput• HW-Output for frequency
• Provide Data-Object for frequency
// freqOut.h
// frequency, a WORD
extern DWORD obIdFrequency;
void FreqOutInit();
Module Relations & Data Objects
Data Objects:
HW-Abstraction Layer
HW-Logic Layer
Application Layer
ASC
CmdInterpreter
FrequencyOut
ADC
AscString
Main
Function call Call Callback Data Notification
DCEO -RTOS
Timer0
• AD value • passing from ADC to scaling (Main)• passing from ADC to RS232
• AscStrRX• received command via RS232
• Frequency Out• passing from scaling (Main) to Frequency Out
Task Groups & Priorities
• Task-group-priorities ARE interrupt-priorities, so task-group-priorities & interrupt-priorities belong into the same priority list
Cyclic Mini-Task Triggering
Conversions
ADC ISRSet Data Object
ReactionRealizing Output
Write to UI
ASC ISR
Cyclic Mini-Task incrementing
& writing clock
ASCBuffer &Control
Sync required
• Data-objects are safely shared across priority-boundaries
• Priority plan identifies synchronization requirements
High Priority Task Group
Medium Priority Task Group
Low PriorityTask Group
ADC InterruptPriority
ASC (UART)Interrupt Priority
Setup - codeDefining Priorities
int main (void) {
...
Task_Init(); // init DCEO-RTOS
AscInit(115200); // init ASC
AscStrInit(0, '\r'); // init AscString
AdcInit(); // init ADC
// enter callbacks for task-group prioss
DataAddNotifyCB(OnAdValChangedHigh,
obIdAdcVal, 0);
TaskT_CallBackCyclicAt(30,
OnCyclicAdcConvTimer, 1, 1);
DataAddNotifyCB(OnRxStrRecv,
obIdRecvString, 2);
DataAddNotifyCB(OnAdValChangedLow,
obIdAdcVal, 2);
INTERRUPT_ENABLE()
while (TRUE);
}
Setting up Mini-Tasks// priorities.h
// define your Task Groups Priorities here
const TaskGroup g_taskGroups[] = {
// ilvl vicChannel
{ 7, 25}, // high (0)
{ 11, 26}, // medium (1)
{ 14, 28} // low (2)
};
// define your own interrupts here
ILVL VIC channel
#define ADC_ILVL 8
#define ADC_CHANNEL 18
#define ASC_ILVL 12
#define ASC_CHANNEL 7
#define TASK_T_ILVL 4
#define TASK_T_CHANNEL 5
Interrupt levels & task priorities shown in red Mini-Task creation shown in red
Implementing Module ADCInterface // implementation continued
void AdcConversionStart()
{ // trigger conversion
AD0CR |= 0x01200000;
}
void adcIsr (void) __irq
{ // ISR, pick up conversion result & set
// data object
WORD adVal;
// ARM7 specific, re-enable interrupts, so higher priority IRQ get through again
IENABLE
// Read A/D Data Register
adVal = (AD0DR & 0xffc0) >> 6;
...
if (adVal != g_lastAdVal)
{
DataSetObject((BYTE*)&adVal,
sizeof(adVal), obIdAdcVal);
g_lastAdVal = adVal;
}
IDISABLE
// Acknowledge Interrupt
VICVectAddr = 0;
}
Implementation
// adc.h
// ad converted value, a WORD, OUT
extern DWORD obIdAdcVal;
void AdcInit();
void AdcConversionStart();
// adc.c
DWORD obIdAdcVal;
void adcIsr (void) __irq ;
WORD g_lastAdVal;
void AdcInit()
{ // set up HW
...
IsrCreate((unsigned long)adcIsr, ADC_ILVL, ADC_CHANNEL);
obIdAdcVal =
DataCreateObject(sizeof(g_lastAdVal));
g_lastAdVal = 0;
DataSetObject((BYTE*)&g_lastAdVal, sizeof(g_lastAdVal), obIdAdcVal);
}
Implementing Module ascStringInterface void ascStr_OnRxRecv(BYTE recvByte)
{ // byte received callback from lower layer
... Add byte to buffer
// set timeout if not set yet
if (ascOnTimeoutId != (DWORD)NULL)
// modify timeout time to NOW +
allowed gaptime
TaskT_ChangeCallbackTime(TaskT_Now()
+ g_ascStrMaxRecvInterByteGapTime,
ascOnTimeoutId, 1);
else
{ // first byte of new string coming in
// set timeout to now + max. gaptime
ascOnTimeoutId = TaskT_CallBackAt(
TaskT_Now() +
g_ascStrMaxRecvInterByteGapTime,
ascStr_OnRxComplete, 1, 1);
}
...
}
// timeout mini-task
void ascStr_OnRxComplete() //
{ // set data object with received string
DataSetObject((BYTE*)&g_ascStrRecvStr,
sizeof(g_ascStrRecvStr), obIdRecvString);
...
}
Implementation
// ascString.h - excerpt of
// Reception of a string is complete when the
// gapTime between two bytes has expired
extern DWORD obIdRecvString;
...
void AscStrInit(DWORD gapTime, char endChar);
BOOL AscStrSend(BYTE* pSendString, WORD len);
// ascString.c
// ringbuffer containing received bytes
DWORD g_ascStrMaxRecvInterByteGapTime;
// max. time allowed between any two bytes
DWORD obIdRecvString;
...
RecvStr g_ascStrRecvStr;
// forward declaration of callbacks
void ascStr_OnRxRecv(BYTE recvByte);
void ascStr_OnRxComplete(int);
Implementing Module FreqOutInterface
Implementation
// freqOut.h
extern DWORD obIdFrequency;
void FreqOutInit();
//freqOut.c
// includes
DWORD obIdFrequency; // Data Object ID
WORD g_foHalfPeriod; // in 100 us
DWORD g_foTaskId;
// forward declaration of mini-tasks
void OnFreqChanged(BYTE* pData, DWORD objectId);
void OnFreqOutHalfPeriod(int timerVal);
void FreqOutInit() {
WORD freq;
obIdFrequency = DataCreateObject(sizeof(freq));
freq = 1; // initialized to 1 Hz
DataSetObject((BYTE*)&freq, sizeof(freq), obIdFrequency);
g_foHalfPeriod = 10000 / freq / 2;
DataAddNotifyCB(OnFreqChanged, obIdFrequency, 0);
g_foTaskId = TaskT_CallBackCyclicAt(g_foHalfPeriod, OnFreqOutHalfPeriod, 0, 0);
}
// Subcriber mini-task
void OnFreqChanged(BYTE* pData, DWORD objectId) {
// calculate half period
g_foHalfPeriod = 10000 / (*(WORD*)pData) / 2;
TaskT_ChangeCallbackTime(g_foHalfPeriod, g_foTaskId, 0);
}
// timed mini-task
void OnFreqOutHalfPeriod(int unusedInt){
unusedInt = 0;
LED_TOGGLE(1)
}
Implement Subscribers & Reaction in Main Module
Initialization Task - Implementation// forward declaration of mini-tasks
void OnCyclicAdcConvTimer(int i);
void OnAdValChangedHigh(BYTE* pData, DWORD objectId);
void OnAdValChangedLow(BYTE* pData, DWORD objectId);
void OnRxStrRecv(BYTE* pData, DWORD objectId);
int main (void) {
...
Task_Init(); // init DCEO-RTOS
AscInit(115200); // init modules
AscStrInit(0, '\r');
AdcInit();
FreqOutInit();
// create mini-tasks
DataAddNotifyCB(OnAdValChangedHigh, obIdAdcVal, 0);
TaskT_CallBackCyclicAt(300, OnCyclicAdcConvTimer,
1, 1);
DataAddNotifyCB(OnRxStrRecv, obIdRecvString, 2);
DataAddNotifyCB(OnAdValChangedLow, obIdAdcVal, 2);
...
while (TRUE) { } // loop forever
}
void OnCyclicAdcConvTimer(int i){
// start AD-conversion
AdcConversionStart();
}
void OnAdValChangedHigh(BYTE* pData, DWORD objectId){
...
UpdateVar(pData, objectId, obIdAdcVal, (BYTE*)&adcValue, sizeof(adcValue));
outFreq = algorythm(adcValue)
if (outFreq != g_freq) {
g_freq = outFreq;
DataSetObject((BYTE*)&g_freq,
sizeof(g_freq), obIdFrequency);
}
}
void OnAdValChangedLow(BYTE* pData, DWORD objectId) {
...
UpdateVar(pData, objectId, obIdAdcVal,
(BYTE*)&adcValue, sizeof(adcValue));
sprintf(adValStr, "AD Val = %04d\r\n", adcValue);
AscStrSend(adValStr, strlen(adValStr));
}
void OnRxStrRecv(BYTE* pData, DWORD objectId)
...
Performance Measurement1 - toggles with 22 - trigger AD conversion3 - ADC ISR (start – end)4 - High prio reaction
(intercepts ISR)5 - Low prio report to UI6 - 3 minus 47 - time base 100µs8 - not idle
Reaction Times (for ARM7 60MHz)• Timed mini-task < 8 s
(time-base-tick to start of mini-task including context switch, 7 low to 2 high)
• Subscriber mini-task < 16 s (set data object to invocation of subscriber including context switch, 6 high (1st) to 6 low (1st)
• Total reaction time > 20 s (external HW-event to external HW-reaction)
SummaryMain differences between traditional RTOS and
DCEO-RTOS
• Stop thinking in terms of processes -> Start thinking about Data, Events and Reactions
• Gain flexibility – use data from all priority mini-tasks without worrying about synchronization
• Gain performance – use 100% processor time and still have a deterministic reaction of higher priority tasks. Use processors interrupt priorization.
Process vs. Data Centric ViewProcess Oriented Data Centric
Central Element Tasks Data Objects & Timers
Reaction implemened by
RTOS involving task switch
HW Interrupt Controller schedules reaction which executes in uses SW ISRs
Synchronization Involves saving / resumption of task status
Not required due to proper design of task-groups.
Programmers job Create producer-tasks and consumer-tasks of events
Design Data-flow, create modules
Inter-Module-Dependancy
Easily becomes high More easy to keep low
Independant Module reusability
Easily becomes low More easy to keep high
Project Fexibility Coded Run-time configurable Data-flow
Outlook & Ideas• DCEO-RTOS is well suited for
– Embedded Software Development– Multi-Core Support -> assign a task-group to a core, only means
of communication is through data-objects– Distributed Computing -> many nodes of a network react to
global data-ojects using a real time communication network
• Next development steps of DCEO-RTOS will address – Support more processors (currently ARM7)– Bus-Interfaces (with HW-abstraction layer)– UML-Tool Integration
• Idea– Development-Tool for easy linking of data-objects to UI-controls
ContactEmail [email protected] rtosdeveloperWeb cleversoftware.de