This document describes aOS, an implementation of real-time operating system for Atmel AVR ATmega micro controller family.
This software was written to self-study and explore operating system basics.
DISCLAIMER: aOS may be used and distributed freely under GNU General Public License (GPL). Author will not take responsibility for any consequencies using this software might cause.
aOS is developed with Atmel’s development kit STK300 which has ATmega103L micro controller. Here are some of features ATmega includes:
The central table is a data structure which holds all vital data of operating system. The central table is defined as follows:
typedef struct
aos_central_table {
ULONG aos_systime; /*
Timer ticks since last boot */
ULONG aos_idlecntr; /*
Counter for processor idle time */
UBYTE aos_version; /*
OS version */
UBYTE aos_running; /*
AOS running flag */
aos_tcb *tcb_used_list; /* Pointer to used TCBs */
aos_tcb *tcb_free_list; /* Pointer to TCB freelist */
aos_tcb *tcb_current; /* Pointer to current running task TCB */
aos_tcb *tcb_runq; /*
Pointer to highest priority runnable task TCB */
aos_scb *scb_used_list;
/* Pointer to used semaphores */
aos_scb *scb_free_list;
/* Pointer to semaphores freelist */
aos_mcb *mcb_used_list; /* Pointer to used mailboxes */
aos_mcb *mcb_free_list; /* Pointer to mailbox freelist */
} aos_cent_tbl;
Members of aos_central_table:
Member |
Type |
Description |
aos_systime |
32-bit unsigned integer |
Counter which is incremented in every 30 ms. |
aos_idlecntr |
32-bit unsigned integer |
Counter which is incremented each time when NULL-task is dispached. |
aos_version |
8-bit unsigned integer |
Holds OS version number. |
tcb_used_list |
TCB pointer |
Pointer to first used task control block. |
tcb_free_list |
TCB pointer |
Pointer to the first free task control block. |
tcb_current |
TCB pointer |
Pointer to currently running task’s task control block. |
tcb_runq |
TCB pointer |
Pointer to highest priority runnable task’s task control block. |
scb_used_list |
SCB pointer |
Pointer to first used semaphore control block. |
scb_free_list |
SCB pointer |
Pointer to first free semaphore control block. |
mcb_used_list |
MCB pointer |
Pointer to first used mailbox control block. |
mcb_free_list |
MCB pointer |
Pointer to first free mailbox control block. |
There are number of constant values which must be set properly before using aOS. This is because aOS pre-allocates memory for certain global data structures in compilation time. All user configurable constant are located in aos.h file. The values include following:
The aOS has declared following data type in aos.h:
typedef unsigned
char UBYTE;
typedef unsigned
int UWORD;
typedef unsigned
long int UDWORD;
typedef unsigned
long int ULONG;
Task is simply a subprogram or function having it’s own operating environment a.k.a. context. The operating system always dispatches the highest priority runnable task and the task will stay on processor until its status changes from runnable by some event or until it is interrupted by higher priority task.
All tasks share the same address space. The operating system doesn’t provide a memory protection of any kind. The result is that all functions and global variables, even internals of aOS, are accessible to user tasks. This must be kept in mind when programming with tasks.
Each task within aOS is assigned to task control block (TCB). The TCB holds all data about a certain task. All used task control blocks are bound together as a linked list. The task control block is defined as follows:
typedef struct
aos_task_control_block {
void *hwstk; /*
Pointer to task's hardware stack */
void *datastk; /*
Pointer to task's data stack */
UBYTE status; /*
Status of task */
UBYTE prio; /*
Priority of task */
UWORD delay; /*
Nbr of ticks to keep task sleeping */
struct aos_task_control_block *next_tcb; /* Pointer to next task's TCB */
void *tcb_sem_q; /* Next task on semaphore waiting list */
} aos_tcb;
Members of task control block:
Member |
Type |
Description |
hwstk |
Pointer |
Task’s HW-stack pointer. |
datastk |
Pointer |
Task’s SW-stack pointer. |
status |
8-bit unsigned integer |
Task’s state status. Possible states are RUNNABLE, RUNNING, WAITING and DELAYD. |
prio |
8-bit unsigned integer |
Task’s priority 0-254. Smaller value, higher priority. |
delay |
16-bit unsigned integer |
Counter for clock ticks to keep task sleeping. |
next_tcb |
TCB pointer |
Pointer to next task control block in used TCB list. |
tcb_sem_q |
TCB pointer |
Pointer to next task control block which is waiting for a semaphore. |
All tasks within aOS are created by a system function aos_task_create(). The function is called internally by aOS to create NULL-task and the first user task during system initialisation. The function is also accessible to user tasks in order to create new tasks.
The function has four parameters which will deliver all needed information to make new task runnable. The information includes function pointer to task itself, pointers to HW- and SW-stacks and task’s priority.
See appendix A for the initial format of HW stack. The HW-stack holds return address to task and all general purpose registers except R28 and R29. On the top, there is SREG with global interrupt enable flag set. The SW-stack is not needed to be formatted, only its start address is saved.
Next the function enters its critical section by disabling interrupts. The function tries to get a free Task Control Block (TCB) from TCB-free list. If all TCBs are in use, the function returns with null pointer. Otherwise free TCB is extracted from free list and it is assigned to new task.
After successful TCB creation and formatting, the interrupts are enabled and function returns with a pointer to new TCB.
AVR micro controllers are implemented in such way that there is a need for two separate stacks. Hardware stack is used by interrupt mechanism and subprogram calls to store the return address. HW stack is also used by PUSH and POP commands. Software stack is used to pass parameters to subprograms and receive return values from them. SW stack is also used to store local variables of a subprogram.
Each task must have dedicated HW and SW-stack. They are allocated statically in compilation time and their sizes are fixed.
Null-task is a similar task to user tasks but it has some special features. The null-task has the lowest priority and it is always runnable. This causes null-task to be dispatched whenever there is no runnable user task available. Hence the null-task is running when the system is idle.
The null task that is used with development board:
void NULLTASK( void )
{
UWORD i;
while( 1 ) {
aos_ctbl.aos_idlecntr++; /* Increase idle time counter */
LED8 = ON; /* Do also some LED blinking */
for(i=0;i<20000;i++)
{};
LED8 = OFF;
for(i=0;i<20000;i++)
{};
}
}
An example of multitask programming can be found in chapter 10, section 10.6. There are four independent tasks: task1, task2, task3 and task4. The first task, task1, is created by aOS in module core.c in function main(). All other task are created by the previously created task. The stacks are also allocated in core.c
It can seen that tasks 1, 2 and 3 use a semaphore sem_portb for mutual exclusion. The PORTB in which LEDs are connected is a resource that only one of those three tasks is allowed to access at time.
There is also message passing between tasks 2 and 3. Task2 uses system function aos_uptime() to get some data to send to mailbox. Task3 picks up the message and prints it to serial line.
Task4 does its own duties only. It doesn’t communicate with other task, it only calls aos_sleep() occasionally. This task has also the lowest priority so it will be pre-empted each time some other task becomes runnable.
The scheduler is responsible for deciding which task will be running next. The decision is based on tasks’ status and priorities. The scheduler function is called _aos_schedule(). It’s prefixed with underscore character to differentiate it from user callable system functions.
The aOS uses pre-emptive scheduling algorithm. This means that tasks and interrupts can cause an immediate task switch, if a higher priority task becomes runnable as the result of an aOS function. The highest priority task which is runnable is always chosen to be executed next. If the chosen task is not the same task that is currently running, the task have to be swapped. The swap, a.k.a. context switch, is achieved by calling _aos_dispatch() system function.
The NULL-task is always made runnable by scheduler. This causes NULL-task to be dispatched if there is no other runnable task in the system.
See appendix B for a flow chart of scheduling algorithm.
The dispatcher makes actual switching between tasks. The switching procedure is also known as a context switch. The context switch has three parts:
The dispatcher function is called _aos_dispatch().It’s prefixed with underscore character to differentiate it from user callable system functions.
6.1.1 Basics
Semaphores provide synchronization and mutual exclusion between tasks. Basically semaphore is a global variable with an initial value from 0 to 65535.
6.1.2 Semaphore control block
Each semaphore within aOS is assigned to a semaphore control block (SCB). The SCB holds all data about a certain semaphore. All used semaphore control blocks are bound together as a linked list. The semaphore control block is defined as follows:
typedef struct
aos_sema {
UWORD count; /*
Semaphore value */
aos_tcb *scb_sem_q; /* Semaphore queue */
struct aos_sema *next_scb; /*
Pointer next semaphore's SCB */
} aos_scb;
Members of semaphore control block:
Member |
Type |
Description |
count |
16-bit unsigned integer |
The value of semaphore 0-65535. |
scb_sem_q |
TCB pointer |
Pointer to first task control block waiting for this semaphore. |
next_scb |
SCB pointer |
Pointer to next used semaphore control block. |
6.1.3 Semaphore creation
A semaphore is created with system function aos_sem_create(). The function takes first free Semaphore Control Block (SCB) from SCB free list, initialises its members and returns a pointer of SCB to calling task. The pointer is used as a “handle” when accessing the semaphore. If all SCBs are in use, a null pointer is returned.
6.1.4 Semaphore operations
Semaphores are manipulated with system functions aos_wait() and aos_signal(). Aos_wait() first tests the value of semaphore. If the value is positive, it is decremented by one and the calling task is resumed. If the value is zero, the calling task is suspended and it is put to semaphore’s wait queue until some other task aos_signal()s the semaphore.
Aos_signal() first examines semaphore’s wait-queue. If there is task(s) waiting for semaphore, the first one is de-queued and re-scheduling will occur. If the wait-queue is empty, the semaphore value is simply incremented by one and calling task is resumed.
Since semaphore operations are indivisible they should be coded as critical sections. This is implemented by simply disabling interrupts while they are executed.
6.2.1 Basics
Mailbox provides a mechanism to pass different types of messages between tasks. A mailbox is a memory location holding a pointer to actual message.
6.2.2 Mailbox control block
Each mailbox within aOS is assigned to a mailbox control block (MCB). The MCB holds all data about a certain mailbox. All used mailbox control blocks are bound together as a linked list. The mailbox control block is defined as follows:
typedef struct
aos_mail_box {
aos_scb *msend; /*
Semaphore for sending to mailbox */
aos_scb *mrecv; /*
Semaphore for receiving from mailbox */
void *mmsg; /*
Pointer to message */
struct
aos_mail_box *next_mcb; /* Pointer to next mailbox */
} aos_mcb;
Members of mailbox control block:
Member |
Type |
Description |
msend |
SCB pointer |
Semaphore to send a message to mailbox. |
mrecv |
SCB pointer |
Semaphore to receive a message from mailbox. |
mmsg |
Pointer |
Pointer to an actual message. |
next_mcb |
MCB pointer |
Pointer to next used mailbox control block. |
6.2.3 Mailbox creation
A mailbox is created with system function aos_mbox_create(). The function takes first free Mailbox Control Block (MCB), initialises its members and returns a pointer of MCB to task. The pointer is used as a “handle” when accessing the mailbox. If all MCBs are in use, a null pointer is returned. Tasks reading from and writing to mailbox should be aware of type and size of messages they are dealing with.
6.2.4 Mailbox operations
The mailboxes are accessed with system functions aos_mbox_send() and aos_mbox_recv(). Aos_mbox_send() sends a message to a mailbox unless there is no message already in the mailbox. If the mailbox is occupied, the calling task is suspended until the message is read. Aos_mbox_recv() receives a message and returns a pointer to message to calling task. If the mailbox is empty, the calling task is suspended until a message arrives.
Message queues behave like mailboxes. The major difference is that message queues are FIFO-type arrays and they are capable of holding numerous message-pointers simultaneously. Currently message queues are not implemented in aOS.
Hardware of AVR provides an individual interrupt vectors for each interrupt. In interrupt vector, there is a jump instruction to actual interrupt service routine (ISR). Hence there is no need for dedicated first level interrupt handler (FLIH) which would find out the source of an interrupt and then dispatch related ISR.
Convenient way to implement interrupt handling is to use two level of ISRs. In the first level there is the ISR vectored by hardware. In the second level, there is a high priority user level task, “device driver or second level interrupt handler”, which will do all the interrupt processing. The only thing that first level ISR has to do is to tell second level that an interrupt has occurred.
The communication between levels is achieved with a single semaphore. The second level ISR creates the semaphore with initial value of 0 and then calls aos_wait(). This causes task to sleep until an interrupt occurs and first level ISR signals the semaphore by calling aos_signal(). Now the second level ISR is waken up by operating system and it may begin to serve the interrupt.
The next step towards real device driver would be to add capabilities for other tasks to communicate with second level ISR. This might be implemented with mailboxes, message queues or even by implementing aos_read() and aos_write() system functions.
Timer0 has a special role since it is used as system “heartbeat” source. It provides an interrupt in 30 ms intervals. Timer0 ISR is responsible for waking up tasks which have called system function aos_sleep(). The ISR goes through TCB chain and decrements each positive delay counter. If task’s delay counter decrements to zero, the task is made runnable and re-scheduling occurs. Timer0 ISR also increments system uptime counter.
Description:
Create a new task within aOS.
Declaration:
aos_tcb *aos_task_create(
void(*task)(void), UBYTE *hwstk, UBYTE *swstk, UBYTE pri )
Parameters:
Return value:
On success, pointer to task’s TCB
On failure, null pointer is returned.
Description:
Put task to sleep for ‘timerticks’ timer ticks. A ‘tick’ is approx. 30 ms.
Declaration:
void aos_sleep( UWORD
timerticks )
Parameters:
Return value:
None
Description:
Get number of timer ticks since system was restarted.
Declaration:
ULONG aos_uptime( void )
Parameters:
None
Return value:
Number of timer ticks since last system restart.
Description:
Create a new semaphore.
Declaration:
aos_scb *aos_sem_create(
UWORD count )
Parameters:
Return value:
On success, a pointer to new semaphore.
On failure, a null pointer.
Description:
Issue a wait operation to semaphore. If the value of semaphore is 0, the calling task is suspended by putting it to semaphore’s wait queue.
Declaration:
void aos_wait( aos_scb
*semaphore )
Parameters:
Return value:
None
Description:
Issue a signal operation to semaphore. Increment the value of semaphore by 1. If there are tasks in semaphore’s wait queue, de-queue the oldest one.
Declaration:
void aos_signal( aos_scb
*semaphore )
Parameters:
Return value:
None
Description:
Create a new mailbox.
Declaration:
aos_mcb *aos_mbox_create(
void )
Parameters:
None
Return value:
On success, a pointer to new mailbox.
On failure, a null pointer.
Description:
Send a message to mailbox. If there is no room in the mailbox, the calling task is suspended until the message is read. The reading task should be aware of the type of message.
Declaration:
void aos_mbox_send(
aos_mcb *mailbox, void *message )
Parameters:
Return value:
None
Description:
Read a message from mailbox. If there the mailbox is empty, the calling task is suspended until a new message is sent to mailbox.
Declaration:
void *aos_mbox_recv(
aos_mcb *mailbox )
Parameters:
Return value:
Pointer to a message read from the mailbox.
Labrosse, Jean J., (1992), uC/OS The Real-Time Kernel
Lister, A.M., and Eager, R.D. (1988), Fundamentals of Operating Systems, Fourth Edition
Tanenbaum, Andrew S., (1987), Operating Systems: Design and Implementation
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
#define AOS_ENTER_CRITICAL #asm("cli")
#define
AOS_EXIT_CRITICAL #asm("sei")
#define
AOS_TASK_RUNNABLE 0b00000000
#define
AOS_TASK_RUNNING 0b00000001
#define
AOS_TASK_WAITING 0b00000010
#define
AOS_TASK_DELAYD 0b00000100
#define
AOS_SEM_MAX 12
#define AOS_TASK_MAX 6
#define
AOS_MBOX_MAX 5
#define
AOS_TASK_HSTK_SIZE 200
#define
AOS_TASK_DSTK_SIZE 200
typedef unsigned
char UBYTE;
typedef unsigned
int UWORD;
typedef unsigned
long int UDWORD;
typedef unsigned
long int ULONG;
typedef struct
aos_task_control_block {
void *hwstk; /*
Pointer to task's hardware stack */
void *datastk; /*
Pointer to task's data stack */
UBYTE status; /*
Status of task */
UBYTE prio; /*
Priority of task */
UWORD delay; /* Nbr of ticks to keep task sleeping */
Struct aos_task_control_block
*next_tcb; /* Pointer to next task's TCB
*/
Void *tcb_sem_q; /* Next task on semaphore waiting
list */
} aos_tcb;
typedef struct
aos_sema {
UWORD count; /* Semaphore value */
aos_tcb *scb_sem_q; /* Semaphore queue */
struct aos_sema *next_scb; /*
Pointer next semaphore's SCB */
} aos_scb;
typedef struct
aos_mail_box {
aos_scb *msend; /*
Semaphore for sending to mailbox */
aos_scb *mrecv; /*
Semaphore for receiving from mailbox */
void *mmsg; /* Pointer to message */
struct aos_mail_box *next_mcb; /* Pointer to next mailbox */
} aos_mcb;
typedef struct
aos_central_table {
ULONG aos_systime; /*
Timer ticks since last boot */
ULONG aos_idlecntr; /*
Counter for processor idle time */
UBYTE aos_version; /*
OS version */
UBYTE aos_running; /*
AOS running flag */
aos_tcb *tcb_used_list; /* Pointer to used TCBs */
aos_tcb *tcb_free_list; /* Pointer to TCB freelist */
aos_tcb *tcb_current; /* Pointer to current
running task TCB */
aos_tcb *tcb_runq; /* Pointer to highest
priority runnable task TCB */
aos_scb *scb_used_list;
/* Pointer to used semaphores */
aos_scb *scb_free_list;
/* Pointer to semaphores freelist */
aos_mcb *mcb_used_list; /* Pointer to used message mailboxes */
aos_mcb *mcb_free_list; /* Pointer to mailbox freelist */
} aos_cent_tbl;
/* Kernel
services and data-types that are visible to user tasks */
typedef aos_scb
aos_semaphore;
typedef aos_mcb
aos_mailbox;
typedef aos_tcb
aos_task;
extern aos_tcb
*aos_task_create( void (*task)(void), UBYTE *hstack, UBYTE *dstack, UBYTE p );
extern void
aos_sleep( UWORD ticks );
extern ULONG
aos_uptime( void );
extern aos_scb
*aos_sem_create( UWORD ival );
extern void
aos_wait( aos_scb *semaphore );
extern UBYTE
aos_signal( aos_scb *semaphore );
extern aos_mcb
*aos_mbox_create( void );
extern void
aos_mbox_send( aos_mcb *mbox, void *msg );
extern void *aos_mbox_recv( aos_mcb *mbox );
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
/*********************************************
Chip type : ATmega103L
Clock
frequency : 4,000000 MHz
Memory model : Small
Internal SRAM
size : 4000
External SRAM
size : 0
Data Stack
size : 32
*********************************************/
#include
<mega103.h>
#include
<stk300.h>
#include
<delay.h>
#include
<stdio.h>
#include
"aos.h"
#include
"user_tasks.h"
aos_cent_tbl
aos_ctbl; /* Central table */
aos_tcb
all_tcb[AOS_TASK_MAX]; /* Pointers to Task Control Blocks */
aos_scb all_scb[AOS_SEM_MAX]; /* Pointers to Semaphore
Control Blocks */
aos_mcb all_mcb[AOS_MBOX_MAX]; /* Pointers to Message
Queue Control Blocks */
aos_tcb
*NULL_TASK; /* Straight
pointer to null-process' PCB */
UBYTE
DNULL[AOS_TASK_DSTK_SIZE], HWNULL[AOS_TASK_HSTK_SIZE]; /* Stacks for NULL task */
UBYTE
data_stack1[AOS_TASK_DSTK_SIZE], hw_stack1[AOS_TASK_HSTK_SIZE]; /* Stacks for task1 */
UBYTE
data_stack2[AOS_TASK_DSTK_SIZE], hw_stack2[AOS_TASK_HSTK_SIZE]; /* Stacks for task2 */
UBYTE
data_stack3[AOS_TASK_DSTK_SIZE], hw_stack3[AOS_TASK_HSTK_SIZE]; /* Stacks for task3 */
UBYTE
data_stack4[AOS_TASK_DSTK_SIZE], hw_stack4[AOS_TASK_HSTK_SIZE]; /* Stacks for task4 */
/* Low level
function which makes actual context switch */
void
_aos_dispatch( void )
{
UBYTE *dstk_highrdy, *hstk_highrdy;
aos_tcb *curr_tcb;
AOS_ENTER_CRITICAL;
curr_tcb = aos_ctbl.tcb_current; /*
Get address to current TCB */
dstk_highrdy = aos_ctbl.tcb_runq->datastk; /* Get highest priority ready tasks data stack
*/
hstk_highrdy = aos_ctbl.tcb_runq->hwstk; /* Get highest priority ready task's hw stack */ aos_ctbl.tcb_current = aos_ctbl.tcb_runq; /* Set highest priority ready task current */
aos_ctbl.tcb_current = aos_ctbl.tcb_runq; /*
Make first in run-queue current */
aos_ctbl.tcb_current->status =
AOS_TASK_RUNNING;
/*
Context switch.
; *dstk_highrdy -> Y+4
; *hstk_highrdy -> Y+2
; *curr_tcb -> Y+0
1. Save HW SP and data SP to current task's
PCB
2. Load SP from highest priority task's TCB
3. Restore context and return to new task
*/
#asm
; Save all registers except SW stack
pointer
PUSH R0
PUSH R1
PUSH R2
PUSH R3
PUSH R4
PUSH R5
PUSH R6
PUSH R7
PUSH R8
PUSH R9
PUSH R10
PUSH R11
PUSH R12
PUSH R13
PUSH R14
PUSH R15
PUSH R16
PUSH R17
PUSH R18
PUSH R19
PUSH R20
PUSH R21
PUSH R22
PUSH R23
PUSH R24
PUSH R25
PUSH R26
PUSH R27
PUSH R30
PUSH R31
IN R0,SREG
PUSH R0
; Save current HW stack pointer, low first
LDD R26,Y+0 ;
hstk_current -> X
LDD R27,Y+1
IN R30,SPL ; *hstk_current = SP;
ST X+,R30
IN R30,SPH
ST X+,R30
; Save current SW stack pointer, low first
MOV R30,R28 ;
LOW(Y) -> R30
ADIW R30,6 ;
Restore Y value (3 pointers * 2 bytes each)
ST X+,R30 ; *dstk_current = Y;
ST X,R29
; Load new HW stack pointer, low first
LDD R30,Y+2
OUT SPL,R30
LDD R30,Y+3
OUT SPH,R30
; Load new SW stack pointer, low first
LDD R30,Y+4
LDD R31,Y+5
MOV R28,R30
MOV R29,R31
; Pop all registers except SW stack pointer
POP R0
OUT SREG,R0
POP R31
POP R30
POP R27
POP R26
POP R25
POP R24
POP R23
POP R22
POP R21
POP R20
POP R19
POP R18
POP R17
POP R16
POP R15
POP R14
POP R13
POP R12
POP R11
POP R10
POP R9
POP R8
POP R7
POP R6
POP R5
POP R4
POP R3
POP R2
POP R1
POP R0
RETI
#endasm
}
void
_aos_schedule( void )
{
aos_tcb *tptr, *highest_tcb;
UBYTE highest;
AOS_ENTER_CRITICAL;
highest = 255; /* NULL-task's priority */
NULL_TASK->status = AOS_TASK_RUNNABLE; /* NULL-task is always runnable */
if( aos_ctbl.tcb_current->status &
AOS_TASK_RUNNING ) /* If current task is running here */
aos_ctbl.tcb_current->status
= AOS_TASK_RUNNABLE; /* it is still
runnable */
tptr = (aos_tcb *)aos_ctbl.tcb_used_list; /* Go
through all runnable tasks */
do { /* and get the one having highest priority */
if( !tptr->status ) { /*
Is task runnable? */
if( tptr->prio <= highest ) { /* Yes, is it's priority higher than highest
allready? */
highest
= tptr->prio; /* Yes, save it */
highest_tcb
= (aos_tcb *)tptr;
}
}
tptr = (aos_tcb *)tptr->next_tcb;
}
while( tptr ); /*
Was it last one? */
aos_ctbl.tcb_runq = (aos_tcb *)highest_tcb; /* Put highest priority task in run-queue */
if( aos_ctbl.tcb_current != aos_ctbl.tcb_runq
) /* If next in run-queue is different
task */
_aos_dispatch(); /* we have to dispatch */
AOS_EXIT_CRITICAL;
}
#pragma savereg-
// Timer 0 output
compare interrupt service routine
/* Timer tick ISR
*/
interrupt
[TIM0_COMP] void timer0_comp_isr(void)
{
volatile aos_tcb *tptr;
volatile UBYTE sched_n_disp;
AOS_ENTER_CRITICAL;
if( aos_ctbl.aos_running ) { /*
Is multitasking on? */
sched_n_disp
= 0; /* Flag of need
for scheduling and dispatching */
tptr = (aos_tcb *)aos_ctbl.tcb_used_list; /*
Point to first TCB */
do { /* Go through all PCBs */
if(
tptr->delay ) { /* Delay for this task? */
tptr->delay--; /* Yes, decrement */
if(
!tptr->delay ) /*
Was it last tick? */
tptr->status
= AOS_TASK_RUNNABLE; /* Yes, make the task
runnable again */
sched_n_disp
= 1; /* We have to re-schedule */
}
tptr = (aos_tcb *)tptr->next_tcb; /*
Get next TCB */
}
while( tptr ); /* Last in PCB chain? */
aos_ctbl.aos_systime++; /* Increment system uptime */
if( sched_n_disp ) { /* If some task became runnable */
_aos_schedule(); /* we have to re-schedule */
}
}
AOS_EXIT_CRITICAL;
}
#pragma savereg+
void
_init_free_list( void )
{
UBYTE i;
aos_tcb *tcb_this, *tcb_next;
aos_scb *scb_this, *scb_next;
aos_mcb *mcb_this, *mcb_next;
AOS_ENTER_CRITICAL;
/* Process PCBs */
for(i=0;i<AOS_TASK_MAX-1;i++) { /*
Link all PCBs in free list */
tcb_this = (aos_tcb *)&all_tcb[i];
tcb_next = (aos_tcb *)&all_tcb[i+1];
tcb_this->next_tcb
= (aos_tcb *)tcb_next;
}
all_tcb[AOS_TASK_MAX].next_tcb = (aos_tcb
*)0; /* Last PCB in chain */
/* Process SCBs */
for(i=0;i<AOS_SEM_MAX-1;i++) { /*
Link all SCBs in free list */
scb_this = (aos_scb *)&all_scb[i];
scb_next = (aos_scb *)&all_scb[i+1];
scb_this->next_scb
= (aos_scb *)scb_next;
}
all_scb[AOS_SEM_MAX].next_scb = (aos_scb *)0; /* Last SCB in chain */
/* Process MCBs */
for(i=0;i<AOS_MBOX_MAX-1;i++) { /*
Link all MCBs in free list */
mcb_this = (aos_mcb *)&all_mcb[i];
mcb_next = (aos_mcb *)&all_mcb[i+1];
mcb_this->next_mcb
= (aos_mcb *)mcb_next;
}
all_mcb[AOS_MBOX_MAX].next_mcb = (aos_mcb
*)0; /* Last MCB in chain */
AOS_EXIT_CRITICAL;
}
void aos_start(
void )
{
if( aos_ctbl.tcb_used_list ) { /*
Is there tasks created? */
aos_ctbl.aos_running
= 1; /* Start multi-tasking */
}
}
void aos_sleep(
UWORD ticks )
{
AOS_ENTER_CRITICAL;
aos_ctbl.tcb_current->delay = ticks; /*
Put task to sleep for 'ticks' time. */
aos_ctbl.tcb_current->status =
AOS_TASK_DELAYD;
_aos_schedule();
AOS_EXIT_CRITICAL;
}
ULONG aos_uptime(
void )
{
return( aos_ctbl.aos_systime );
}
void NULLTASK( void )
{
UWORD i;
while( 1 ) {
aos_ctbl.aos_idlecntr++;
LED8 = ON;
for(i=0;i<20000;i++)
{};
LED8 = OFF;
for(i=0;i<20000;i++)
{};
}
}
void main(void)
{
UBYTE *nt_dstk,
*nt_hstk; /* NULL-task's stacks */
// Input/Output
Ports initialization
// Port A
PORTA=0x00;
DDRA=0x00;
// Port B
PORTB=0xFF;
DDRB=0xFF;
// Port D
PORTD=0xFF;
DDRD=0x00;
// Port E
PORTE=0x00;
DDRE=0x00;
// Timer/Counter
0 initialization
// Clock source:
System Clock
// Clock value:
3,906 kHz
// Mode: Output
Compare
// OC0 output:
Disconnected
// Timer/Counter
0 is cleared on compare match
TCCR0=0x0F;
ASSR=0x00;
// 3906 Hz =
0.256 ms -> 120 x 0.256 ms ~ 30 ms / timer tick
TCNT0=0x00;
OCR0=120;
// Timer/Counter
1 initialization
// Clock source:
System Clock
// Clock value:
Timer 1 Stopped
// Mode: Output
Compare
// OC1A output:
Discon.
// OC1B output:
Discon.
// Noise
Canceler: Off
// Input Capture
on Falling Edge
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer/Counter
2 initialization
// Clock source:
System Clock
// Clock value:
Timer 2 Stopped
// Mode: Output
Compare
// OC2 output:
Disconnected
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
// External
Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
// INT3: Off
// INT4: Off
// INT5: Off
// INT6: Off
// INT7: Off
EIMSK=0x00;
EICR=0x00;
//
Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x02;
// UART
initialization
// Communication
Parameters: 8 Data, 1 Stop, No Parity
// UART Receiver:
On
// UART
Transmitter: On
// UART Baud
rate: 9600
UCR=0x18;
UBRR=0x19;
// Analog
Comparator initialization
// Analog
Comparator: Off
// Analog Comparator
Input Capture by Timer/Counter 1: Off
ACSR=0x80;
/* Initialize PCB
& SCB freelist */
_init_free_list();
/* Set up AOS
central table */
aos_ctbl.aos_running
= 0; /* Not running */
aos_ctbl.tcb_used_list
= (aos_tcb *)0; /* No created processes yet */
aos_ctbl.tcb_free_list
= (aos_tcb *)&all_tcb[0]; /* First
TCB in free list */
aos_ctbl.scb_used_list
= (aos_scb *)0; /* No created semaphores yet */
aos_ctbl.scb_free_list
= (aos_scb *)&all_scb[0]; /* First
SCB in free list */
aos_ctbl.mcb_used_list
= (aos_mcb *)0; /* No created mailboxes yet */
aos_ctbl.mcb_free_list
= (aos_mcb *)&all_mcb[0]; /* First
MCB in free list */
/* Allways create
NULL task which is allways runnable */
/* and which has
the lowest priority */
NULL_TASK =
aos_task_create( NULLTASK, HWNULL, DNULL, 255 );
/* Use
NULL-task's stacks prior to first context switch */
nt_dstk = (UBYTE
*)NULL_TASK->datastk;
nt_hstk = (UBYTE
*)NULL_TASK->hwstk;
#asm
; Load new HW stack pointer, low first
LDD R30,Y+0
OUT SPL,R30
LDD R30,Y+1
OUT SPH,R30
; Load new data stack pointer, low first?
LDD R30,Y+2
LDD R31,Y+3
MOV R28,R30
MOV R29,R31
#endasm
aos_ctbl.tcb_current
= (aos_tcb *)NULL_TASK;
/***********************************************/
/* */
/* THE FIRST USER
TASK IS CREATED HERE. */
/* Modify
according to your needs. */
/* */
/***********************************************/
aos_task_create(
task1, hw_stack1, data_stack1, 50 );
PORTB=0xFF;
/* Start
multitasking */
aos_start();
_aos_schedule();
/* This causes
program to jump actual NULL process */
#asm
POP R0
OUT SREG,R0
POP R31
POP R30
POP R27
POP R26
POP R25
POP R24
POP R23
POP R22
POP R21
POP R20
POP R19
POP R18
POP R17
POP R16
POP R15
POP R14
POP R13
POP R12
POP R11
POP R10
POP R9
POP R8
POP R7
POP R6
POP R5
POP R4
POP R3
POP R2
POP R1
POP R0
RETI
#endasm
while (1)
{
/* We won't never, ever reach this point */
#asm("nop");
};
}
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
#include
"aos.h"
aos_tcb
*aos_task_create( void (*task)(void), UBYTE *hstack, UBYTE *dstack, UBYTE p )
{
UBYTE *stk, *dstk, *hstk;
UBYTE i;
aos_tcb *tcb_new;
AOS_ENTER_CRITICAL;
tcb_new = (aos_tcb *)aos_ctbl.tcb_free_list; /*
Get next free TCB */
if( tcb_new ) { /* TCBs left? */
/* Set up the hardware stack */
hstk = (UBYTE *)hstack +
(AOS_TASK_HSTK_SIZE - 1); /* Stack
begins in the end of array */
stk = hstk;
*stk-- = (UBYTE)(task); /*
Low part of task's address */
*stk-- = (UBYTE)(task >> 8); /*
High part of task's address */
for(i=0;i<30;i++) /*
Clear all other registers */
*stk-- = 0;
*stk-- = 0x80; /* SREG with interrupts enabled */
/* Set up data stack */
dstk = (UBYTE *)dstack +
(AOS_TASK_DSTK_SIZE - 1); /* Stack
begins in the end of array */
aos_ctbl.tcb_free_list
= (aos_tcb *)tcb_new->next_tcb; /*
Release TCB from freelist */
tcb_new->next_tcb = (aos_tcb
*)aos_ctbl.tcb_used_list; /* Link new
TCB into TCB used list */
aos_ctbl.tcb_used_list
= (aos_tcb *)tcb_new;
tcb_new->datastk = (UBYTE *)dstk; /*
Load data stack pointer to TCB */
tcb_new->hwstk = (UBYTE *)stk; /*
Load hw stack pointer to TCB */
tcb_new->status = AOS_TASK_RUNNABLE; /* Make task runnable */
tcb_new->prio = (UBYTE)p; /* Set task's priority */
tcb_new->delay = (UWORD)0; /* No initial delay */
tcb_new->tcb_sem_q = (aos_tcb *)0; /*
Initialize semaphore queue */
}
AOS_EXIT_CRITICAL;
return( tcb_new );
}
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
aos_scb
*aos_sem_create( UWORD init_val )
{
aos_scb *scb_new;
AOS_ENTER_CRITICAL;
scb_new = (aos_scb *)aos_ctbl.scb_free_list; /* Get next free SCB */
if( scb_new ) { /*
SCBs left? */
aos_ctbl.scb_free_list
= (aos_scb *)scb_new->next_scb; /*
Release SCB from freelist */
scb_new->next_scb = (aos_scb
*)aos_ctbl.scb_used_list; /* Link new
SCB into SCB used list */
aos_ctbl.scb_used_list
= (aos_scb *)scb_new;
scb_new->count = init_val; /* Set semaphore's initial value */
scb_new->scb_sem_q = (aos_tcb *)0; /* No task pending on semaphore */
}
AOS_EXIT_CRITICAL;
return( scb_new );
}
/*
About wait(), signal() and HW locking:
Since we are in uniprocessor environment,
indivisibility is
achieved by simply disabling interrupts.
After that there is
no way for task to loose the processor.
Otherwise somekind of
hardware locking would be needed. In
multi-processor environments
possibilities
are 'test-and-set'-instruction or instruction to
interchange
two memory locations.
*/
void aos_wait(
aos_scb *sem )
{
aos_tcb *ptcb;
AOS_ENTER_CRITICAL;
if( sem->count ) { /* Semaphore value >0 ? */
sem->count--; /*
Yes, decrement the value */
}
else { /* No, we have to wait */
ptcb = (aos_tcb *)sem->scb_sem_q;
if( ptcb ) { /*
Any tasks in queue? */
while(
ptcb->tcb_sem_q ) { /* Yes, find queue's end because */
ptcb
= (aos_tcb *)ptcb->tcb_sem_q; /* this
is a FIFO, not a stack. */
}
ptcb->tcb_sem_q = (aos_tcb
*)aos_ctbl.tcb_current; /* Link current
process to wait queue */
}
else {
sem->scb_sem_q = (aos_tcb
*)aos_ctbl.tcb_current; /* No, this is the
first one */
}
aos_ctbl.tcb_current->tcb_sem_q
= (aos_tcb *)0; /* Terminate queue properly */
aos_ctbl.tcb_current->status
= AOS_TASK_WAITING; /* Now we're
waiting */
_aos_schedule(); /* Re-schedule run-queue */
}
AOS_EXIT_CRITICAL;
}
UBYTE aos_signal(
aos_scb *sem )
{
aos_tcb *ptcb;
AOS_ENTER_CRITICAL;
ptcb = (aos_tcb *)sem->scb_sem_q;
if( ptcb ) { /*
Is there pending wait()s? */
sem->scb_sem_q = (aos_tcb
*)ptcb->tcb_sem_q; /* Yes, dequeue the first one */
ptcb->status = AOS_TASK_RUNNABLE; /*
Make task runnable */
_aos_schedule(); /* Re-schedule run-queue */
}
else { /* No, just increment the semaphore */
sem->count++;
}
AOS_EXIT_CRITICAL;
}
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
#include
"aos.h"
/* Create and
initialize a mailbox */
aos_mcb *aos_mbox_create(
void )
{
aos_mcb *mcb_new;
mcb_new = 0;
AOS_ENTER_CRITICAL;
mcb_new = (aos_mcb *)aos_ctbl.mcb_free_list; /*
Save pointer to next free mcb */
if( mcb_new ) { /*
mcbs left? */
aos_ctbl.mcb_free_list
= (aos_mcb *)mcb_new->next_mcb; /* Yes,
release mcb from freelist */
mcb_new->next_mcb = (aos_mcb
*)aos_ctbl.mcb_used_list; /* Link new
mcb into mcb used list */
aos_ctbl.mcb_used_list
= (aos_mcb *)mcb_new;
mcb_new->mrecv = (aos_scb
*)aos_sem_create( 0 ); /* Semaphore for
receiving messages */
mcb_new->msend = (aos_scb
*)aos_sem_create( 1 ); /* Semaphore for
sending messages */
mcb_new->mmsg = (void *)0; /* Clear message pointer */
}
AOS_EXIT_CRITICAL;
return( mcb_new );
}
/* Send a message
to mailbox */
void
aos_mbox_send( aos_mcb *mcb, void *msg )
{
aos_wait( (aos_scb *)mcb->msend ); /* Is there room in the mailbox */
mcb->mmsg = msg; /* Put
message in the mailbox */
aos_signal( (aos_scb *)mcb->mrecv ); /* Signal the queue's receive semaphore */
}
/* Receive a
message from mailbox */
void
*aos_mbox_recv( aos_mcb *mcb )
{
void *msg;
aos_wait( (aos_scb *)mcb->mrecv ); /* Wait for a message */
msg = mcb->mmsg; /* Get the message from mailbox */
aos_signal( (aos_scb *)mcb->msend ); /* Signal the queue's sending semaphore */
return( msg );
}
/*************************************************************************
This file is part of aOS.
aOS is free software; 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.
aOS 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.
You should have received a copy of the GNU General
Public License
along with aOS; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
Copyright 2001, 2002
Anssi Ylätalo
**************************************************************************/
#include
"aos.h"
#include
<stdlib.h>
#include
<string.h>
aos_semaphore
*sem_portb; /* Semaphore for accessing PORTB */
aos_mailbox
*pipe; /* Mailbox for message passing */
UBYTE foomsg[20]; /* Buffer for messages */
void task1( void
)
{
sem_portb = (aos_semaphore *)aos_sem_create(
1 ); /*
Mutex for writing PORTB */
aos_task_create( task2, hw_stack2,
data_stack2, 100 ); /* Create task2 */
printf("Task1: %x\r",
aos_ctbl.tcb_current );
while(1)
{
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB &= ~0x01; /* Put on LED1 */
aos_signal( sem_portb ); /* Release PORTB */
aos_sleep( 2 ); /* Suspend for 2 timer
ticks */
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB |= 0x01; /* Put off LED1 */
aos_signal( sem_portb ); /* Release PORTB */
aos_sleep( 2 ); /* Suspend for 2 timer
ticks */
}
}
void task2( void
)
{
ULONG
time; /* Uptime counter */
aos_task_create( task3, hw_stack3,
data_stack3, 150 ); /* Create task2 */
pipe = aos_mbox_create(); /* Create a mailbox */
printf("Task2: %x\r", aos_ctbl.tcb_current
);
while(1)
{
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB &= ~0x02; /* Put on LED2
*/
aos_signal( sem_portb ); /*
Release PORTB */
aos_sleep( 33 ); /* Suspend for 33
timer ticks */
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB |= 0x02; /* Put off LED2 */
aos_signal( sem_portb ); /*
Release PORTB */
aos_sleep( 33 ); /* Suspend for 33
timer ticks */
time = aos_uptime(); /* Get system uptime
*/
sprintf( foomsg, "%u ", time ); /* Print formatted time to buffer */
aos_mbox_send( pipe, foomsg ); /* Send buffer to mailbox */
}
}
void task3( void
)
{
UBYTE *msg;
aos_task_create( task4, hw_stack4,
data_stack4, 220 ); /* Create task4 */
printf("Task3: %x\r",
aos_ctbl.tcb_current );
while(1)
{
msg = aos_mbox_recv( pipe ); /*
Receive a message from mailbox */
puts( msg ); /* Print the
message */
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB &= ~0x04; /* Put on LED3
*/
aos_signal( sem_portb ); /*
Release PORTB */
aos_sleep( 16 ); /* Suspend for 16
timer ticks */
aos_wait(
sem_portb ); /* Wait for access to PORTB */
PORTB |= 0x04; /* Put off LED3 */
aos_signal( sem_portb ); /*
Release PORTB */
aos_sleep( 16 ); /* Suspend for 16
timer ticks */
}
}
void task4( void
)
{
UWORD i;
ULONG j;
UBYTE mask;
printf("Task4: %x\r",
aos_ctbl.tcb_current );
mask = 0x08; /* Start with bitmap 00001000 */
j = 0;
while(1)
{
PORTB &= ~mask; /* Put on some LED
*/
for(i=0; i<50000;i++) {} /* Some delay. Please notice that
task is not suspended */
PORTB |= mask; /* Put off LED */
for(i=0; i<50000;i++) {} /* Delay again */
mask <<= 1; /* Shift bitmap
1 bit left */
if( mask & 0x80 ) /* Did we
overflow? */
mask = 0x08; /* Yes, start from beginning */
if( j++ == 20 ) { /* Do something in
every 20th round */
aos_sleep(
10 ); /* In this case we suspend for 10 ticks */
j = 0;
}
}
}