Вы находитесь на странице: 1из 30

BUTSCHER Morgane DANIAUD Vivien DESHAYES Raphael MORARD Jean-Sverin

Multitasking on AT91SAM7X microcontroller

Table of contents:
Introduction 1. Specifications 2. Fundamental concepts
2.1. Generalities on multitasking 2.2. Stacks

2 2 3
3 3

3. RAM and stacks management


3.1. RAM structure 3.2. Stack features

4
4 4

4. User part 5. Interrupt sources


5.1. Three possibilities of periodic interrupt 5.2. Configuration of the Timer Counter 0 5.3. Interrupt generation 5.4. Behavior of AIC and TC0 during an interrupt

7 9
9 10 10 11

6. Interrupt handling
6.1. Some useful registers 6.2. Context save

12
12 13

7. Work on the system stack


7.1. Different stacks 7.2. Tasks context management

16
16 16

8. Example of use
8.1. Simple program 8.2. Use programs in multitasking

21
21 22

Conclusion Appendix: Source code

22 23

Introduction:
The project which is presented in this report is a description of a multitasking environment in an AT91SAM7X microcontroller. The main concepts of multitasking structure are task scheduling and stack management. This structure has to manage tasks in order to execute them together. The presentation is not only aimed at users, who want to use the multitasking application, but also contains the description of how the system and functions are build.

1. Specifications:
The task number that multitasking handle is to be defined by the user The use of a real time clock not required This project is aimed at providing a tool for second-year students to be used during practical work. They will therefore be able to compare multitasking and interrupts.

2. Fundamental concepts:
In a first part some fundamental concepts: how multitasking works, what a scheduler and a context are and how a stack works, are explained.

2.1. Generalities on multitasking:


The multitasking application goal is to run several tasks together. However, in most cases, only one CPU (Central Processing Unit) is available, and it means that only one task can be executed at a time. That is why, in multitasking, the CPU executes a part of a task, then a part of another one, then another one... then the first one again...etc. And it does it so rapidly that the user feels that all the tasks are running together (see figure 1). The scheduler is the conductor: for example, it decides which task has to be run. But, before switching from a task to another one, all the data used by the active task have to be solved and all the data used by the next task have to be loaded. That is why all these data are stored in a special memory called the stack. Finally, the data used by a task are called the task context.
Figure 1 - Multitasking

2.2. Stacks:
Let us move onto how a stack works. On figure 2, there is a stack (in the middle) and some data blocks on the left (a blue one, a red one a green one). To be stored in the stack, the data blocks are piled up in the stack, and to load the data blocks, they are removed backwards. The arrows identify two levels called the stack top (also called the stack pointer) and the stack bottom.

Figure 2 - Stack operation

When the microprocessor is taken, multitasking was not available by default. So stacks must be created in order to store the context of each task.

3. RAM and stacks management:


3.1. RAM structure:
In the Random Access Memory, a stack table composed of N stacks is created. Note that N is defined as NB_TASKS in the header file (Multi_task.h). It is the maximum number of tasks the user can execute in parallel and it must be less than 11 (see the specifications). Obviously, the number of tasks the user really wants to execute can be less than or equal to N. Each stack of the stacks table contains the context of one task. We assigned DATA_RAM bytes to the stacks table which means that STACK_SIZE bytes were assigned to each stack (where STACK_SIZE equals DATA_RAM / N). See below in figure 3 the diagram representing the stacks table in the RAM on one side, and a zoom in on the other side. Addstart and Addend are respectively the stacks_table start address and the stacks_table end address, whereas add1start and add1end are respectively the stack_1 start address and the stack_1 end address. These addresses are used by the designer in order to read and write in the stacks.

Figure 3 - RAM and stacks

3.2. Stack features:


The issue raised is how this stack table is managed. In order to manage each stack correctly, we created another table composed of N structures. Each structure contains important elements to handle a task, that is to say: num The task number (0 if this is task number 0, 1 if this is task number 1... n if this is task number n) ; state The task state (unused, sleeping, active or starting see below) ; add_fct The name of the C programming function executed by this task ; stack_bottom The address of the corresponding stack bottom ; stack_top The stack pointer to read and write in the corresponding stack (the stack top) ;

stack_limit

The limit address delineating the STACK_SIZE bytes allocated to this task ;

In figure 4, you can see a diagram summarizing the links between these two tables.

Figure 4 - Link between the stack table and task tables

The next part deals with a another task structure parameter: state. In a common working of multitasking, each task goes through different states depending on its life cycle. That is why there are four states:

UNUSED

(STARTING)

ACTIVE SLEEPING

When the task is not used. The first case is if the scheduler is not running and the task not launched. The second case is if the scheduler is running but the task not launched or over ; This state is important because of the interrupt working. It will be detailed why below. For the moment, just note that this state is not visible from the user point of view. It is just a transitional state between the UNUSED state and the ACTIVE state ; When the task is initialized, ready and running. It is the state following the UNUSED state or alternately with the SLEEPING state ; When the execution of the task must be stopped during a moment (to wait for data for instance). Typically, it is the user who sparks off this state.

In order to know continuously the state of each task, the task state are store in four tables: unused[], starting[], active[] and sleeping[]. One more time, these tables or all composed by N squares. We just deal with the first n squares, the other ones being unused. The working of every table is similar. Lets take the example of unused[] : the first square corresponds to task number zero, the second square to task number one... A 1 is stored in the first square if the task number zero is in an unused state. A 0 is stored if it is not. Figure 5 shows an example of this situation.

NB_TASK

used tasks

unused[]

1 0 0 0

0 1 0 0

0 1 0 0

0 0 1 0

0 1 0 0

0 0 0 1

1 0 0 0

0 1 0 0

1 0 0 0

1 0

starting[]

active[]

0 0 -

sleeping[]

Tasks 0 and 6 are unused (not launched or over) Tasks 8 are 9 are unused (not used by the user) Tasks 1, 2, 4 and 7 are starting Task 3 is active Task 5 is sleeping

Figure 5 - Example of the different state tables

4. User part:
This part is about the user space, that is to say all the functions he can execute in order to run this program.

int Init_Struct ()

This function initializes the data structure to implement N parallel tasks. Then, it is up to the user to use fewer tasks if he wants to. Each structure of the tasks table is initialized in the same way. Lets take the standard example of task number i: we put i in num, UNUSED in state, the name of the associated C function in add_fct and the corresponding addresses of tasks_RAM in stack_bottom, stack_top and stack_limit. As for the tables unused[], starting[], active[] and sleeping[], we feel unused[] with ones and the other tables with zeros. 0 E_RUNNING_SCHED

Init_Struct Input : Output :

No input 0 if successful E_RUNNING_SCHED if the scheduler is already started

int Create_Task (void (*function)(void))

This function allows the user to add a task provided that there are less than N tasks created and that the scheduler is not already running. It just changes the parameter of the next free task_t square of the table tasks by fixing add_fct to the input parameter function. To do that, the data structure must already have been called but the scheduler must not be running. 0 E_RUNNING_SCHED E_NO_SPACE_LEFT E_NO_INIT

function

Create_Task

Input : Output :

The C function associated to this task 0 if successful E_RUNNING_SCHED if the scheduler is already started E_NO_SPACE_LEFT if all the N tasks have already been created E_NO_INIT if the data structure is not yet initialized

int Launch_Task (int n)

This function allows starting a task. Thus, the user can control the moment he wants to start a task and is not obliged to start all the tasks at the scheduler setting up. It replaces the parameter state of the corresponding task_t to STARTING. 0 E_UNINIT_TASK E_RUNNING_TASK

Launch_Task

Input : Output :

The number of the task we want to launch 0 if successful E_UNINIT_TASK if the task is not initialized E_RUNNING_TASK if the task is already launched

int Start_Scheduler ()

This function starts the scheduler. It is from this moment that the launched tasks are executed, so that the program runs. It defines the interrupt function. 0 E_NO_INIT E_NO_FUNCTION

Start_Scheduler Input : Output :

No input 0 if successful E_NO_INIT if the data structure is not initialized E_NO_FUNCTION if no tasks are initialized

int Scotch_Task (int n)

This function allows the user to stop the execution of a task by fixing his state to SLEEPING. It changes the parameter state to SLEEPING provided that the task is already running and not sleeping. 0 E_UNINIT_TASK E_SLEEPING_TASK

Scotch_Task

Input : Output :

The number of the task we want to stop 0 if successful E_UNINIT_TASK if the task is not initialized E_SLEEPING_TASK if the task is already sleeping

int Wake_Task (int n)

This function allows the user to wake a sleeping task by changing his state to ACTIVE. The task must be already initialized and sleeping. n Input : Output : Wake_Task 0 E_UNINIT_TASK E_ACTIVE_TASK

The number of the task we want to stop 0 if successful E_UNINIT_TASK if the task is not initialized E_ACTIVE_TASK if the task is already running

The figure 6 is a use example of these functions :

Figure 6 - Use example of these functions

5. Interrupt sources:
5.1. Three possibilities of periodic interrupt:
In a multi tasks platform, an interrupt is necessary to plan a schedule for each task. When an interrupt occurs, the scheduler has to change the running task. In this project, a periodic interrupt is necessary; they have to be generated by the controller. In the AT91SAM7X microcontroller, there are 3 kinds of periodic interrupt: The PIT is inside the System Controller, it is used to generate periodic interrupt. This component is designed to offer maximum accuracy and efficient management, even for systems with long response time. The PIT has a 20 bits counter with a clock at MCK/16. Three Timer Counters are external peripherals. Timer Counter can be used to generate interrupt or a special clock. They have a lot of option: to control the input clock and registers values. A Timer Counter has a 12 bits counter with 5 possible input clock (MCK/2, MCK/8, MCK/32, MCK/128 and MCK/1024). The Real-time Timer is built around a 32-bit counter and used to count elapsed seconds. It generates a periodic interrupt. It is inside the System Controller too.

Figure 7 - AT91SAM7X architecture

The comparison of this three interrupt generators give us a crucial point: The PIT and the RTT use the System Controller vector. In our case if we use this kind of interrupt, users of the multi tasks will not be allowed to use something in the System Controller, because it will cause confrontation between each declaration. Regarding this, we focused on the first Timer Counter (TC0). When an interrupt from this timer will occur, the scheduler has to change the active task.

5.2. Configuration of the Timer Counter 0:


The configuration of the TC0 is determinate when a user start the scheduler (function Start_Scheduler() with source code in page 26). The beginning of this configuration active the PMC of the TC0, it power on the timer. Then we configure the AIC (Interrupt Controller) for the TC0 with a low priority. In the AIC we put the name of the interrupt function: ITSched(). The TC0 can use one of 5 clocks, we choose the slowest: MCK/1024. It corresponds to a frequency of 0.2 MHz (199691 Hz). The register C stores the maximum of what the counter can reach: 2 000. We want an interrupt each 10 ms: 2000 = 10 199691

5.3. Interrupt generation:


Each period, the selected clock increments the 16-bits counter value. Each increment, this value is compared to the register C. When values are equal, an interrupt is generated.

Figure 8 - Interrupt generation

10

5.4. Behavior of AIC and TC0 during an interrupt:


During an interrupt (function ITSched(), source code in page 27), we have to change the stack pointer (sp) value. Thats why all interrupt sources has to be avoid because it can cause some troubles and modify system register values. Before disabling all interrupt, the value of active interrupt sources must be save. Another action must be made in the interrupt: we have to read the TC0 status register to empty the timer counter interrupt vector.

11

6. Interrupt handling:
6.1. Some useful registers:
In the project, two different stacks are used: the IRQ stack and the Supervisor stack. They only are two stacks among the six existing. On the figure below, the six registers are displayed and the user stacks shows the different register succession.

Figure 9: Register organization

There are one dedicated program counter ( PC ), one dedicated current program status register ( CPSR ), 5 dedicated saved program registers ( SPSR ) and 30 general purpose registers. However these are arranged into several banks, with the accessible bank being governed by the processor mode. Each mode can access: - a particular set of r0-r12 registers - a particular r13 ( the stack pointer ) and r14 ( link register ) which is used as the subroutine link register and stores the return address when Branch with Link operations are performed. Thus, to return from a branch the instruction below is used: MOV pc , lr. - r15 ( the program counter ). Depending on the details of the particular computer, the PC holds either the address of the instruction being executed, or the address of the next instruction to be executed. - cpsr ( the current program status register) . It contains the current value of the condition code bits ( N, Z, C and V) and 8 system status bits. and privileged modes can also access - a particular spsr ( saved program status register ) which stores the current value of CPSR when an exception occurs, so that it can be restored after handling the exception.

Figure 10: Program Status Register

12

There are : Condition Code Flag which are copies from the ALU status flags Mode Bits , M[4:0] define the processor mode Interrupt Disable bits : I=1, disables the IRQ and F=1, disables the FIQ

6.2. Context save:


The first step of interrupt handling consists in preparing and saving their content in the IRQ stack. Once these registers are freed and ready to be used, the interrupt to be launched is loaded. The figure below shows the different program stages. First of all, the link register doesn't hold the right return address as the program counter is already fetching the next instruction from memory. This address is therefore adjusted. It is also stored in the IRQ stack. SPSR which contains the current value of CPSR has to be saved in the task using a work register : r14 which was just freed. R0 will be used to save other registers and is therefore saved in the IRQ stack.

Figure 11: Context save sequence

The next step consists in indicating which interrupt has to be launched. The address of the latter is loaded. The entry point of the interrupt handling is set in r14. Subsequently the interrupt is enabled by switching in Supervisor Mode. Then the scratch/used registers and lr are saved in svc stack. Finally the interrupt function is launched by branching to the address holds by r0.

13

Loads interrupt address

Enables Interrupt and switches in Supervisor Mode Saves registers and LR in SVC stack

Stores pc in r14

Executes r0

Figure 12: Context save sequence

The figure below shows the context save sequence. Once the interrupt function ends, the registers are restored from supervisor stack and interrupts disabled. The mode chosen is the IRQ mode. EOICR means End of Interrupt Command Register and is the exit point of the interrupt handling. The AIC has to know when the current interrupt handling is over. This is done by writing in AIC_EOICR. The AIC restores the previous interrupt level if there was an ongoing one. An idle interrupt can then be handled. In our case, this ends the interrupt. All the registers stored in the IRQ stack can be restored: r14, SPSR and r0. The last instruction return to the program running before the interrupt occurred.
Restores registers and LR from SVC Stack

Disables Interrupt and switches back in IRQ Mode

End of interrupt on the AIC

Restores SPSR and r0 from IRQ stack

Restores LR_irq from IRQ stack in PC

Figure 13: Context restoration sequence

14

SVC MODE

IRQ MODE

INTERRUPT

CPSR

Return address calculated from SVC mode PC value and stored in IRQ mode LR
SPSR_IRQ

CPSR SPSR_IRQ

Use SVC mode CPSR copied to IRQ mode SPSR


Figure 14: Exception and registers

The figure above sums up the different steps concerning the registers during an interrupt.

However, this whole sequence only handles interrupts but not multitasking. That is why a work had to be done on the stacks.

15

7. Work on the system stack:


7.1. Different stacks:
In the AT91SAM7X, six different tasks are used: The user stack which is use for the normal program execution state or for the operating system. The supervisor stack which is in a protected mode for the operating system, it is used during an interrupt. The third is abort mode stack which implements virtual memory and memory protection. The undefined mode stack design for supporting software emulation of hardware coprocessors. The IRQ stack which use for classical interrupt. The FIQ stack used when interrupt are chosen as fast interrupt.

In our case tasks are managed with an interrupt, that is why only two stacks are useful, the IRQ stack and the supervisor stack. In fact when an interrupt occurs the stack is automatically set in the IRQ mode then turn to the supervisor mode as it can be seen in the assembler file.

7.2. Tasks context management:


In order to save the entire context in the supervisor stack, the IRQ stack must be copied each time a scheduler interrupt occurs. Before change the task context, three registers (R0, lr, pc) which are saved in the IRQ stack, have to be stored in the Supervisor stack. Conversely, before the end of an interrupt, these registers which are in the supervisor stack must be copied in the IRQ stack.

7.2.1. Scheduler running:


First the scheduler has two main aims. It starts tasks which are created (Create_Task) and ready to be launching (Launch_Task). The second goal is to switch between tasks which are running.

Figure 15: Interrupt sequence

16

In this figure the two main steps can be seen. When task are starting, the IRQ stack increases. In fact during the scheduler interrupt a task is launched so the end of the interrupt does not occur consequently the IRQ stack does not fulfil. During the second step, tasks are running but the interrupt change the task but this time the end of interrupt occurs so the IRQ stack decreases at the end of the interrupt.

7.2.2. Context management:


As explained previously there is two working stack. Each time an interrupt occur all these two stacks are fulfilled. In order to manage these two stacks the assembly language is used because we need a low level programming language to operate action on registers. In fact when a scheduler interrupt occurs the stack pointer needs to be modify to change the context for the next task. Moreover the three registers saved in the IRQ stack must be copied into the supervisor stack before changing the context.

stack pointer

stack pointer

17

stack pointer

stack pointer

These figures show the three steps when a task runs for the first time and scheduler interrupt occurs. The two middle figures show the save of the context. The last one show when the copy of the IRQ stack is performed. Then the stack pointer is changed to the next task stack.

18

stack pointer

The new task can be launched. The same actions occur and the new context is created.

stack pointer

In this example only two tasks has been initialized so the stack pointer is not set to a free task but at the top of the task1 stack. Before launching task1 IRQ stack has to be change, so the task one irq stack is copied into the IRQ stack.

19

stack pointer

Then the task1 is launching. This operation is done at each stack change.

20

8. Example of use:
8.1. Simple program:
Two simple led function:
void led1 ( void ) { unsigned int waiting_time ; int flag =0; int i; while(1) { for(waiting_time = 0; waiting_time < LedSpeed;waiting_time++) ; if(flag == 0) { flag = 1; BASE_PIO_LED->PIO_SODR = LED1 ; } else if(flag == 1) { flag = 0; BASE_PIO_LED->PIO_CODR = LED1; } } } void led2 ( void ) { unsigned int waiting_time ; int flag =0; int i; while(1) { for(waiting_time = 0; waiting_time < LedSpeed;waiting_time++) ; if(flag == 0) { flag = 1; BASE_PIO_LED->PIO_SODR = LED1 ; } else if(flag == 1) { flag = 0; BASE_PIO_LED->PIO_CODR = LED1; } } }

21

8.2. Use programs in multitasking:


The first step is to create a variable to store the number of the task, and the task structure must be initializing. Then the task has to be created. Next tasks have to be launch. To finish the scheduler is launching.

int main() { Int task[2]; Init_Struct(2); Task[0] = Create_Task(led1); Task[1] = Create_Task(led2); Launch_Task(task[0]); Launch_Task(task[1]); Start_Scheduler(); // infinite loop for (;;) { } }

Conclusion:
The multitasking environment is now operational, some options have been added since the beginning of the project. It is now possible to finish a task and to start and restart tasks. But some correction must be added: there is a leak of memory in the IRQ stack. Another drawback is that a minimum of one task must be active when an interrupt occurs. The next step to lead an operating system is the management of shared resources (memory, pins ) between tasks. Semaphores can be added to conduct on this way.

22

Appendix: source codes multitask.h


1 /* 2 filename : multitask.h 3 multitask environnement for AT91SAM7X controller 4 ENSEIRB-MATMECA 5 6 7 Not operationnal 8 version date who from 9 0.0 2010-11-12 (yb) from scratch 10 0.1 2010-11-26 jsm from 0.0 11 0.2 2010-12-09 jsm from 0.1 12 final 2010-01-26 13 */ 14 15 // Constants 16 #define NB_TASKS 10 // number of tasks to be managed (less than 11) 17 18 19 // Error Messages 20 #define E_RUNNING_SCHED -1 21 #define E_NO_SPACE_LEFT -2 22 #define E_UNINIT_TASK -10 23 #define E_ACTIVE_TASK -11 24 #define E_SLEEPING_TASK -12 25 26 27 extern int Init_Struct(int n); 28 // initializes data structures to implement n parrallel tasks 29 // must be the first function called. 30 // cannot be called from a task 31 // erases all previous data when called 32 // error messages 33 // -1 : called after scheduler start (E_RUNNING_SCHED) 34 35 int Create_Task(void (*task_func)(void) ); 36 // initializes a task with the function given in argument 37 // returns the id number of the new task (positive value) or an error (negative value) 38 // the function given in argument must not return 39 // error messages : 40 // -1 : called after scheduler start (E_RUNNING_SCHED) 41 // -2 : no room for new task (E_NO_SPACE_LEFT) 42 43 extern int Start_Scheduler(); 44 // actually starts the scheduler 45 // 0 means success 46 // error messages 47 // -1 : called after scheduler start (E_RUNNING_SCHED) 48 49 50 extern int Wake_Task(int n); 51 // moves a task from sleeping to active state 52 // 0 means success 53 // errors : 54 // -10 : Task Not initialized (E_UNINIT_TASK) 55 // -11 : Active Task (E_ACTIVE_TASK) 56 57 extern int Scotch_Task(int n); 58 // moves a task from active to sleeping state 59 // 0 means success 60 // errors : 61 // -10 : Task Not initialized (E_UNINIT_TASK) 62 // -12 : Sleeping Task (E_SLEEPING_TASK) 63 int Launch_Task(int task_number);

23

64 // Relunch an existing unused task with an reinitializing a its own stack 65 // Return 0 if succes 66 // errors: 67 // -14 : Task doesn't exist (E_TASK_NO_EXISTINGG) 68 // -15 : Task not in unused state(E_TASK_RUNNING) 69

multitask.c
1 /* 2 filename : multitask.c 3 multitask environnement for AT91SAM7X controller 4 ENSEIRB-MATMECA 5 6 7 operationnal 8 version date who from 9 0.0 2010-11-12 (yb) from scratch 10 0.1 2010-11-19 rd from 0.0 11 0.2 2010-11-21 jsm from 0.1 12 0.3 2010-11-26 jsm from 0.2 13 0.4 2010-11-26 yb from 0.3 14 0.5 2010-12-09 rd from 0.4 15 0.6 2010-12-10 all from 0.5 16 final 2010-01-26 17 */ 18 19 //Include Standard files 20 #include <stdlib.h> 21 #include "include/AT91SAM7X-EK.h" 22 23 #include "Multi_task.h" 24 25 // task state definitions 26 #define UNUSED 0 27 #define SLEEPING 1 28 #define ACTIVE 2 29 #define STARTING 3 30 31 // Error Messages 32 #define E_RUNNING_SCHED -1 33 #define E_NO_SPACE_LEFT -2 34 #define E_NO_INIT -3 35 #define E_NO_FUNCTION -4 36 #define E_UNINIT_TASK -10 37 #define E_ACTIVE_TASK -11 38 #define E_SLEEPING_TASK -12 39 #define E_TASK_NO_EXISTING -14 40 #define E_TASK_RUNNING -15 41 42 // Constants 43 #define DATA_RAM 32000 // size of RAM used for tasks in bytes 44 #define TASK_SIZE (DATA_RAM/4)/NB_TASKS // amount of RAM per task in int (assuming that 1int=4bytes... ;-)) 45 46 47 typedef struct s_task_type // structure that defines a task 48 { 49 int num; // task number 50 int state; // task state 51 void (*add_fct)(void); // address of the associate function 52 void* stack_bottom; // address of the first (older) stack element 53 void* stack_top; // address of the first free stack element 54 void* stack_limit; // address of the end of stack section 55 } task_t; 56 57 58 AT91_REG save_sp; // stack pointer address

24

59 AT91_REG *psave_sp = &save_sp; 60 AT91_REG *pint_sp = NULL; 61 AT91_REG int_sp; 62 int nb_task_running = 0; 63 int actual_nb_tasks = 0; // number of initialized tasks 64 int sleeping[NB_TASKS]; // array of sleeping tasks 65 int active[NB_TASKS]; // array of active tasks 66 int unused[NB_TASKS]; // array of not initialized tasks 67 int starting[NB_TASKS]; // array of not sleeping tasks 68 task_t tasks[NB_TASKS]; // array of all manageable tasks 69 int tasks_RAM[NB_TASKS][TASK_SIZE]; // this table contains data area for tasks 70 int task_running = 0; // curently running task 71 int state_multitask = 0; // 0 when struct is not init, 1 when struct is init, 2 when scheduler is running 72 int scheduler_started; 73 char premier = 2; 74 75 void ITsched(); 76 77 78 int Init_Struct(int n) 79 // initializes data structures to implement n parrallel tasks 80 // must be the first function called. 81 // cannot be called from a task 82 // erases all previous data when called 83 // error messages 84 // -1 : called after scheduler start (E_RUNNING_SCHED) 85 { 86 int i = 0; 87 88 if (scheduler_started) 89 return E_RUNNING_SCHED; 90 91 for (i=0 ; i<NB_TASKS ; i++) 92 { 93 // initializes the array of all manageable tasks 94 tasks[i].num = i; 95 tasks[i].state = UNUSED; 96 tasks[i].add_fct = NULL; 97 tasks[i].stack_bottom = &(tasks_RAM[i][TASK_SIZE]); // bottom of stack is at higher addresses 98 tasks[i].stack_top = &(tasks_RAM[i][TASK_SIZE]); // stack is empty at first 99 tasks[i].stack_limit = &(tasks_RAM[i][0]); // lower address is the end of stack space 100 101 // initializes the array of sleeping, active, starting and not initialized tasks 102 sleeping[i] = 0; 103 active[i] = 0; 104 unused[i] = 1; 105 starting[i] = 0; 106 state_multitask = 1; 107 } 108 109 return 0; 110 } 111 112 113 114 int Create_Task(void (*task_func)(void)) 115 // initializes a task with the function given in argument 116 // returns the id number of the new task (positive value) or an error (negative value) 117 // the function given in argument must not return 118 // default state is STARTING 119 // error messages : 120 // -1 : called after scheduler start (E_RUNNING_SCHED) 121 // -2 : no room for new task (E_NO_SPACE_LEFT) 122 // -3 : data not initialized (E_NO_INIT) 123 { 124 // if struct is not init 125 if (state_multitask == 0) 126 return E_NO_INIT;

25

127 128 // if scheduler is already running 129 if (state_multitask == 2) 130 return E_RUNNING_SCHED; 131 132 // if all the tasks are initialized 133 if (actual_nb_tasks == NB_TASKS) 134 return E_NO_SPACE_LEFT; 135 136 tasks[actual_nb_tasks].state = UNUSED; 137 tasks[actual_nb_tasks].add_fct = task_func; 138 139 starting[actual_nb_tasks] = 1; 140 actual_nb_tasks++; 141 142 return (actual_nb_tasks-1); 143 } 144 145 146 147 int Start_Scheduler() 148 // actually starts the scheduler 149 // error messages : 150 // -3 : data not initialized (E_NO_INIT) 151 // -4 : no function to run (E_NO_FUNCTION) 152 { 153 // if struct is not init 154 if (state_multitask == 0) 155 return E_NO_INIT; 156 157 // if no functions are initialized 158 if (actual_nb_tasks == 0) 159 return E_NO_FUNCTION; 160 161 //TC0 Configuration 162 AT91C_BASE_PMC -> PMC_PCER = (1 << AT91C_ID_TC0); // Timer Counter 0 Power ON 163 AT91C_BASE_AIC -> AIC_IDCR = (1 << AT91C_ID_TC0); // Disable interrupt 164 AT91C_BASE_AIC -> AIC_SMR[AT91C_ID_TC0] = (AT91C_AIC_PRIOR_HIGHEST) | (AT91C_AIC_SRCTYPE_EXT_LOW_LEVEL); // AIC Configuration 165 AT91C_BASE_AIC -> AIC_SVR[AT91C_ID_TC0] = (unsigned int) ITsched; // Define interrupt function 166 AT91C_BASE_TC0 -> TC_CMR = AT91C_TC_CLKS_TIMER_DIV5_CLOCK | AT91C_TC_CPCTRG; // TC0 clock configure at 199691 Hz and auto reset of the counter 167 AT91C_BASE_TC0 -> TC_CCR = AT91C_TC_CLKEN; // TC0 clock enable 168 AT91C_BASE_TC0 -> TC_IER = AT91C_TC_CPCS; // TC0 interrupt type 169 AT91C_BASE_TC0 -> TC_RC = 2000; // 2000/199691=10ms between each interrupt 170 AT91C_BASE_TC0 -> TC_CCR = AT91C_TC_SWTRG; 171 AT91C_BASE_AIC -> AIC_IECR = (1 << AT91C_ID_TC0); 172 173 return 0; 174 } 175 176 177 178 int Wake_Task(int n) 179 // moves a task from sleeping to active state 180 // 0 means success 181 // errors : 182 // -10 : Task Not initialized (E_UNINIT_TASK) 183 // -11 : Active Task (E_ACTIVE_TASK) 184 { 185 if (sleeping[n] == 1) 186 { 187 tasks[n].state = ACTIVE; 188 sleeping[n] = 0; 189 active[n] = 1; 190 return 0; 191 } 192 193 if (active[n] == 1) 194 return E_ACTIVE_TASK;

26

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

return E_UNINIT_TASK; }

int Scotch_Task(int n) // moves a task from active to sleeping state // 0 means success // errors : // -10 : Task Not initialized (E_UNINIT_TASK) // -12 : Sleeping Task (E_SLEEPING_TASK) { if (active[n] == 1) { tasks[n].state = SLEEPING; sleeping[n] = 1; active[n] = 0; return 0; } if (sleeping[n] == 1) return E_SLEEPING_TASK; return E_UNINIT_TASK; }

int Launch_Task(int task_number) // Relunch an existing unused task with an reinitializing a its own stack // Return 0 if succes // errors: // -14 : Task doesn't exist (E_TASK_NO_EXISTINGG) // -15 : Task not in unused state(E_TASK_RUNNING) { if(task_number >= actual_nb_tasks) return E_TASK_NO_EXISTING; if(tasks[task_number].state == SLEEPING||tasks[task_number].state == ACTIVE || tasks[task_number].state == STARTING) return E_TASK_RUNNING; tasks[task_number].stack_top = tasks[task_number].stack_bottom; // reinitializing stack

234 235 236 237 tasks[task_number].state = STARTING; //Task ready to be start 238 239 sleeping[task_number] = 0; 240 active[task_number] = 0; 241 unused[task_number] = 0; 242 starting[task_number] = 1; 243 244 return 0; 245 246 } 247 248 249 250 void ITsched() 251 // this function is intented to be run as an interrupt 252 // and implements the scheduler 253 { 254 int i = task_running; 255 256 // Search for the next active or starting task 257 do 258 { 259 i++; 260 if (i > NB_TASKS-1) 261 i = 0; 262 } while ((active[i] != 1) && (starting[i] != 1)); 263 264 // Disable interrupt

27

265 AT91C_BASE_AIC->AIC_IDCR = 1 << AT91C_ID_TC0; // Timer Counter 0 Interrupt Disable 266 unsigned long int interrupt_sr = AT91C_BASE_AIC -> AIC_IMR; // Store Status of all interrupt 267 AT91C_BASE_AIC -> AIC_IDCR = 0xFFFFFFFF; // Disable all interrupt 268 AT91C_BASE_TC0 -> TC_SR; // Read the Status Register to empty the Interrupt Register 269 270 // if next task is "starting" 271 // change task to "active" 272 // remove IT mode 273 // launch function 274 // infinite loop 275 276 //SP modification 277 //commande asm 278 // if(j < NB_TASKS+1) 279 { 280 //asm("MOV save_sp, sp"); 281 if(premier <= 1 ) //Verify that's not the first launching of ITsched() 282 { 283 premier = 0; 284 asm( "mov r2, sp \n" //Save sp register in save_sp 285 "ldr r3, psave_sp \n" 286 "str r2, [r3] \n"); 287 save_sp -= 4; 288 int_sp = 0xFFFC -(nb_task_running)*12; //Store IRQ stack into our supervisor stack 289 asm( "ldr r3, int_sp \n" 290 "ldr r3, [r3] \n" 291 "ldr r2,save_sp \n" 292 "mov sp,r2 \n" 293 "str r3, [r2] \n"); 294 save_sp -= 4; 295 int_sp -= 4; 296 asm( "ldr r3, int_sp \n" 297 "ldr r3, [r3] \n" 298 "ldr r2,save_sp \n" 299 "mov sp,r2 \n" 300 "str r3, [r2] \n"); 301 save_sp -= 4; 302 int_sp -= 4; 303 asm( "ldr r3, int_sp \n" 304 "ldr r3, [r3] \n" 305 "ldr r2,save_sp \n" 306 "mov sp,r2 \n" 307 "str r3, [r2] \n"); 308 309 tasks[task_running].stack_top = (void*) save_sp; // in the current task 310 } 311 312 313 task_running = i; 314 save_sp = (AT91_REG) tasks[task_running].stack_top; // in the new task 315 //commande asm 316 317 if(premier < 1) // Check if it is the second (or more) lauch of ITsched() 318 { 319 if(premier <= 0) 320 premier = 1; 321 asm( "ldr r2, save_sp \n" //restore the IRQ stack from our supervisor stack 322 "ldr r3, int_sp \n" 323 "ldr r2, [r2] \n" 324 "str r2, [r3] \n"); 325 save_sp += 4; 326 int_sp += 4; 327 asm( "ldr r2, save_sp \n" 328 "ldr r3, int_sp \n" 329 "ldr r2, [r2] \n" 330 "str r2, [r3] \n"); 331 save_sp += 4; 332 int_sp += 4; 333 asm( "ldr r2, save_sp \n"

28

334 "ldr r3, int_sp \n" 335 "ldr r2, [r2] \n" 336 "str r2, [r3] \n"); 337 save_sp += 4; 338 int_sp += 4; 339 } 340 asm( "ldr r2,save_sp \n" // Change stack 341 "mov sp,r2 \n"); 342 premier--; 343 } 344 345 // Enable interrupt 346 AT91C_BASE_AIC -> AIC_IECR = interrupt_sr; // Restore status of all interrupt 347 AT91C_BASE_AIC -> AIC_IECR = 1 << AT91C_ID_TC0; // Timer Counter 0 Interrupt Enable 348 349 // First time execute of a task 350 if (tasks[task_running].state == STARTING) 351 { 352 tasks[task_running].state = ACTIVE; // Change task to active 353 starting[task_running] = 0; 354 active[task_running] = 1; 355 AT91C_BASE_AIC->AIC_EOICR = 0; // The function is not an interrupt function anymore 356 nb_task_running++; 357 //Launch_Function((tasks[task_running].add_fct)); // Go to the new function 358 (tasks[task_running].add_fct)(); 359 tasks[task_running].state = UNUSED; //In case of return from the task (STOP) 360 active[task_running] = 0; 361 unused[task_running] = 1; 362 while(1){} 363 } 364 } 365

29

Вам также может понравиться