The StartOS function is the entry point of the kernel.
void StartOS(
void
);
None.
The funtion never returns.
Call StartOS
function to start the kernel. This function will add
Idle task to the task list and switch to the task with the highest priority
which is ready to go.
After calling this function all contents of the stack will
be erased! The kernel rewinds the stack pointer to the initial value
when you enter into kernel mode. So the kernel itself is non reentrant,
that's why all interrupts are disabled in kernel mode.
The system enters into kernel mode in the macros ENTERINT()
and ENTER()
, and returns to normal
mode in macro LEAVE()
.
int main(void)
{
//Initialize peripherals and etc.
....
//Create tasks
KCreateTask(&KeysTask); //TID=1, prio=5
KCreateTask(&PotsTask); //TID=2, prio=2
KCreateTask(&MeterTask); //TID=3, prio=3
KCreateTask(&LCDTask); //TID=4, prio=1
KCreateTask(&VFOTask); //TID=5, prio=7
KCreateTask(&KeyerTask); //TID=6, prio=10
//Set up initial events state
KSetEvents(SPI_READY);
//Run the kernel
StartOS();
}
First of all you should declare and implement your task.
SOS offers simple macros to make it easy to create a task.
For declaration:
DECLARE_TASK(TaskName)
For implementation:
TASK(TaskName,TaskStack,Priority){ /*your task*/ }
Note: your task should never end up. If you want to terminate a task
call
TerminateTask
rather then return from the task function.
Next step is calling CreateTask
to put your task into task list
and activate
it (if the task has the nesessary priority).
Note: If you want to create task before calling StartOS
you
should use KCreateTask
instead of CreateTask
There are two functions and two macroses for creating new task:
tid_t KCreateTaskExt(void (*entry)(void),uint16_t* stack,uint8_t priority);
tid_t __attribute__((naked)) CreateTaskExt(void (*entry)(void),uint16_t*
stack,uint8_t priority);
#define
CreateTask(tdb)CreateTaskExt((tdb)->entry,(tdb)->stack,(tdb)->priority)
#define KCreateTask(tdb)
KCreateTaskExt((tdb)->entry,(tdb)->stack,(tdb)->priority)
Your project should have the Idle task, i.e. task that is always ready to go. The system will execute it when nothing more useful is ready to run.
The Idle task has fixed (and lowest) priority level 0.
There is a special macro for implementing Idle task:
#define IDLE(stack) TASK(Idle,stack,0)
Note: You do not need to declare Idle task. It has
been already declared in the OS.h file. Also you do not need to call any
CreateTask
function for the Idle task. It will be created
automatically after calling StartOS
.
You can use two types of interrupt service routines (ISRs).
The first are the ordinary ISRs you have used in your projects without using SOS. In this case you should not call any OS functions.
The second are more useful. In this case you have the SOSsyntax of an ISR:
osinterrupt(XXX_VECTOR) XXX_ISR(void)
{
ENTERINT();
//Your ISR code.
//You can call OS functions here.
//But remember YOU ARE IN THE KERNEL MODE HERE (see below)
LEAVE();
}
Marco ENTERINT()
will switch the system into special
kernel mode. In the kernel mode interrupts are disabled and stack is switched
to the kernel stack. You can use OS functions which begin with K (e.g.
KSetEvents, KReshedule
and etc.). These are designed specially to be called from
kernel mode. (They do not switch tasks immediately, rather
then correct internal OS data structures. Task switching will be made upon
returning from kernel mode). LEAVE()
macro
switchs the system from
kernel to normal mode. If necessary it also calls sheduler, which switchs
tasks. Consult Remarks sections in function description on using it in
interrupt handlers.
KReschedule
sets a flag to reschedule tasks.
It is defined in Kernel.h
#define KReschedule() ({ extern uint8_t Reschedule; Reschedule=1; })
Generally you do need to call this macro, since SOS kernel will set the rescheduling flag when needed.
//Use this function to gain control for
//the next task with the same priority
void __attricute__((naked)) Yield(void)
{
ENTER(); //Enter kernel mode
KReshedule(); //Set the rescheduling flag
LEAVE(); //Leave kernel mode and run scheduler
}
The KCreateTaskExt
function creates new task to execute.
tid_t KCreateTaskExt(
void (*entry)(void),
uint16_t* stack,
uint8_t
priority
);
entry
stack
priority
If the function succeeds, the return value is a task identifier to the new task. If the function fails, the return value is K_ERR (0xFF).
This function prevents switching tasks, so use it before starting kernel
(before calling StartOS) or from the interrupt handlers (tasks will be
rescheduled on the return from interrupt). For general purposes use
CreateTaskExt
function.
The CreateTaskExt
function creates new task to execute.
tid_t CreateTaskExt(
void (*entry)(void),
uint16_t* stack,
uint8_t priority
);
entry
stack
priority
If the function succeeds, the return value is a task identifier to the new task. If the function fails, the return value is K_ERR (0xFF).
This function calling can result in switching tasks, so do not use it before
starting kernel (before calling StartOS) or from the interrupt handlers. For
those purposes use KCreateTaskExt
function.
The KTerminateTask
function terminates a task.
void KTerminateTask(
tid_t tid
);
tid
The funtion does not return a value.
This function prevents switching tasks, so use it in the interrupt
handlers (tasks will be rescheduled on the return from interrupt). For general
purposes use TerminateTask
function.
The TerminateTask
function terminates a task.
void TerminateTask(
tid_t tid
);
tid
The funtion does not return a value.
This function calling can result in switching tasks, so do not use it before
starting kernel (before calling StartOS) or from the interrupt handlers. For
those purposes use KTerminateTask
function.
uint16_t KeyerStack[32];
TASK(KeyerTask,KeyerStack,10)
{
for(;;)
{
uint32_t e=WaitEvents(TERMINATE|DOSOMETHING,WAIT_ANY);
if(e==TERMINATE)TerminateTask(GetTID()); //Terminate task if TERMINATE event is set
//Do something here
......
}
}
The KSuspendTask
function suspends execution of the task.
void KSuspendTask(
tid_t tid
);
tid
The funtion does not return a value.
The execution of the task is suspended until another task calls
ResumeTask
or interrupt handler calls
KResumeTask
function.
This function prevents switching tasks, so use it in the interrupt
handlers (tasks will be rescheduled on the return from interrupt). For general
purposes use SuspendTask
function.
int main(void)
{
//Initialize peripherals and etc.
....
//Create tasks
KSuspendTask(KCreateTask(&PotsTask)); //This task will be suspended upon system startup
....
//Run the kernel
StartOS();
}
The SuspendTask
function suspends execution of the task.
void SuspendTask(
tid_t tid
);
tid
The funtion does not return a value.
The execution of task is suspended until another task calls
ResumeTask
or interrupt handler calls
KResumeTask
function.
This function call results in switching tasks, so do not use it before
starting kernel (before calling StartOS) or from the interrupt handlers. For
those purposes use KSuspendTask
function.
....
SuspendTask(GetTID());//Suspend self
....
The KResumeTask
function resumes execution of the task.
void KResumeTask(
tid_t tid
);
tid
The funtion does not return a value.
This function prevents switching tasks, so use it in the interrupt
handlers (tasks will be rescheduled on the return from interrupt). For general
purposes use ResumeTask
function.
The ResumeTask
function resumes execution of the task.
void ResumeTask(
tid_t tid
);
tid
The funtion does not return a value.
This function call can result in switching tasks, so do not use it before
starting the kernel (before calling StartOS) or from the interrupt handlers. For
those purposes use KResumeTask
function.
....
ResumeTask(TASK1_TID); //Resume TASK1 task
....
The KSetTaskPriority
function sets the priority value for the
specified task.
void KSetTaskPriority(
tid_t tid,
uint8_t priority
);
tid
priority
The funtion does not return a value.
This function prevents task switching, so use it in the interrupt
handlers (tasks will be rescheduled on the return from interrupt). For general
purposes use SetTaskPriority
function.
The SetTaskPriority
function sets the priority value for the
specified task.
void SetTaskPriority(
tid_t tid,
uint8_t priority
);
tid
priority
The funtion does not return a value.
This function call can result in switching tasks, so do not use it before
starting kernel (before calling StartOS) or from the interrupt handlers. For
those purposes use KSetTaskPriority
function.
uint8_t p=GetTaskPriority(GetTID()); //Get current task priority
SetTaskPriority(GetTID(),10); //Rise task priority
.... //Execute importand part of code
SetTaskPriority(GetTID(),p); //Restore priority
The GetTaskPriority
function retrieves the priority value for the
specified task.
uint8_t GetTaskPriority(
tid_t tid
);
tid
The return value is the task's priority level. (0 – idle priority, 1 – lowest, … , 255 – highest.)
This function is safe to call from interrupt handlers.
SetTaskPriority
for sample code.
The GetTID
function returns the Task IDentifier.
tid_t GetTID(
);
The return value is the Task IDentifier of the current task.
GetTID
is actually a macro with zero overhead.
SetTaskPriority
for sample code.
A critical section is a synchronization object whose state is set to signaled when it is not owned by any task, and nonsignaled when it is owned. Critical section object can be owned by only one task at a time, which makes it useful for protecting a shared resource from simultaneous access. Threads will obtain ownership of the critical section according to their priority (e.g. when the critical section is released and there are several tasks waiting for its freeing, the ownership will be granted to the highest priority task).
The task is responsible for allocating the memory used by a critical
section (fortunatily sizeof(cs_t)==1
:).
A task uses the EnterCS
function to request ownership of a
critical section. It uses the LeaveCS
function to release ownership of a critical section. If the critical section
object is currently owned by another thread, EnterCS
waits
indefinitely for ownership.
Once a thread owns a critical section, it can make additional calls to EnterCS
without blocking its execution. This prevents a thread from deadlocking itself
while waiting for a critical section that it already owns. To release its
ownership, the thread must call LeaveCS
once for each time that it
entered the critical section.
You can have unlimited number of the critical sections (actually it is limited by the available memory), but due to RAM economy critical sections occupied only 1 byte of the RAM. I think that 15 normal and 1 idle task will be sufficient for the most msp430 projects, so critical sections can be applyed only to first 16 tasks and may have 16 levels in depth.
The EnterCS
function waits for ownership of the specified critical
section object. The function returns when the calling thread is granted
ownership.
void EnterCS(
cs_t* cs
);
cs
The funtion does not return a value.
This function call can result in switching tasks, so do not use it before starting kernel (before calling StartOS) or from the interrupt handlers. Due to RAM economy I suggest that 15 normal and 1 idle task will be sufficient for msp430 projects, so critical sections can be applyed only to first 16 task and may have 16 levels in depth.
cs_t lcdcs=0; //Allocate critical section
void LCDDrawChar(char ch)
{
EnterCS(&lcdcs); //Enter critical section
.... //Draw char ch on the LCD (non reentrant piece of code)
LeaveCS(&lcdcs); //Leave critical section
}
uint16_t Task1Stack[50];
TASK(Task1,Task1Stack,1)
{
for(;;)
{
....
LCDDrawChar('1');
....
}
}
uint16_t Task2Stack[50];
TASK(Task2,Task2Stack,1)
{
for(;;)
{
....
LCDDrawChar('2');
....
}
}
The LeaveCS
function releases ownership of the specified
critical section object.
void LeaveCS(
cs_t* cs
);
cs
The funtion does not return a value.
This function call can result in switching tasks, so do not use it before starting kernel (before calling StartOS) or from the interrupt handlers. Due to RAM economy I suggest that 15 normal and 1 idle task will be sufficient for msp430 projects, so critical sections can be applyed only to first 16 task and may have 16 levels in depth.
EnterCS
for sample code.
An event object is a synchronization object whose state can be explicitly set
to signaled by use of the KSetEvent
or
SetEvent
function.
The event is useful in sending a signal to a task indicating that a particular
event has occurred. For example, an interrupt handler may notify task that data
is ready to be processed. It sets a specified event to the signaled state when
the interrupt occurs A single task can specify different events, then use
WaitEvents
function to wait for the state of any one (or
all) of the events to be signaled. Also you can use CheckEvents
to check particular event(s) state.
Due to RAM limitations SOS incoorporates only 32 events. (Note: timer module will occupy up to 7 events when used.) All events are global objects.
Each event is represented as 32 bit mask (with one bit set). It is very convient, because you can combine different events by simple "OR-ing" them:
WaitEvents(EVENT_1|EVENT_2,WAIT_ALL); //Waits for BOTH events
WaitEvents(EVENT_1|EVENT_2,WAIT_ANY); //Waits for ANY event
Usually events ones allocated are used from program start to the end. That is why SOS does not incoorporates dynamic events creation and deleting. You should arrange all events manually (i.e. assign unique bit number to each event in your program). This may looks like:
#define EVENT_1 0x00000080
#define EVENT_2 0x00000100
Note:The timer module occupys up to 7 lower bits when used.
There are the two types of events in SOS:
Event type | Description |
---|---|
Manual reset event |
An event whose state remains signaled until it is explicitly reset to
nonsignaled by the ResetEvents function.
While it is signaled, any number of waiting tasks, can be released.
|
Auto reset event | An event whose state remains signaled until a single waiting task is released, then the system automatically sets the state to nonsignaled. If no tasks are waiting, the event's state remains signaled. |
The KSetEvents
function sets the state of the specified
event(s) to signaled.
void KSetEvents(
uint32_t mask
);
mask
The funtion does not return a value.
This function prevents switching tasks, so use it in the interrupt
handlers (tasks will be resheduled on the return from interrupt). For general
purposes use SetEvent
function.
osinterrupt(PORT2_VECTOR) Port2IntHandler(void)
{
ENTERINT();
P2IFG=0;
KSetEvents(VFO_CHANGED|REDRAW_MULTY_CLR); //Inform tasks about event
LEAVE();
}
The SetEvents
function sets the state of the specified
event(s) to signaled.
void SetEvents(
uint32_t mask
);
mask
The funtion does not return a value.
This function call can result in switching tasks, so do not use it before
starting kernel (before calling StartOS) or from the interrupt handlers. Use
KSetEvent
function in the interrupt handlers.
switch(key)
{
....
case KMETER: //Meter
meter=(meter+1)%3;
SetEvents(REDRAW_LINE3); //Inform display updating task, that we need to redraw part of the display
break;
....
}
The ResetEvents
and KResetEvents
functions set
the state of the specified event(s) to nonsignaled.
void ResetEvents(
uint32_t mask
);
void KResetEvents(
uint32_t mask
);
mask
The funtions do not return a value.
The ResetEvent
function disables interrupts during events setting,
so use it in the common code. This function does not cause task switching.
The KResetEvent
is actually inline assembly macros, so use it in
the kernel mode code (e.g. interrupt handlers), when interrupts are already
disabled.
for(;;)
{
uint32_t e=WaitEvents(EVENT1|EVENT2,WAIT_ANY);
//Process events
....
//Check if we need more cycles ?
e=More();
ResetEvents(e); //Reset events if we have done all the necessary thing
}
The SetAutoEvents
and KSetAutoEvents
functions set
the type of the specified event(s) to auto reset type.
void SetAutoEvents(
uint32_t a
);
void KSetAutoEvents(
uint32_t a
);
a
The funtions do not return a value.
The SetAutoEvents
function disables interrupts during events type
setting, so use it in the common code. This function does not cause task
switching.
The KSetAutoEvents
is actually inline assembly macros, so use it
in the kernel mode code (e.g. interrupt handlers), when interrupts are already
disabled.
If you do need to change event(s) type during program execution you can use
another method to set up the desired events types. The EVENTS_MODE
constant in the Config.h
file specifies event type upon system startup. Set
the bit if you want the corresponding event to be auto reset type, reset
otherwise.
The SetManualEvents
and KSetManualEvents
functions set
the type of the specified event(s) to manual reset type.
void SetManualEvents(
uint32_t m
);
void KSetManualEvents(
uint32_t m
);
m
The funtions do not return a value.
The SetManualEvents
function disables interrupts during events
type setting, so use it in the common code. This function does not cause task
switching.
The KSetManualEvents
is actually inline assembly macros, so use it
in the kernel mode code (e.g. interrupt handlers), when interrupts are already
disabled.
If you do need to change event(s) type during program execution you can use
another method to set up the desired events types. The EVENTS_MODE
constant in the Config.h
file specifies event type upon system startup. Set
the bit if you want the corresponding event to be auto reset type, reset
otherwise.
StartOS
for sample code.
The WaitEvents
function waits until either any one or all
(according to the mode
flag) of the specified events are in the
signaled state.
uint32_t WaitEvents(
uint32_t mask,
uint8_t mode
);
mask
mode
WAIT_ALL
constant is used,
the function returns when the state of all events in the mask
parameter
is signaled. If WAIT_ANY
, the function returns when the state of
any one of the events is set to signaled. In the latter case, the return value
indicates the event whose state caused the function to return.
If mode
parameter is WAIT_ALL
then the return value
the same as mask
parameter.
If mode
parameter is WAIT_ANY
then the return value
indicates the event that caused the function to return.
WaitEvents
function determines whether the wait criteria have been
met. If the criteria have not been met, the calling task enters the wait state.
It uses no processor time while waiting for the criteria to be met.
When mode
set to WAIT_ALL
, the function's wait
operation is completed only when the states of all events have been set to
signaled. The function does not modify the states of the specified events until
the states of all eventss have been set to signaled.
The function modifies the state of the autoreset type events. Modification occurs only for the event or events whose signaled state caused the function to return.
This function calling can result in switching tasks, so do not use it before starting kernel (before calling StartOS) or from the interrupt handlers.
ResetEvents
for sample code.
The TryEvents
function checks the state of the specified
event(s).
uint32_t TryEvents(
uint32_t mask,
uint8_t mode
);
mask
mode
mask
are combined.
See WaitEvents
function description for
more information.
If the specified criteria have not been met the funtion returns zero. Otherwise
the behaviour is the same as WaitEvents
function.
The TryEvents
function checks for the current events state. If the
specified criteria have been met, the function behaves exactly as
WaitEvents
function does, otherwise it returns zero and
does nothing. This function does not cause task switching.
Timers are used to satisfy various tasks' timing requirements.
Timer module implementation in SOS is a bit tricky. Timers implemetation uses
Timer_B
hardware timer module. (So for msp430f14x up to seven
timers
available simultaneously). I used such unusual scheme because of minimal use of
RAM/ROM/CPU resources goal. Timer module implements dynamically allocated
timers,
so you should never have any problems associated with the limited timers
number.
(I do not think that application that uses more then seven timers
simultaneously
is properly designed, except of some very specific cases).
Each time the specified interval (or time-out value) for a timer elapses, the system sets the event associated with the timer. So the timer module operation is completely independent from the SOS kernel.
A task uses the CreateTimer
function
to create new timer. You can create syncronization and notification
timers
by specifying the appropriate flags. Syncronization timers use
auto-reset events, while notification
- manual-reset events. You can also create "suicidal" timer; it will
kill itself after the
specified timeout elapsed. The timer can set the corresponding event only once
or periodically
(see CreateTimer
function
description for more information).
The task should call the KillTimer
function for each
created timer (except of "suicidal" timers) to allow reuse of timer
resource.
All timeout-values are specified in the units dependent from the
particular hardware in use
(SOS assumes that Timer_B module is clocked from the 32768 Hz source). Use ms(t)
macro
to specify timeout-values in milliseconds.
....
Sleep(ms(100)); //Suspend the execution of the current task by 100 ms
....
The CreateTimer
function creates a timer with the specified
time-out value and flags.
timer_t CreateTimer(
uint8_t tt,
uint16_t t,
uint16_t cyc,
);
tt
t
cyc
The funtion returns a bitmask of the event associated with this timer (this
value is the timer handle also).
If the function fails (i.e. all timers have been already allocated) the
function returns K_ERR (0xFF)
value.
You can create various timers by specifying the appropriate flags.
Flag | Description |
---|---|
TT_SYNC | Use to create syncronization timer. It will use auto-reset event. |
TT_NOTIFY | Use to create notifycation timer. It will use manual-reset event. |
TT_SUICIDAL | The timer will kill itself after the specified timeout elapsed. |
TT_PULSE | The combination of TT_SYNC|TT_SUICIDAL. |
cyc
parameter is not 0 the timer will set the associated
event every cyc
period. Such timer can not be "suicidal".
The task should call the KillTimer
function for each
created timer (except of "suicidal" timers) to allow reuse of timer
resource.
Sleep
function is implemented
using just two SOS calls:
void Sleep(uint16_t t)
{
WaitEvents(CreateTimer(TT_PULSE,t,0),WAIT_ALL);
}
The SetTimer
function sets new timing parameter for the previously
created timer, specified by timer
parameter.
void SetTimer(
timer_t timer,
uint16_t t,
uint16_t cyc,
);
timer
CreateTimer
function call).
t
cyc
The funtion does not return a value.
See CreateTimer
function
description for more information.
The KillTimer
function destroys the specified timer.
void KillTimer(
timer_t timer
);
timer
CreateTimer
function call).
The funtion does not return a value.
Do not use this function for "suicidal" timers.
The Sleep
function suspends the execution of the current task
for the specified interval.
void Sleep(
uint16_t t
);
t
The funtion does not return a value.
See CreateTimer
function
description
for Sleep
source.
....
Sleep(ms(100)); //Suspend the execution of the current task by 100 ms
....