ブログ:
Real Time Scheduler on Windows Embedded Compact

2016年2月18日木曜日
Windows

WindowsA key distinguishing feature that makes the Windows Embedded Compact different from all other versions of Windows is its real-time scheduler. This is a very important feature to consider when developing embedded systems. It is always nice to remember the meaning of real-time in the engineering context, which is why I have transcribed below my favorite definition, found in the book “Real-Time System Design and Analysis”, written by Phillip A. Laplante:

"A real-time system is a system that must satisfy explicit (bounded) response-time constraints or risk severe consequences, including failure."

- Real-Time System Design and Analysis (Phillip A. Laplante)

Thus, a real-time system executes the code to produce a precise answer and it does that within a prescribed time constraint; otherwise, this may lead to a system failure. Real-time does not necessarily mean fast processing capabilities.

The Windows Embedded Compact is able to behave in accordance with the definition above. Hence, one of the first things we must understand is how does its task scheduler work? WinCE follows two simple rules while scheduling tasks. Every 1ms the scheduler looks at the existing task in the system and decides which task will run based on the rules mentioned below:

  1. Higher priority tasks should be executed first.
  2. Tasks with the same priority level should be executed according to a Round Robin with a timeslice of 100ms (or as defined in the Task Quantum).

For rule number one, the WinCE has 256 priority levels, higher values mean lower priority, and lower values mean higher priorities. Guidance on the usage of priorities can be found in the following article: Real-Time Priority System Levels (Windows Embedded CE 6.0).

Regarding rule number two, when several tasks run at the same priority, the system will run each task for a time period of 100ms (this time period can be configured according to the Thread Quantum). Once a task has been executed for the specified time period, the kernel suspends that task and allows another task with the same priority to run for its configured time period (on WinCE this time period is called Quantum). Following this, if there is a third task with the same priority in the queue, the scheduler will then suspend the second task and start the third task until all the tasks with same priority have run once. Once all of the tasks have been executed for one quantum, the scheduler will start the first task again. A thread can only run for a quantum unless a higher priority thread requires the CPU. In this case the thread is pre-empted by the scheduler.

Computer Hardware- Colibri VF61, Iris Module Carrier Board
Image 1: A computer hardware used in Toradex Colibri VF61 Iris module together with a carrier board.

I will use two examples to help demonstrate the rules described above. The first example is intended to test rule number 2. I will start two different tasks, the first will set the level of digital output of my hardware to logical level high, and the other task will set the same digital output to low. I will use a Toradex Colibri VF61 256MB System on Module which is based on NXP®/Freescale Vybrid processor, along with the Iris Carrier Board (Image 1). The processor of Colibri VF61 is an Arm® Cortex-A5 running at 500 MHz, equipped with 256 MB of RAM and 512 MB of Flash on module. The OS is Windows Embedded Compact 6.0 and supplied by Toradex in a ready-to-use fashion to its customers. The WinCE license is included in it price too, cool right? I have implemented the source code below.

/// @file         Gpio_Demo.c
/// @copyright    Copyright (c) 2014 Toradex AG
/// $Author: guilherme.fernandes $
/// $Revision: 2908 $
/// $Date: 2015-08-10 08:29:50 +0200 (Mo, 10 Aug 2015) $
/// @brief        Program to show how to use the Gpio Library 
///               
/// @target       Colibri VFxx,Txx,iMx Modules
/// @test         Tested on:  VFxx
/// @caveats      None
 
#include  
#include "gpio.h"
 
// === define constant pins / gpios ===
uIo io1 = COLIBRI_PIN(101);  
HANDLE hGpio;
HANDLE hThreadON, hThreadOFF;
 
DWORD WINAPI ThreadON (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 100);
	Sleep(5); //Allow the other Thread to configure its PRIO
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic high
		Gpio_SetLevel(hGpio, io1, ioHigh);
    }
    return 0;
}
 
 
DWORD WINAPI ThreadOFF (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 100);
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic low
		Gpio_SetLevel(hGpio, io1, ioLow);
    }
    return 0;
}
 
 
//=============================================================================
// Application Entry Point
// 
// The simple error handling using ASSERT statements is only effective when 
// the application is run as a debug version.
//=============================================================================
int wmain(int argc, _TCHAR* argv[])   
{ 
    //HANDLE hGpio = NULL;            ///< handle to the GPIO library
    BOOL success;
 
    // === Initialize GPIO library. ===
    // We don't use registry-based  configuration, thus we can 
    // pass NULL go Gpio_Init()
    hGpio = Gpio_Init(NULL);   
    ASSERT(hGpio != 0);
    success = Gpio_Open(hGpio);
    ASSERT (success);
 
    // === Io Manipulation ===
    // Configure the pin to act as GPIO (as opposed to an Alternate function)
    // Set it to Output,  High
    Gpio_ConfigureAsGpio(hGpio, io1);
    Gpio_SetDir         (hGpio, io1, ioOutput);
    Gpio_SetLevel       (hGpio, io1, ioHigh);
 
	CeSetThreadPriority(GetCurrentThread(), 99);
 
	//Create two concorrent Threads, one set GPIO to High and other to Low
	hThreadON = CreateThread (0, 0, ThreadON, 0, 0, 0);
	hThreadOFF = CreateThread (0, 0, ThreadOFF, 0, 0, 0);
 
	//Time to finish the Program
	Sleep(3000);
 
    return(TRUE);
}
 

Note that there are two different entry points for the threads: ThreadON and ThreadOFF. Each function contains only the instruction to set the same GPIO pin to high or low. Also, please note that each function will run indefinitely, and not explicitly; thereby releasing the CPU for other threads or programs.

The main function starts the threads and we can see in the image 2 the resulting digital output behavior measured on an oscilloscope. As expected, once the tasks have the same priority level, they will alternate according to a Round Robin schedule with a "timeslice" for 100ms for each task. Therefore, this simple demo shows the second rule in action!

Two threads with same priority level
Image 2: Perform two threads with the same priority level, the first connect a digital output and the second off.

To test the first rule, we make the following changes in our code. First, in the ThreadON function I will write a Sleep(5) command which will make the thread suspend its execution and send a request to the scheduler to be resumed in 30ms.

DWORD WINAPI ThreadON (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 99);
Sleep(5); //Allow the other Thread to configure its PRIO
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic high
		Gpio_SetLevel(hGpio, io1, ioHigh);
		Sleep(5);
    }
    return 0;
}

I will also increase the priority of ThreadON changing it from 100 to 99 (see the change to be highlighted in Yellow above).

Thus, the ThreadON will have a higher priority than the ThreadOFF. The result can be observed in the oscilloscope picture below. Each time the thread of higher priority wakes-up, the kernel stops the lower priority thread to allow the higher priority thread to run.

Threads with different priorities
Image 3: Implementing threads with different priorities. The highest priority thread interrupts the schedule of the lower case.

“Hands-up” is an important signal if you are working with a multicore system. The WinCE Kernel started offering support for multicore processors from version 7.0. Then a new property, called affinity, will define which core executes the thread. If you try to run the abovementioned sample in WinCE 7.0 on a multicore system without determining that the booth treads run in the same core, the result will be different as they will run at the same time in different cores. Usually it’s not a good idea to set the affinity because by not setting it, we will allow the scheduler to run our thread on the first core that is available, thereby improving the latencies.

Real-time is a necessary requirement today for a broad set of applications, including embedded system in industrial automation, applications of embedded systems in robotics or embedded medical devices. Knowing how to use threads and how the WinCE Scheduler works will allow you to get a deterministic execution of your application and enable you to create real-time systems using Windows Embedded Compact!

References

This blog post was originally featured on Embarcados.com in Portuguese. See here.

記者: Guilherme Fernandes, CEO, Toradex Brasil

コメントを投稿

Please login to leave a comment!
Have a Question?