After graduating at UBC I got the opportunity to teach a course called Measurement and Instrumentation, a core 3rd year course in Mechanical Engineering. The course covered all the steps in getting data from a physical system into a computer: sensor selection, analog signal processing, and readout.
As a lab component I supplied students with a PCB (designed by my lab-mate) that spits out data from a 3-axis accelerometer. Their task was to write a driver in Visual C# that let them read in the data, then use the data to do something cool.
The microprocessor on the PCB (an MSP430, the workhorse of our research lab) was programmed to transmit data through a UART at 128000 baud. The package we chose was simply [255, XAcceleration, YAcceleration, ZAcceleration], where the three data bytes were confined to [0,254] in value.
Many students struggled writing their drivers, as there were a few complications.
First problem, events involving a serial port in C# are handled on a different thread than events involving the GUI. Illegal cross-thread call errors plagued students who tried to read in serial data and directly use it to update a chart or text box. It was interesting to see that programs run on slower computers would crash right away, whereas more modern machines could run up to 30 seconds or so before there was a collision - simultaneously writing to and reading from the same address in memory. A simple solution to this problem was storing your incoming data in concurrent queues, a thread-safe data structure.
The second common trip-up was building a functional state machine. The data coming in from the accelerometer is unlabeled; if you just look at one byte there’s no way to know which axis it represents. We solve this problem by knowing a) the pattern of incoming data package, and b) which axis we read last. When the serial port is first connected we wait to see 255, then we know the next byte will be the X-axis data, and the subsequent one will be the Y-axis, etc.
The final problem was lag. Roughly 1400 useful bytes/second are being sent across the cable, and if you use a first-in-first-out queue and don’t process the data fast enough, you’ll end up with overflow. Some projects would work OK immediately after loading, then get progressively more and more sluggish as time wore on before eventually crashing altogether. The simple fix was making sure each ‘use the data’ step used all of the data in the queue, not just the first available byte.
As an example project, I used the accelerometer PCB to control a game. When the person holding the accelerometer PCB makes a certain gesture, for example a punch up and to the left, that gesture will produce a characteristic combination of X,Y, and Z accelerations. Our punch up and to the left gives a peak [X, Y, Z] accelerations of greater than [1.3,-1,2] g. My C# program continually looks for these gesture signatures, and when it finds one, it sends the appropriate keystroke to the gameplay window using the SendKeys function.