Multiple Tasks Application with Kernel Objects
The code of the listings below shows a more complete example and contains three tasks, a mutual exclusion semaphore, and a message queue.
/* *********************************************************************************************** * INCLUDE FILES *********************************************************************************************** */ #include <app_cfg.h> #include <bsp.h> #include <os.h> /* *********************************************************************************************** * LOCAL GLOBAL VARIABLES *********************************************************************************************** */ static OS_TCB AppTaskStartTCB; (1) static OS_TCB AppTask1_TCB; static OS_TCB AppTask2_TCB; static OS_MUTEX AppMutex; (2) static OS_Q AppQ; (3) static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; (4) static CPU_STK AppTask1_Stk[128]; static CPU_STK AppTask2_Stk[128]; /* *********************************************************************************************** * FUNCTION PROTOTYPES *********************************************************************************************** */ static void AppTaskStart (void *p_arg); (5) static void AppTask1 (void *p_arg); static void AppTask2 (void *p_arg);
(1) Here we allocate storage for the OS_TCB
s of each task.
(2) A mutual exclusion semaphore (a.k.a. a mutex) is a kernel object (a data structure) that is used to protect a shared resource from being accessed by more than one task. A task that wants to access the shared resource must obtain the mutex before it is allowed to proceed. The owner of the resource relinquishes the mutex when it has finished accessing the resource. This process is demonstrated in this example.
(3) A message queue is a kernel object through which Interrupt Service Routines (ISRs) and/or tasks send messages to other tasks. The sender “formulates” a message and sends it to the message queue. The task(s) wanting to receive these messages wait on the message queue for messages to arrive. If there are already messages in the message queue, the receiver immediately retrieves those messages. If there are no messages waiting in the message queue, then the receiver will be placed in a wait list associated with the message queue. This process will be demonstrated in this example.
(4) A stack is allocated for each task.
(5) The prototype of the tasks are declared.
The listing below shows the C entry point, i.e. main()
.
void main (void) { OS_ERR err; BSP_IntDisAll(); OSInit(&err); /* Check for 'err' */ OSMutexCreate((OS_MUTEX *)&AppMutex, (1) (CPU_CHAR *)"My App. Mutex", (OS_ERR *)&err); /* Check for 'err' */ OSQCreate ((OS_Q *)&AppQ, (2) (CPU_CHAR *)"My App Queue", (OS_MSG_QTY )10, (OS_ERR *)&err); /* Check for 'err' */ OSTaskCreate((OS_TCB *)&AppTaskStartTCB, (3) (CPU_CHAR *)"App Task Start", (OS_TASK_PTR )AppTaskStart, (void *)0, (OS_PRIO )APP_TASK_START_PRIO, (CPU_STK *)&AppTaskStartStk[0], (CPU_STK_SIZE)APP_TASK_START_STK_SIZE / 10, (CPU_STK_SIZE)APP_TASK_START_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /* Check for 'err' */ OSStart(&err); /* Check for 'err' */ }
(1) Creating a mutex is simply a matter of calling OSMutexCreate()
. You need to specify the address of the OS_MUTEX
object that will be used for the mutex. Resource Management provides additional information about mutual exclusion semaphores.
You can assign an ASCII name to the mutex, which is useful when debugging.
(2) You create the message queue by calling OSQCreate()
and specify the address of the OS_Q
object. Message Passing provides additional information about message queues.
You can assign an ASCII name to the message queue which can also be useful during debugging.
You need to specify how many messages the message queue is allowed to receive. This value must be greater than zero. If the sender sends messages faster than they can be consumed by the receiving task, messages will be lost. This can be corrected by either increasing the size of the message queue, or increasing the priority of the receiving task.
(3) The first application task is created.
The listing below shows how to create other tasks once multitasking as started.
static void AppTaskStart (void *p_arg) { OS_ERR err; p_arg = p_arg; BSP_Init(); CPU_Init(); BSP_OS_TickInit(); OSTaskCreate((OS_TCB *)&AppTask1_TCB, (1) (CPU_CHAR *)"App Task 1", (OS_TASK_PTR )AppTask1, (void *)0, (OS_PRIO )5, (CPU_STK *)&AppTask1_Stk[0], (CPU_STK_SIZE)0, (CPU_STK_SIZE)128, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); OSTaskCreate((OS_TCB *)&AppTask2_TCB, (2) (CPU_CHAR *)"App Task 2", (OS_TASK_PTR )AppTask2, (void *)0, (OS_PRIO )6, (CPU_STK *)&AppTask2_Stk[0], (CPU_STK_SIZE)0, (CPU_STK_SIZE)128, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); BSP_LED_Off(0); while (1) { BSP_LED_Toggle(0); OSTimeDlyHMSM((CPU_INT16U) 0, (CPU_INT16U) 0, (CPU_INT16U) 0, (CPU_INT32U)100, (OS_OPT )OS_OPT_TIME_HMSM_STRICT, (OS_ERR *)&err); } }
(1) Task #1 is created by calling OSTaskCreate()
. If this task happens to have a higher priority than the task that creates it, µC/OS-III will immediately start Task #1. If the created task has a lower priority, OSTaskCreate()
will return to AppTaskStart()
and continue execution.
(2) Task #2 is created and if it has a higher priority than AppTaskStart()
, µC/OS-III will immediately switch to that task.
static void AppTask1 (void *p_arg) { OS_ERR err; CPU_TS ts; p_arg = p_arg; while (1) { OSTimeDly ((OS_TICK )1, (1) (OS_OPT )OS_OPT_TIME_DLY, (OS_ERR *)&err); OSQPost ((OS_Q *)&AppQ, (2) (void *)1; (OS_MSG_SIZE)sizeof(void *), (OS_OPT )OS_OPT_POST_FIFO, (OS_ERR *)&err); OSMutexPend((OS_MUTEX *)&AppMutex, (3) (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING; (CPU_TS *)&ts, (OS_ERR *)&err); /* Access shared resource */ (4) OSMutexPost((OS_MUTEX *)&AppMutex, (5) (OS_OPT )OS_OPT_POST_NONE, (OS_ERR *)&err); } }
(1) The task starts by waiting for one tick to expire before it does anything useful. If the µC/OS-III tick rate is configured for 1000 Hz, the task will be suspended for 1 millisecond.
(2) The task then sends a message to another task using the message queue AppQ
. In this case, the example sends a fixed message of value “1,” but the message could have consisted of the address of a buffer, the address of a function, or whatever would need to be sent.
(3) The task then waits on the mutual exclusion semaphore since it needs to access a shared resource with another task. If the resource is already owned by another task, AppTask1()
will wait forever for the mutex to be released by its current owner. The forever wait is specified by passing 0 as the second argument of the call.
(4) When OSMutexPend()
returns, the task owns the resource and can therefore access the shared resource. The shared resource may be a variable, an array, a data structure, an I/O device, etc. You should note that we didn’t actually show the access to the shared resource. This is not relevant at this point.
(5) When the task is done with the shared resource, it must call OSMutexPost()
to release the mutex.
static void AppTask2 (void *p_arg) { OS_ERR err; void *p_msg; OS_MSG_SIZE msg_size; CPU_TS ts; CPU_TS ts_delta; p_arg = p_arg; while (1) { p_msg = OSQPend((OS_Q *)&AppQ, (1) (OS_MSG_SIZE *)&msg_size, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)&ts, (OS_ERR *)&err); ts_delta = OS_TS_GET() - ts; (2) /* Process message received */ (3) } }
(1) Task #2 starts by waiting for messages to be sent through the message queue AppQ
. The task waits forever for a message to be received because the third argument specifies an infinite timeout.
When the message is received p_msg
will contain the message (i.e., a pointer to “something”). In our case, AppTask2()
will always receive a message value of ‘1
’. Both the sender and receiver must agree as to the meaning of the message. The size of the message received is saved in “msg_size
”. Note that “p_msg
” could point to a buffer and “msg_size
” would indicate the size of this buffer.
Also, when the message is received, “ts
” will contain the timestamp of when the message was sent. A timestamp is the value read from a fairly fast free-running timer. The timestamp is typically an unsigned 32-bit (or more) value.
(2) Knowing when the message was sent allows the user to determine how long it took this task to get the message. This is done by reading the current timestamp and subtracting the timestamp of when the message was sent. Note that the receiving task may not get the message immediately since ISRs or other higher-priority tasks might execute before the receiver gets to run.
(3) Here you would add your own code to process the received message.