The first two tasks send and receive an incrementing number to/from a queue. One task acts as a producer and the other as the consumer. The consumer is a higher priority than the producer and is set to block on queue reads. The queue only has space for one item - as soon as the producer posts a message on the queue the consumer will unblock, pre-empt the producer, and remove the item.
The second two tasks work the other way around. Again the queue used only has enough space for one item. This time the consumer has a lower priority than the producer. The producer will try to post on the queue blocking when the queue is full. When the consumer wakes it will remove the item from the queue, causing the producer to unblock, pre-empt the consumer, and immediately re-fill the queue.
The last two tasks use the same queue producer and consumer functions. This time the queue has enough space for lots of items and the tasks operate at the same priority. The producer will execute, placing items into the queue. The consumer will start executing when either the queue becomes full (causing the producer to block) or a context switch occurs (tasks of the same priority will time slice).
The first task repeatedly sends a string to a queue, character at a time. The serial port interrupt will empty the queue and transmit the characters. The task blocks for a pseudo random period before resending the string.
The second task blocks on a queue waiting for a character to be received. Characters received by the serial port interrupt routine are posted onto the queue - unblocking the task making it ready to execute. If this is then the highest priority task ready to run it will run immediately - with a context switch occurring at the end of the interrupt service routine. The task receiving characters is spawned with a higher priority than the task transmitting the characters.
With the loop back connector in place, one task will transmit a string and the other will immediately receive it. The receiving task knows the string it expects to receive so can detect an error.
This also creates a third task. This is used to test semaphore usage from an ISR and does nothing interesting.
N 'fixed delay' co-routines are created that just block for a fixed period then post the number of an LED onto a queue. Each such co-routine uses its index parameter as an index into array in order to obtain the block period and LED that is flashed. A single 'flash' co-routine is also created that blocks on the same queue, waiting for the number of the next LED it should flash. Upon receiving a number it simply toggle the instructed LED then blocks on the queue once more. In this manner each LED from LED 0 to LED N-1 is caused to flash at a different rate.
The 'fixed delay' co-routines are created with co-routine priority 0. The flash co-routine is created with co-routine priority 1. This means that the queue should never contain more than a single item. This is because posting to the queue will unblock the higher priority 'flash' co-routine which will only block again when the queue is empty. An error is indicated if an attempt to post data to the queue fails - indicating that the queue is already full.
hookNUM_HOOK_CO_ROUTINES co-routines are created. Each co-routine blocks to wait for a character to be received on a queue from the tick ISR, checks to ensure the character received was that expected, then sends the number back to the tick ISR on a different queue.
The tick ISR checks the numbers received back from the 'hook' co-routines matches the number previously sent.
If at any time a queue function returns unexpectedly, or an incorrect value is received either by the tick hook or a co-routine then an error is latched.
This demo relies on each 'hook' co-routine to execute between each hookTICK_CALLS_BEFORE_POST tick interrupts. This and the heavy use of queues from within an interrupt may result in an error being detected on slower targets simply due to timing.
Two of the created suicidal tasks kill one other suicidal task before killing themselves - leaving just the original task remaining.
The creator task must be spawned after all of the other demo application tasks as it keeps a check on the number of tasks under the scheduler control. The number of tasks it expects to see running should never be greater than the number of tasks that were in existence when the creator task was spawned, plus one set of four suicidal tasks. If this number is exceeded an error is flagged.
One counter task loops indefinitely, incrementing the shared count variable on each iteration. To ensure it has exclusive access to the variable it raises it's priority above that of the controller task before each increment, lowering it again to it's original priority before starting the next iteration.
The other counter task increments the shared count variable on each iteration of it's loop until the count has reached a limit of 0xff - at which point it suspends itself. It will not start a new loop until the controller task has made it "ready" again by calling vTaskResume (). This second counter task operates at a higher priority than controller task so does not need to worry about mutual exclusion of the counter variable.
The controller task is in two sections. The first section controls and monitors the continuous count task. When this section is operational the limited count task is suspended. Likewise, the second section controls and monitors the limited count task. When this section is operational the continuous count task is suspended.
In the first section the controller task first takes a copy of the shared count variable. To ensure mutual exclusion on the count variable it suspends the continuous count task, resuming it again when the copy has been taken. The controller task then sleeps for a fixed period - during which the continuous count task will execute and increment the shared variable. When the controller task wakes it checks that the continuous count task has executed by comparing the copy of the shared variable with its current value. This time, to ensure mutual exclusion, the scheduler itself is suspended with a call to vTaskSuspendAll (). This is for demonstration purposes only and is not a recommended technique due to its inefficiency.
After a fixed number of iterations the controller task suspends the continuous count task, and moves on to its second section.
At the start of the second section the shared variable is cleared to zero. The limited count task is then woken from it's suspension by a call to vTaskResume (). As this counter task operates at a higher priority than the controller task the controller task should not run again until the shared variable has been counted up to the limited value causing the counter task to suspend itself. The next line after vTaskResume () is therefore a check on the shared variable to ensure everything is as expected.
The second test consists of a couple of very simple tasks that post onto a queue while the scheduler is suspended. This test was added to test parts of the scheduler not exercised by the first test.
The LED flash tasks provide instant visual feedback. They show that the scheduler is still operational.
The PC port uses the standard parallel port for outputs, the Flashlite 186 port uses IO port F.
All the tasks run at the idle priority and never block or yield. This causes all eight tasks to time slice with the idle task. Running at the idle priority means that these tasks will get pre-empted any time another task is ready to run or a time slice occurs. More often than not the pre-emption will occur mid calculation, creating a good test of the schedulers context switch mechanism - a calculation producing an unexpected result could be a symptom of a corruption in the context of a task.
As with flop.c, the tasks created in this file are a good test of the scheduler context switch mechanism. The processor has to access 32bit variables in two or four chunks (depending on the processor). The low priority of these tasks means there is a high probability that a context switch will occur mid calculation. See the flop.c documentation for more information.
Creates two tasks that communicate over a single queue. One task acts as a producer, the other a consumer.
The producer loops for three iteration, posting an incrementing number onto the queue each cycle. It then delays for a fixed period before doing exactly the same again.
The consumer loops emptying the queue. Each item removed from the queue is checked to ensure it contains the expected value. When the queue is empty it blocks for a fixed period, then does the same again.
All queue access is performed without blocking. The consumer completely empties the queue each time it runs so the producer should never find the queue full.
An error is flagged if the consumer obtains an unexpected value or the producer find the queue is full.
A task wishing to display a message will call vPrintDisplayMessage (), with a pointer to the string as the parameter. The pointer is posted onto the xPrintQueue queue.
The task spawned in main.c blocks on xPrintQueue. When a message becomes available it calls pcPrintGetNextMessage () to obtain a pointer to the next string, then uses the functions defined in the portable layer FileIO.c to display the message.
NOTE: Using console IO can disrupt real time performance - depending on the port. Standard C IO routines are not designed for real time applications. While standard IO is useful for demonstration and debugging an alternative method should be used if you actually require console IO as part of your application.
Each task starts by attempting to obtain the semaphore. On obtaining a semaphore a task checks to ensure that the guarded variable has an expected value. It then clears the variable to zero before counting it back up to the expected value in increments of 1. After each increment the variable is checked to ensure it contains the value to which it was just set. When the starting value is again reached the task releases the semaphore giving the other task in the set a chance to do exactly the same thing. The starting value is high enough to ensure that a tick is likely to occur during the incrementing loop.
An error is flagged if at any time during the process a shared variable is found to have a value other than that expected. Such an occurrence would suggest an error in the mutual exclusion mechanism by which access to the variable is restricted.
The first set of two tasks poll their semaphore. The second set use blocking calls.