Sunday, November 29, 2015

The Chronograph - Part 3

Now I'm going to turn my attention to the software for the stand-alone controller shown in the previous post.

Before I ever started working on the UI for the device, the first order of business was to get the code needed to actually perform it's primary function written.


So I knew that the timing resolution needed to be very high because the items being sensed may pass by at > 3000 feet-per-second or about 915 meters per second. With 1 foot between the screen stages, at 4000 feet-per-second, it takes 250us to travel 1 foot... The Arduino is clocking at 16Mhz, so if I set the timer prescaler to divide by 8, the timer will increment at 2Mhz.

I wrote special fast timer and stopwatch classes that ended up with a 1 microsecond resolution, instead of the 4 microsecond resolution of the standard micros() function. Internally, however, the stopwatch timer had a resolution of 500 nano-seconds (billionths of a second) or 1/2000000.

class FastTimer
{
public:
 FastTimer() {};
 static void init();
 unsigned long micros();
};

typedef enum Stopwatches {
 SW1,
 SW2,
 SW3,
 SW4,
 SW5,
 SW6,
 SW7,
 SW8
} Stopwatches;

class Stopwatch
{
public:
 Stopwatch(FastTimer timer);
 boolean Start(Stopwatches stopwatch);
 unsigned long Stop(Stopwatches stopwatch);
 unsigned long Elapsed(Stopwatches stopwatch);
private:
 typedef struct InternalStopwatch
 {
  unsigned long startCount;
  unsigned long stopCount;
  boolean running;
 };
 FastTimer _timer;
 InternalStopwatch _stopwatches[SW8 + 1];
};

The implementation is over here. It should work on an Arduino Uno and Mega.

Now that I have a fast timer, I can actually start timing things. Here is the Chronograph screen class:


// ChronoScreen.h

#ifndef _CHRONOSCREEN_h
#define _CHRONOSCREEN_h

#if defined(ARDUINO) && ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif
#include <Printable.h>
#include <FastTimer.h>

#define SCREEN1IR 42
#define SCREEN2IR 44
#define SCREEN3IR 46
#define SCREEN1PULSE 4
#define SCREEN2PULSE 3
#define SCREEN3PULSE 2
#define SCREEN1PIN 19
#define SCREEN2PIN 20
#define SCREEN3PIN 21

class ChronoScreen : public Printable
{
private:
 bool timingPending;
public:
 ChronoScreen();
 void init();
 void SetScreenLEDState(bool state);
 bool StartTiming(void);
 bool GetScreenData(unsigned long &screen12Count, unsigned long &screen13Count);
 bool GetVelocityData(float &VelocStage1, float &VelocStage2);
 void CancelTiming(void);
        virtual size_t printTo(Print& p) const;
 volatile bool *timingFinished;
};

void CheckTimingStarted(volatile bool &condition);
void CheckTimingFinished(volatile bool &condition);

extern ChronoScreen chronoScreen;

#endif

Call SetScreenLEDState() to turn the IR emitters on or off. Each stage can be independently controlled, but it's not surfaced in this class. Once, timingFinished goes to true, you can call GetScreenData or GetVelocityData to read the actual timing information.

Remember, I said that the timer resolution is 1us, and the screens are 1 foot apart so to get the actual feet-per-second, simply divide 1000000.0 by the elapsed number of microseconds. If it took 250us travel between two of the screens, that would be 4000 feet-per-second, so 1000000.0 / 250 = 4000. A combination of the physical dimensions and proper timer setup, makes the calculations very fast and simple.

Oh, those CheckTimingStarted() and CheckTimingFinished() functions? Those are used by the Event loop class to check for certain break-out states or events. I'll cover the event loop later. The whole idea of the event loop was to keep the UI fully responsive and functional even while timing. The actual screen timing is done in interrupt routines, so that part is fully pre-emptive.

Initial tests were done by standing the screens up vertically and dropping an object through them. Since the acceleration of gravity is a known constant, 32.3 ft/s2 (9.8 m/s2), it's easy to calculate the expected timings.. except that the second screen stage will always be faster than the first since the object is accelerating. Also, the timing is effectively an average because it cannot measure instantaneous speed. So, using equations like these, I figured out what I should expect for timings. I should see timings that result in about 5.7 feet-per-second. That is precisely what I was seeing with a little variance for inconsistent drop heights.

Next up, we'll start to look at the UI, event loop, and the touch-screen.