[Date Prev][Date Next] [Chronological] [Thread] [Top]

Re: [rtl] RTL ROBOTS



On Sun, 7 Mar 1999, Stephane Pierre Bordas wrote:

> I am confronted to persons that think that RTL is not reliable because it
> is a freeware and thus would prefer to use Windows for applications
> needing calculations of robot arm trajectories requiring a data treatment
> as fast as one millisecond (at least 10 milliseconds). 
> 
> I don't think this would be a good idea to use Windows with such small
> durations, but I am facing the reluctancy of my partners and would like to
> know your advice on that.

Unlike many of the very smart people on this list who have always used
RT-Linux, or another RTOS, I started my project as your peers suggest,
with a Redmond OS known as NT.

This was a bad idea.

>From this experience I can say NT won't work. But, hopefully I can do a
little better and offer some code tidbits so that you can write some NT
code to show that it can not work.

First you need access to NT performance counters - the same can be
done in RT-Linux using the rdtsc() call of the kernel as suggested by
Mantegazza to examine jitter (Note: you will need to replace the DEBUG()
macro with printf() or whatever - this is what's in GError.h):

GPerfCounter.h
/*
/* Quick & Dirty Performance Counter Routines for Windows NT
/*
/*	QP_START	- starting point for timing
/*	QP_END		- ending point for timing
/*	QP_SECONDS	- returns a double giving duration in seconds
/*
/* Macros set QPCStartMark() and QPCEndMark() in the code and
/* QPCDurationInSeconds returns the time between the end
/* and start marks in seconds.
*/

// GPerfCounter.h
#ifndef G_PERFCOUNTER_HEADER
#define G_PERFCOUNTER_HEADER

#ifdef NT_PERFORMANCE_TIMING
BOOL QPCStartMark();
BOOL QPCEndMark();
double QPCDurationInSeconds();

#define QP_START		QPCStartMark()
#define QP_STOP			QPCEndMark()
#define QP_SECONDS		QPCDurationInSeconds()

#else

#define QP_START
#define QP_STOP
#define QP_SECONDS		-1.0

#endif
/* End Query Performace Counter Routines
*/

#endif
// GPerfCounter.h



GPerCounter.cpp:
/*
/* Quick & Dirty Performance Counter Routines for Windows NT
/*
/*	QP_START	- starting point for timing
/*	QP_END		- ending point for timing
/*	QP_SECONDS	- returns a double giving duration in seconds
/*
/* Macros set QPCStartMark() and QPCEndMark() in the code and
/* QPCDurationInSeconds returns the time between the end
/* and start marks in seconds.
*/
#include "GError.h"

static 	_int64	liQPCounter1 = 0;
static  _int64  liQPCounter2 = 0;
BOOL QPCStartMark()
{
	return QueryPerformanceCounter( (LARGE_INTEGER *)&liQPCounter1 );
	
}
BOOL QPCEndMark()
{
	return QueryPerformanceCounter( (LARGE_INTEGER *)&liQPCounter2 );
}
double QPCDurationInSeconds()
{
	/*Variables for the NT Performance Counter system*/
	static  double  dSecondsPerCount = 0.0;
	_int64	liQPFrequency = 0;
	_int64	liQPCountDifference = 0;

	if ( 0.0 == dSecondsPerCount ) {  /*frequency not yet set*/

		/*Get the frequency of the performance counter
		(counts/sec)*/
		if ( 0 == QueryPerformanceFrequency( \ 
				(LARGE_INTEGER*)&liQPFrequency) ) {
			/*No performance counter is available*/
			return -1.0;
		}
		DEBUG(10, ("QueryPerformanceFrequency returns %I64d counts/second\n", liQPFrequency));		
		dSecondsPerCount = ( 1.0/(double )liQPFrequency );
	}

	liQPCountDifference = liQPCounter2 - liQPCounter1;
	return liQPCountDifference * dSecondsPerCount;
}
/* End Query Performace Counter Routines
*/

If you imagine your RT task running in a service, as I did, so that it is
not subject to the tampering of the user, then you will need some other
routines as well. In any case, you will have a high priority loop
servicing the task, perhaps something like:

	//Setup the Thread...

	//Make this service the most important thing in user-space;
	//this will not prevent ISRs, kernel level DPCs and whatnot from
	//blowing this thread away. GUI routines may invert the priority
	//scheme and make a mess (minimize/maximize a window).
	//NT is _not_ a Real Time Operating System.
	if ( !SetPriority( REALTIME_PRIORITY_CLASS, 
		THREAD_PRIORITY_TIME_CRITICAL) )
		return DAQSVC_ERR_PRIORITY_NOT_SET;

	/* the main service loop will run until the RunServiceMutex is
	released */
	do {
	
		//do what you need to ...		

		//This is a REALTIME priority thread so give some CPU time
		//to other threads; this loop will run up CPU time to 100%
		//under the NT Task Manager (shift-ctrl-esc).
		YieldProcessorTime(0);

	} 
	/* Repeat while RunServiceMutex is still taken. */
	while ( (WAIT_TIMEOUT == dwRunMutex) && (bExecuteLoop) );
	
This provides for a "polling mode" type of task, of course, NT has a rich
object set, so you can wait on a variety of other object types instead.
For example, you could wait for input from some client task via a shared
mutex, or whatever. Such a wait will not make a difference as far as the
task timing is concerned, ISR/DPCs in the NT kernel will still insert 
lags in your regularly scheduled task (try minimizing then maximizing
windows, it's amazing how long this will delay a top priority task;
recall that under NT 4.0 the display is integrated into the kernel). 

These are some routines to help you with priorities and sleeping:

///////////////////////////////////////////////////////////////////////////////
//
// BOOL SetPriority( DWORD dwPriorityClass, int nPriority )
//
// Purpose:
//	Sets the priority and priority class of the current process and 
//	thread.
//
// Parameters:
//	dwPriorityClass		-The process class
//	nPriority		-The priority within the given
//				process class
//
// Returns:
//	TRUE on success, FALSE otherwise.
// 
BOOL SetPriority( DWORD dwPriorityClass, int nPriority )
{
	DWORD	dwReturnedPriorityClass = 0;
	int		nReturnedPriority = 0;

	//Run this process with the specified priority class
    if (FALSE == SetPriorityClass(GetCurrentProcess(), \
		dwPriorityClass)) {
		//Could not set the priority class
		return FALSE;
	}

	//Double check that the was actually given; it is possible
	//that SetPriorityClass might simply set the highest priority it
	//can, but fail to provide the requested (e.g. REALTIME) priority.
    dwReturnedPriorityClass = GetPriorityClass(GetCurrentProcess());
	if (dwReturnedPriorityClass != dwPriorityClass) {
		//Could not set the specified priority class
		return FALSE;
	}

	//Set the thread priority within the specified process class
	return SetThreadPriority(GetCurrentThread(), nPriority);	
}

///////////////////////////////////////////////////////////////////////////////
//
// void YieldProcessorTime( DWORD dwMilliseconds )
//
// Purpose:
//	Surrender time slice to other ready threads. Calls SwitchToThread
//	if nSeconds equals 0 or Sleep( nTime ).
//
// Parameters:
//	nTime		-Time to sleep in milliseconds.
//
// Returns:
//	void.
//
// Comments:
//	Sleep is not guaranteed to return after nTime milliseconds, the
//	time to return may be longer.
//
void YieldProcessorTime( DWORD dwMilliseconds )
{
	QP_START;
	if ( 0 < dwMilliseconds ) Sleep(dwMilliseconds);	
						//May sleep longer!
	else if ( 0 == dwMilliseconds ) SwitchToThread();	
						//On the current CPU only
	QP_STOP;
	DEBUG(10, ("YieldProcessorTime: %f\n", QP_SECONDS));
}



NT is really a pretty nice general purpose OS, it just isn't suited to
hard real-time work. When you look at all the advantages of Linux, and
throw RT into the mix, technically, you really don't have a choice.

Good luck,
-Don

--- [rtl] ---
To unsubscribe:
echo "unsubscribe rtl" | mail majordomo@rtlinux.cs.nmt.edu OR
echo "unsubscribe rtl <Your_email>" | mail majordomo@rtlinux.cs.nmt.edu
----
For more information on Real-Time Linux see:
http://www.rtlinux.org/~rtlinux/