An I/O controller for virtual pinball machines: accelerometer nudge sensing, analog plunger input, button input encoding, LedWiz compatible output controls, and more.

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers quadSensor.h Source File

quadSensor.h

00001 // AEDR-8300-1K2 optical encoder / generic quadrature sensor plunger 
00002 // implementation
00003 //
00004 // This class implements the Pinscape plunger interface for the 
00005 // AEDR-8300-1K2 optical encoder in particular, and quadrature sensors 
00006 // in general.  The code was written specifically for the AEDR-8300-1K2,
00007 // but it should work with any other quadrature sensor that's electrically 
00008 // compatible and that doesn't exceed the maximum interrupt rate we can 
00009 // handle on the KL25Z.  To be electrically compatible, the device must 
00010 // be 3.3V compatible, have logic type outputs (basically square waves
00011 // for the signals), and provide two outputs 90 degrees out of phase.  
00012 // The maximum interrupt rate that the KL25Z can handle (with our 
00013 // FastInterruptIn class) is about 150 kHz.
00014 //
00015 // A quadrature sensor works by detecting transitions along a bar-coded 
00016 // scale.  Most position encoders (including the AEDR-8300) are optical,
00017 // but the same principle can be used with other technologies, such as
00018 // magnetic pole strips.  Whatever the underlying physical "bar" type,
00019 // the device detects transitions between the bars and the spaces between
00020 // the bars and relays them to the microcontroller via its outputs.  A
00021 // quadrature device actually consists of two such sensors, slightly offset
00022 // from each other relative to the direction of motion of the scale, so 
00023 // that their bar transitions are 90 degrees out of phase.  The phase
00024 // shift in the two signals is what allows the microcontroller to sense
00025 // the direction of motion.  The controller figures the current position
00026 // by counting bar transitions (incrementing the count when moving in one
00027 // direction and decrement it in the other direction), so it knows the 
00028 // location at any given time as an offset in units of bar widths from the
00029 // starting position.  The position reading is always relative, because
00030 // we can only count up or down from the initial point.
00031 //
00032 // In many applications involving quadrature sensors, the relative 
00033 // quadrature reading is augmented with a separate sensor for absolute 
00034 // positioning.  This is usually something simple and low-res, like an 
00035 // end-of-stroke switch or a zero-crossing switch.  The idea is that you 
00036 // use the low-res absolute sensor to tell when you're at a known reference 
00037 // point, and then use the high-res quadrature data to get the precise 
00038 // location relative to the reference point.  To keep things simple, we 
00039 // don't use any such supplemental absolute sensor.  It's not really 
00040 // necessary for a plunger, because a plunger has the special property 
00041 // that it always returns to the same point when not being manipulated.  
00042 // It's almost as good as having a sensor at the park position, because
00043 // even though we can't know for sure the plunger is there at any given
00044 // time, it's a good bet that that's where it is at startup and any time
00045 // we haven't seen any motion in a while.  Note that we could easily add 
00046 // support in the software for some kind of absolute sensing if it became 
00047 // desirable; the only challenge is the complexity it would add to the
00048 // physical system.
00049 //
00050 // The AEDR-8300 lets us collect some very precise data on the 
00051 // instantaneous speed of the plunger thanks to its high resolution and
00052 // real-time position updates.  The shortest observed time between pulses 
00053 // (so far, with my test rig) is 19us.  Pulses are generated at 4 per
00054 // bar, with bars at 75 per inch, yielding 300 pulses per inch.  The 19us
00055 // pulse time translates to an instantaneous plunger speed of 0.175 
00056 // inches/millisecond, or 4.46 mm/ms, or 4.46 m/s, or 9.97 mph.
00057 //
00058 // The peak interrupt rate of 19us is well within the KL25Z's comfort
00059 // zone, as long as we take reasonable measures to minimize latency.  In
00060 // particular, we have to elevate the GPIO port IRQ priority above all
00061 // other hardware interrupts.  That's vital because there are some
00062 // relatively long-running interrupt handlers in the system, particularly
00063 // the USB handlers and the microsecond timer.  It's also vital to keep
00064 // other GPIO interrupt handlers very fast, since the ports all share
00065 // a priority level and thus can't preempt one another.  Fortunately, the
00066 // rest of the Pinscape system make very little use of GPIO interrupts;
00067 // the only current use is in the IR receiver, and that code is designed 
00068 // to do minimal work in IRQ context.
00069 //
00070 // We use our custom FastInterruptIn class instead of the original mbed
00071 // InterruptIn.  FastInterruptIn gives us a modest speed improvement: it
00072 // has a measured overhead time per interrupt of about 6.5us compared with
00073 // the mbed libary's 8.9us, which gives us a maximum interrupt rate of
00074 // about 159kHz vs mbed's 112kHz.  The AEDR-8300's maximum 19us is well
00075 // within both limits, but FastInterruptIn gives us a little more headroom
00076 // for substituting other sensors with higher pulse rates.
00077 //
00078 
00079 #ifndef _QUADSENSOR_H_
00080 #define _QUADSENSOR_H_
00081 
00082 #include "FastInterruptIn.h"
00083 
00084 class PlungerSensorQuad: public PlungerSensor
00085 {
00086 public:
00087     // Construct.
00088     //
00089     // 'dpi' is the approximate number of dots per inch of linear travel
00090     // that the sensor can distinguish.  This is equivalent to the number
00091     // of pulses it generates per inch.  This doesn't have to be exact,
00092     // since the main loop rescales it anyway via calibration.  But it's
00093     // helpful to have the approximate figure so that we can scale the
00094     // raw data readings appropriately for the interface datatypes.
00095     //
00096     // For the native scale, we'll assume a 4" range at our dpi rating.
00097     // The actual plunger travel is constrainted to about a 3" range, but
00098     // we want to leave a little extra padding to reduce the chances of
00099     // going out of range in unusual situations.
00100     PlungerSensorQuad(int dpi, PinName pinA, PinName pinB) 
00101         : PlungerSensor(dpi*4),
00102           chA(pinA), chB(pinB)
00103     {   
00104         // Use 1" as the reference park position
00105         parkPos = dpi;
00106         
00107         // start at the park position
00108         pos = parkPos;
00109           
00110         // get the initial pin states
00111         st = (chA.read() ? 0x01 : 0x00) 
00112              | (chB.read() ? 0x02 : 0x00);
00113         
00114         // set up the interrupt handlers
00115         chA.rise(&PlungerSensorQuad::aUp, this);
00116         chA.fall(&PlungerSensorQuad::aDown, this);
00117         chB.rise(&PlungerSensorQuad::bUp, this);
00118         chB.fall(&PlungerSensorQuad::bDown, this);
00119 
00120         // start our sample timer with an arbitrary zero point of now
00121         timer.start();
00122     }
00123     
00124     // Auto-zero.  Return to the park position.  If we're using reverse
00125     // orientation, go to the park position distance from the top end
00126     // of the scale.
00127     virtual void autoZero()
00128     {
00129         pos = reverseOrientation ? nativeScale - parkPos : parkPos;
00130     }
00131         
00132     // Begin calibration.  We can assume that the plunger is at the
00133     // park position when calibration starts, so perform an explicit
00134     // auto-zeroing operation.
00135     virtual void beginCalibration(Config &)
00136     {
00137         autoZero();
00138     }
00139     
00140     // read the sensor
00141     virtual bool readRaw(PlungerReading &r)
00142     {
00143         // Get the current position in native units
00144         r.pos = pos;
00145         
00146         // Set the timestamp on the reading to right now.  Our internal
00147         // position counter reflects the position in real time, since it's
00148         // updated in the interrupt handlers for the change signals from
00149         // the sensor.
00150         r.t = timer.read_us();
00151         
00152         // success
00153         return true;
00154     }
00155     
00156     virtual void sendStatusReport(class USBJoystick &js, uint8_t flags)
00157     {
00158         // send the common status report
00159         PlungerSensor::sendStatusReport(js, flags);
00160 
00161         // send the extra quadrature sensor status report
00162         js.sendPlungerStatusQuadrature((st & 0x01) != 0, (st & 0x02) != 0);
00163     }
00164     
00165     // figure the average scan time in microseconds
00166     virtual uint32_t getAvgScanTime() 
00167     { 
00168         // we're updated by interrupts rather than scanning, so our
00169         // "scan time" is exactly zero
00170         return 0;
00171     }
00172     
00173 private:
00174     // interrupt inputs for our channel pins
00175     FastInterruptIn chA, chB;
00176     
00177     // current position - this is the cumulate counter for all
00178     // transitions so far
00179     int pos;
00180     
00181     // Park position.  This is essentially arbitrary, since our readings
00182     // are entirely relative, but for interface purposes we have to keep 
00183     // our raw readings positive.  We need an initial park position that's
00184     // non-zero so that plunger motion forward of the park position remains
00185     // positive.
00186     int parkPos;
00187     
00188     // Channel state on last read.  This is a bit vector combining
00189     // the two channel states:
00190     //   0x01 = channel A state
00191     //   0x02 = channel B state
00192     uint8_t st;
00193     
00194     // interrupt handlers
00195     static void aUp(void *obj) { 
00196         PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
00197         self->transition(self->st | 0x01); 
00198     }
00199     static void aDown(void *obj) { 
00200         PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
00201         self->transition(self->st & 0x02); 
00202     }
00203     static void bUp(void *obj) {
00204         PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
00205         self->transition(self->st | 0x02); 
00206     }
00207     static void bDown(void *obj) { 
00208         PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
00209         self->transition(self->st & 0x01); 
00210     }
00211     
00212     // Transition handler.  The interrupt handlers call this, so
00213     // it's critical that this run as fast as possible.  The observed
00214     // peak interrupt rate is one interrupt per 19us.  Fortunately, 
00215     // our work here is simple:  we just have to count the pulse in 
00216     // the appropriate direction according to the state transition 
00217     // that the pulse represents.  We can do this with a simple table 
00218     // lookup.
00219     inline void transition(int stNew)
00220     {
00221         // Transition matrix: dir[n][m] gives the direction of 
00222         // motion when we switch from state 'n' to state 'm'.
00223         // The state number is formed by the two-bit number B:A,
00224         // where each bit is 1 if the channel pulse is on and 0
00225         // if the channel pulse is off.  E.g., if chA is OFF and 
00226         // chB is ON, B:A = 1:0, so the state number is 0b10 = 2.
00227         // Slots with 'NV' are Not Valid: it's impossible to make 
00228         // this transition (unless we missed an interrupt).  'NC'
00229         // means No Change; these are the slots on the matrix
00230         // diagonal, which represent the same state on both input
00231         // and output.  Like NV transitions, NC transitions should
00232         // never happen, in this case because no interrupt should
00233         // be generated when nothing has changed.
00234         const int NV = 0, NC = 0;
00235         static const int dir[][4] = {
00236             { NC,  1, -1, NV },
00237             { -1, NC, NV,  1 },
00238             {  1, NV, NC, -1 },
00239             { NV, -1,  1, NC }
00240         };
00241         
00242         // increment or decrement the position counter by one notch, 
00243         // according to the direction of motion implied by the transition
00244         pos += dir[st][stNew];
00245 
00246         // the new state is now the current state
00247         st = stNew;
00248     }
00249     
00250     // timer for input timestamps
00251     Timer timer;
00252 };
00253 
00254 #endif