A data-centric event-oriented RTOS for MCUs

Download Report

Transcript A data-centric event-oriented RTOS for MCUs

A data-centric event-oriented
RTOS for MCUs
Simplify multithreaded programming
And Boost Performance
by Dirk Braun
Contents
1. The Concept of the Data-Centric RTOS
3 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
2. The DCEO-RTOS in practise
•
•
•
Example Application
Define Task-Groups, Mini-Tasks & Data Objects
Performance Measurements
3. Summary & Outlook
Goal 1: Fast Real Time Reaction
• Real-Time Reaction – objective: fast & deterministic
• Cause of a Reaction is an Event
• Event
Event
– change of state of connected HW
– Time has elapsed
– New: Data item has changed
• Reaction
– SW reaction
– Ultimately – HW reaction
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
main()
{
CreateTask(MyTask);
...
while (TRUE);
}
MyTask()
{ // implements reaction
while (TRUE)
{
WaitForEvent(&myEvent);
// react to myEvent here
// todo: sync access to g_input
reaction = g_input * xxx
};
}
MyISR() __irq
{ // do some HW stuff to
// retrieve input
// todo: sync access to g_input
g_input = HW;
SetEvent(&myEvent);
}
Event-Reaction Implementation
using SW-Interrupts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
main()
{
// setup ISRs
while (TRUE);
}
MyTask() __irq
{ // implements reaction
// react to myEvent here
reaction = g_input * xxx
}
MyISR() __irq
{ // do some HW stuff to
// retrieve input
g_input = HW;
myTaskTrigger = 1;
}
• 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
Scaling
range
ADCISR
Publisher Subscriber engine
DCEORTOS
change
data object
DataPublisher
safe data copy
passed into callbacks
Subscriber 1
(callback function)
at low priority
raw AD
value
Generic
Scaler
Device
Reaction
output
scaled
AD value
Realize
Output
Bus Network
Subscriber n
(callback function)
at priority m
Display
Goal 4: Module Independency
Increased by Data Centric View …
Reuse Modules
Random
Generator
Data Logger
ADCISR
Scaling
range
ADC Test App
raw
AD
value
ADCISR
raw
AD
value
Check
value
Scaler Test App
Generic
Scaler
scaled
AD
value
raw
value
Ref.
scaled
value
Data Logger
writes to file
Scaling
range
Generic
Scaler
scaled
value
Check
scaling
… by using simpler interfaces …
Interface using Data-Object
// adc.h
// ADC value, a WORD
extern DWORD obIdAdcVal;
Simple Interface
void AdcInit();
void AdcConvStart();
Interface using global
data & event
// adc.h
// ADC value, a WORD
WORD g_adcVal;
EVENT g_adcValAvailEvent;
MUTEX g_adcValAccMutex;
void AdcInit();
void AdcConvStart();
Module independency
easier to maintain
… by avoiding global variables
„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
Universal access function for
all data-objects + notification
// myCode.c
void UseVarValueFunction() {
MyType myVar;
myVar = GetVar();
}
// myCode.c
void UseVarValueFunction() {
MyType myVar;
GetDataObject(&myVar, obIdMyVar);
}
void WriteVarValueFunction() {
MyType myVar;
...
SetVar(myVar);
}
void WriteVarValueFunction() {
MyType myVar;
...
SetDataObject(&myVar, obIdMyVar);
}
Instead of providing a separate set
of access functions just publish a
Data-Objects ID (a handle).
void OnMyVarChanged(BYTE* pData, …) {
MyType* pNewMyVar = pData;
...
}
Mutual Dependency ->
non reusable code
Module MyCode1
Module MyCode2
// myCode1.h
#include “myCode2.h”
// myCode2.h
#include “myCode2.h”
Extern WORD myVar1;
...
Extern WORD myVar2;
...
// myCode1.c
#include “myCode2.h”
// myCode2.c
WORD myVar1;
...
WORD myVar2;
...
#include “myCode2.h”
 modules cannot be
reused without each other
 cannot be used in a
layered model
One module cannot
compile without the
other
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
1 void Task1() {
// do something
EnterMutex(g_mutexA);
// use shared resource
.
.
suspended
.
.
LeaveMutex(g_mutexA);
.
suspended
.
// do something
2
3
void Task2() {
// do something
EnterMutex(g_mutexA);
suspended
// use shared resource
LeaveMutex(g_mutexA);
4
5
Mutexes rarely
cause task switches
}
}
Task 2
3
high prio
Process flow
OS calls
higher prio
task 2
4
5
Task 2 leaves
Mutex A
2
Task 2
finished
low prio
1
Task 1
Task 1 enters
Mutex A Section 1
Task 2 tries to enter Mutex A. This
causes a priority inversion and Task 1
is resumed at HIGH priority.
Task 1 leaves Mutex A and is
suspended to low priority again.
Task 2 is resumed.
Task 1
finished
spare
processor time
;-)
Critical Section 1
inactive Task
running Task
suspended Task
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
Scaling
range
ADCISR
raw AD
value
Generic
Scaler
scaled AD
value
Device
Reaction
Realize
Output
Three tasks „Generic Scaler“ „Device
Reaction“ and „Realize Output“ are
subscriber mini-tasks of one taskgroup at a single priority.
output
Bus
Network
Display
High
Prio
HW
IRQ
Med
Prio
Low
Prio
Time
Generic Device Realize
Scaler Reaction Output
Generic Device Realize
Scaler Reaction Output
ADCISR
ADCISR
Bus
Network
Bus
Network
Display
Display pre-empted
Display
continued
Timed & Subscriber Mini-Tasks
High Prio
Software - Interrupt
Med Prio
Software - Interrupt
Low Prio
Publisher – Subscriber
Time – Engine
Software - Interrupt
•Manages Data-Objects and
•Timed
mini-task
invoked when
Publisher
–
Subscriber
Time
–
Engine
subcriber mini-tasks
time has come
•Manages
Data-Objects
and
•Timed mini-task invoked when
•Subscribers
invokedPublisher
when
–
Subscriber
– Engine
•Cyclic
and
one-time Time
(time out)
subcriber
mini-tasks
time has come
data-of-interest
changed
•Manages
and
timed tasks supported
•Timed mini-task invoked when
•Subscribers
invoked Data-Objects
when
•Cyclic
and
one-time (time out)
subcriber
mini-tasks
time has come
data-of-interest
changed
timed tasks supported
•Subscribers invoked when
•Cyclic and one-time (time out)
data-of-interest changed
timed tasks supported
• 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.
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
Description
ADC
•
•
Function for triggering conversions
Provide Data-Object for
conversion result
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
Main application
•
•
•
•
React on new AD values
Set Output Reaction
React to UI
Time Output
Interface
// adc.h
// ADC value, a WORD
extern DWORD obIdAdcVal;
Module
ADC
void AdcInit();
void AdcConvStart();
// ascString.h
extern DWORD obIdRecvString;
extern DWORD obIdSentString;
AscString
void AscStrInit(DWORD
gapTime, char endChar);
BOOL AscStrSend(BYTE*
pSendString, WORD len);
Empty
Main
More Modules
Description
ASC
•
Byte-wise RS232 communication
Interface
// adc.h
void AscInit(DWORD baudrate);
void AscSendByte(BYTE sendByte);
Module
ASC
// register callbacks
BOOL AscSetRxCallBack(
AscRXCbPtr pRxCallback);
BOOL AscSetTxCallBack(
AscTXCbPtr pTxCallback);
CmdInterpreter (helper module)
•
Provides functions to interpret
commands in a string
// CmdInterpreter.h
typedef enum CmdCodeEnum {
CmdHelp, // general
CmdPeriod,
CmdCodeInvalid,
CmdNull
} CmdCode;
Cmd
Interpreter
CmdCode CiGetCommand(char*
cmdString, int len);
FrequencyOutput
•
•
HW-Output for frequency
Provide Data-Object for frequency
// freqOut.h
// frequency, a WORD
extern DWORD obIdFrequency;
void FreqOutInit();
Frequency
Output
Module Relations & Data Objects
Application
Layer
HW-Logic
Layer
DCEO RTOS
AscString
ADC
HWAbstraction
Layer
Function call
Cmd
Interpreter
Main
Timer0
Frequency
Out
ASC
Call Callback
Data Objects:
Data Notification
• 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
High Priority
Task Group
Reaction
Realizing Output
ADC Interrupt
Priority
Medium Priority
Task Group
ADC ISR
Set Data Object
Cyclic MiniTask Triggering
Conversions
Sync required
ASC (UART)
Interrupt Priority
Low Priority
Task Group
ASC ISR
Write to UI
ASC
Buffer &
Control
Cyclic MiniTask incrementing
& writing clock
• Task-group-priorities ARE interrupt-priorities, so task-grouppriorities & interrupt-priorities belong into the same priority list
• Data-objects are safely shared across priority-boundaries
• Priority plan identifies synchronization requirements
Setup - code
Defining Priorities
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
#define
#define
#define
ASC_ILVL
ASC_CHANNEL
12
TASK_T_ILVL
4
TASK_T_CHANNEL
int main (void) {
...
Task_Init();
AscInit(115200);
AscStrInit(0, '\r');
AdcInit();
INTERRUPT_ENABLE()
while (TRUE);
Interrupt levels & task priorities shown in red
init
init
init
init
DCEO-RTOS
ASC
AscString
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);
7
5
//
//
//
//
}
Mini-Task creation shown in red
Implementing Module ADC
Interface
// adc.h
// ad converted value, a WORD, OUT
extern DWORD obIdAdcVal;
void AdcInit();
void AdcConversionStart();
Implementation
// 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);
}
// 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;
}
Implementing Module ascString
Interface
// 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);
Implementation
// 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);
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);
...
}
Implementing Module FreqOut
Interface
// freqOut.h
extern DWORD obIdFrequency;
void FreqOutInit();
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;
Implementation
//freqOut.c
// includes
DWORD obIdFrequency;
WORD g_foHalfPeriod;
DWORD g_foTaskId;
// Data Object ID
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);
// in 100 us
// forward declaration of mini-tasks
void OnFreqChanged(BYTE* pData, DWORD objectId);
void OnFreqOutHalfPeriod(int timerVal);
}
// 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);
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)
...
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
}
Performance Measurement
1 - toggles with 2
2 - trigger AD conversion
3 - ADC ISR (start – end)
4 - High prio reaction
(intercepts ISR)
5 - Low prio report to UI
6 - 3 minus 4
7 - time base 100µs
8 - 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 (1 st) to 6 low (1st)
• Total reaction time > 20 s
(external HW-event to external HW-reaction)
Summary
Main 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 View
Process 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 Design Data-flow, create
and consumer-tasks of modules
events
Inter-ModuleDependancy
Easily becomes high
More easy to keep low
Independant
Easily becomes low
Module reusability
More easy to keep high
Project Fexibility
Run-time configurable Dataflow
Coded
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
Contact
Email [email protected]
Skype rtosdeveloper
Web cleversoftware.de