Accuracy of timestamping streamed data (code included - long)
Accuracy of timestamping streamed data (code included - long)
- Subject: Accuracy of timestamping streamed data (code included - long)
- From: Hank Heijink <email@hidden>
- Date: Mon, 25 Sep 2006 17:07:32 -0400
Hi everyone,
I'm new to this list, and fairly new to Cocoa (although by now I've
worked my way through Hillegass and Dalrymple + same). I'm working on
the following problem:
For an application in behavioral science, I'm recording cursor
movements on an old (10+ years) and big (3 by 5 feet) Quora
digitizing tablet. The tablet streams 6-byte coordinates at a
constant rate of about 160 Hz, and I want to get as constant a
recording rate as I can get. After saving my data to a file, I find
I'm fairly accurate, that is, I've timestamped my coordinates such
that they're between 5 and 7.5 ms apart. The standard deviation of
the differences in time is about 0.3 ms.
My question is, can I achieve even better accuracy, and if so, how?
I'd like a more constant rate - I know the output rate of the tablet
is more constant than this, and I'm not sure what causes the
variability. My application is built in release mode and it's the
only one running. Not sure if background daemons are having an effect
and I don't know much about USB timing accuracy, but I guess at this
level of accuracy, everything could have an effect...
I hope I'm clear... Any thoughts are much appreciated!
Thanks a lot,
Hank
PS. For the more curious... the tablet is connected to my 2.0GHz
MacBook with an RS422 cable that connects to a Keyspan serial-port-to-
USB converter.
-----
Here's what I've done (with the relevant code, apologies for the
amount). I've taken the naive approach:
In my interface, a button detaches an NSThread using
detachNewThreadSelector:toTarget:withObject:, and this is the selector.
- (void)launchThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// Set the priority of this thread to something high to improve
timing accuracy
if (![NSThread setThreadPriority:1.0])
NSLog(@"Failed to set priority");
// Initialize a timer with a firing rate of 0.005 seconds (5 ms or
200 Hz) --
// Well over the tablet's streaming frequency of about 160 Hz.
timer = [NSTimer timerWithTimeInterval:0.005
target:self
selector:@selector(updateTabletCursor)
userInfo:nil
repeats:YES];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate
distantFuture]]);
[pool release];
}
This is the method that does the work. shouldStopStreaming is set
from the interface, recordingArray is an instance variable
(NSMutableArray) of self.
- (void)updateTabletCursor
{
if ([tablet shouldStopStreaming]) {
[timer invalidate];
return;
}
// Read one coordinate from the tablet
MVCoordinate *coordinate = [tablet read];
[self setCurrentCoordinate:coordinate];
// Do we need to record the movement?
if ([self isRecording]) {
[recordingArray addObject:coordinate];
}
}
This is the read function. The timestamping happens in the init code
of the MVCoordinate class by calling mach_absolute_time. It gets
converted to nanoseconds when the recordingArray is written to a file.
- (id)read
{
unsigned char buffer[60];
unsigned char *indexInBuffer;
int bytesRead, i;
if ((bytesRead = ReadSerialPort(fileDescriptor, buffer)) <= 0)
return nil;
indexInBuffer = buffer;
// A new coordinate starts with a 1 on the most significant bit of
the first byte.
// Search for that byte
for (i = 0; i < bytesRead; i++) {
if (*(indexInBuffer + i) & 0x80) break;
}
return [[[MVCoordinate alloc] initWithRawData:indexInBuffer]
autorelease];
}
And finally, the ReadSerialPort function. PortAvailableForReading is
a wrapper for select(). The two integer arguments are used to make
the timeval struct.
int ReadSerialPort (int fileDescriptor, unsigned char *stringToRead)
{
ssize_t bytesRead = 0;
unsigned char buffer[60]; // We're only expecting 6 bytes
// Have we got anything to read?
if (!PortAvailableForReading(fileDescriptor, 0, 20000)) {
fprintf (stderr, "Nothing to read\n");
return bytesRead;
} else {
bytesRead = read (fileDescriptor,
buffer,
sizeof (buffer));
}
memcpy(stringToRead, buffer, bytesRead);
return bytesRead;
}
-----
Hank Heijink
Postdoctoral Research Fellow
Center for Neurobiology and Behavior
Columbia University Medical Center
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden