Arnaud VALLEY / Mbed 2 deprecated Pinscape_Controller_V2_arnoz

Dependencies:   mbed FastIO FastPWM USBDevice

Revision:
82:4f6209cb5c33
Child:
86:e30a1f60f783
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plunger/quadSensor.h	Thu Apr 13 23:20:28 2017 +0000
@@ -0,0 +1,269 @@
+// AEDR-8300-1K2 optical encoder / generic quadrature sensor plunger 
+// implementation
+//
+// This class implements the Pinscape plunger interface for the 
+// AEDR-8300-1K2 optical encoder in particular, and quadrature sensors 
+// in general.  The code was written specifically for the AEDR-8300-1K2,
+// but it should work with any other quadrature sensor that's electrically 
+// compatible and that doesn't exceed the maximum interrupt rate we can 
+// handle on the KL25Z.  To be electrically compatible, the device must 
+// be 3.3V compatible, have logic type outputs (basically square waves
+// for the signals), and provide two outputs 90 degrees out of phase.  
+// The maximum interrupt rate that the KL25Z can handle (with our 
+// FastInterruptIn class) is about 150 kHz.
+//
+// A quadrature sensor works by detecting transitions along a bar-coded 
+// scale.  Most position encoders (including the AEDR-8300) are optical,
+// but the same principle can be used with other technologies, such as
+// magnetic pole strips.  Whatever the underlying physical "bar" type,
+// the device detects transitions between the bars and the spaces between
+// the bars and relays them to the microcontroller via its outputs.  A
+// quadrature device actually consists of two such sensors, slightly offset
+// from each other relative to the direction of motion of the scale, so 
+// that their bar transitions are 90 degrees out of phase.  The phase
+// shift in the two signals is what allows the microcontroller to sense
+// the direction of motion.  The controller figures the current position
+// by counting bar transitions (incrementing the count when moving in one
+// direction and decrement it in the other direction), so it knows the 
+// location at any given time as an offset in units of bar widths from the
+// starting position.  The position reading is always relative, because
+// we can only count up or down from the initial point.
+//
+// In many applications involving quadrature sensors, the relative 
+// quadrature reading is augmented with a separate sensor for absolute 
+// positioning.  This is usually something simple and low-res, like an 
+// end-of-stroke switch or a zero-crossing switch.  The idea is that you 
+// use the low-res absolute sensor to tell when you're at a known reference 
+// point, and then use the high-res quadrature data to get the precise 
+// location relative to the reference point.  To keep things simple, we 
+// don't use any such supplemental absolute sensor.  It's not really 
+// necessary for a plunger, because a plunger has the special property 
+// that it always returns to the same point when not being manipulated.  
+// It's almost as good as having a sensor at the park position, because
+// even though we can't know for sure the plunger is there at any given
+// time, it's a good bet that that's where it is at startup and any time
+// we haven't seen any motion in a while.  Note that we could easily add 
+// support in the software for some kind of absolute sensing if it became 
+// desirable; the only challenge is the complexity it would add to the
+// physical system.
+//
+// The AEDR-8300 lets us collect some very precise data on the 
+// instantaneous speed of the plunger thanks to its high resolution and
+// real-time position updates.  The shortest observed time between pulses 
+// (so far, with my test rig) is 19us.  Pulses are generated at 4 per
+// bar, with bars at 75 per inch, yielding 300 pulses per inch.  The 19us
+// pulse time translates to an instantaneous plunger speed of 0.175 
+// inches/millisecond, or 4.46 mm/ms, or 4.46 m/s, or 9.97 mph.
+//
+// The peak interrupt rate of 19us is well within the KL25Z's comfort
+// zone, as long as we take reasonable measures to minimize latency.  In
+// particular, we have to elevate the GPIO port IRQ priority above all
+// other hardware interrupts.  That's vital because there are some
+// relatively long-running interrupt handlers in the system, particularly
+// the USB handlers and the microsecond timer.  It's also vital to keep
+// other GPIO interrupt handlers very fast, since the ports all share
+// a priority level and thus can't preempt one another.  Fortunately, the
+// rest of the Pinscape system make very little use of GPIO interrupts;
+// the only current use is in the IR receiver, and that code is designed 
+// to do minimal work in IRQ context.
+//
+// We use our custom FastInterruptIn class instead of the original mbed
+// InterruptIn.  FastInterruptIn gives us a modest speed improvement: it
+// has a measured overhead time per interrupt of about 6.5us compared with
+// the mbed libary's 8.9us, which gives us a maximum interrupt rate of
+// about 159kHz vs mbed's 112kHz.  The AEDR-8300's maximum 19us is well
+// within both limits, but FastInterruptIn gives us a little more headroom
+// for substituting other sensors with higher pulse rates.
+//
+
+#ifndef _QUADSENSOR_H_
+#define _QUADSENSOR_H_
+
+#include "FastInterruptIn.h"
+
+class PlungerSensorQuad: public PlungerSensor
+{
+public:
+    // Construct.
+    //
+    // 'dpi' is the approximate number of dots per inch of linear travel
+    // that the sensor can distinguish.  This is equivalent to the number
+    // of pulses it generates per inch.  This doesn't have to be exact,
+    // since the main loop rescales it anyway via calibration.  But it's
+    // helpful to have the approximate figure so that we can scale the
+    // raw data readings appropriately for the interface datatypes.
+    PlungerSensorQuad(int dpi, PinName pinA, PinName pinB) 
+        : chA(pinA), chB(pinB)
+    {   
+        // remember the dpi setting
+        this->dpi = dpi;
+        
+        // Use 1" as the reference park position
+        parkPos = dpi;
+        
+        // Figure the scale factor for reports.  We want a 3" range to
+        // mostly cover the 16-bit unsigned range (0..65535) of the read()
+        // reports.  To leave a little cushion to avoid overflow, figure
+        // the actual factor using a 4" range.
+        posScale = 65535/(dpi*4);
+        
+        // start at the park position
+        pos = parkPos;
+          
+        // get the initial pin states
+        st = (chA.read() ? 0x01 : 0x00) 
+             | (chB.read() ? 0x02 : 0x00);
+        
+        // set up the interrupt handlers
+        chA.rise(&PlungerSensorQuad::aUp, this);
+        chA.fall(&PlungerSensorQuad::aDown, this);
+        chB.rise(&PlungerSensorQuad::bUp, this);
+        chB.fall(&PlungerSensorQuad::bDown, this);
+
+        // start our sample timer with an arbitrary zero point of now
+        timer.start();
+    }
+    
+    // Auto-zero.  Return to the park position
+    virtual void autoZero()
+    {
+        pos = parkPos;
+    }
+        
+    // Begin calibration.  We can assume that the plunger is at the
+    // park position when calibration starts.
+    virtual void beginCalibration()
+    {
+        pos = parkPos;
+    }
+    
+    // read the sensor
+    virtual bool read(PlungerReading &r)
+    {
+        // Return the current position, adjusted for our dpi scaling
+        r.pos = uint16_t(pos * posScale);
+        
+        // Set the timestamp on the reading to right now.  Our internal
+        // position counter reflects the position in real time, since it's
+        // updated in the interrupt handlers for the change signals from
+        // the sensor.
+        r.t = timer.read_us();
+        
+        // success
+        return true;
+    }
+    
+    // figure the average scan time in microseconds
+    virtual uint32_t getAvgScanTime() 
+    { 
+        // we're updated by interrupts rather than scanning, so our
+        // "scan time" is exactly zero
+        return 0;
+    }
+        
+private:
+    // interrupt inputs for our channel pins
+    FastInterruptIn chA, chB;
+    
+    // "Dots per inch" for the sensor.  This reflects the approximate
+    // number of quadrature transition pulses we expect per inch of
+    // physical travel.  This is usually a function of the "scale" 
+    // (the reference guide that the sensor moves across to sense
+    // its motion).  Quadrature sensors usually generate four pulses
+    // per "bar/window pair" on the scale, and the scale is usually
+    // measured in terms of "lines per inch" (or something analogous
+    // for non-optical sensors, such as "poles per inch" for a magnetic 
+    // sensor).  So the effective "dots per inch" is usually equal to
+    // 4x the scale marks per inch.  It's not critical for us to know
+    // the exact dpi rating, since the main loop rescales the raw 
+    // readings via its calibration mechanism, but it's helpful to
+    // know at least an approximate dpi so that the raw readings fit
+    // in the interface datatypes.
+    int dpi;
+    
+    // Position report scaling factor.  The read() interface uses 16-bit
+    // ints, so we need to report positions on a 0..65535 scale.  For
+    // maximum precision in downstream calculations, we should use as
+    // much of the range as possible, so we need to rescale our raw
+    // readings to fill the range.  We figure this based on our sensor
+    // dpi.
+    int posScale;
+    
+    // current position - this is the cumulate counter for all
+    // transitions so far
+    int pos;
+    
+    // Park position.  This is essentially arbitrary, since our readings
+    // are entirely relative, but for interface purposes we have to keep 
+    // our raw readings positive.  We need an initial park position that's
+    // non-zero so that plunger motion forward of the park position remains
+    // positive.
+    int parkPos;
+    
+    // Channel state on last read.  This is a bit vector combining
+    // the two channel states:
+    //   0x01 = channel A state
+    //   0x02 = channel B state
+    uint8_t st;
+    
+    // interrupt handlers
+    static void aUp(void *obj) { 
+        PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
+        self->transition(self->st | 0x01); 
+    }
+    static void aDown(void *obj) { 
+        PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
+        self->transition(self->st & 0x02); 
+    }
+    static void bUp(void *obj) {
+        PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
+        self->transition(self->st | 0x02); 
+    }
+    static void bDown(void *obj) { 
+        PlungerSensorQuad *self = (PlungerSensorQuad *)obj; 
+        self->transition(self->st & 0x01); 
+    }
+    
+    // Transition handler.  The interrupt handlers call this, so
+    // it's critical that this run as fast as possible.  The observed
+    // peak interrupt rate is one interrupt per 19us.  Fortunately, 
+    // our work here is simple:  we just have to count the pulse in 
+    // the appropriate direction according to the state transition 
+    // that the pulse represents.  We can do this with a simple table 
+    // lookup.
+    inline void transition(int stNew)
+    {
+        // Transition matrix: this gives the direction of motion
+        // when we switch from state dir[n] to state dir[n][m].
+        // The state number is formed by the two-bit number B:A,
+        // where each bit is 1 if the channel pulse is on and 0
+        // if the channel pulse is off.  E.g., if chA is OFF and 
+        // chB is ON, B:A = 1:0, so the state number is 0b10 = 2.
+        // Slots with 'NV' are Not Valid: it's impossible to make 
+        // this transition (unless we missed an interrupt).  'NC'
+        // means No Change; these are the slots on the matrix
+        // diagonal, which represent the same state on both input
+        // and output.  Like NV transitions, NC transitions should
+        // never happen, in this case because no interrupt should
+        // be generated when nothing has changed.
+        const int NV = 0, NC = 0;
+        static const int dir[][4] = {
+            { NC,  1, -1, NV },
+            { -1, NC, NV,  1 },
+            {  1, NV, NC, -1 },
+            { NV, -1,  1, NC }
+        };
+        
+        // increment or decrement the position counter by one notch, 
+        // according to the direction of motion implied by the transition
+        pos += dir[st][stNew];
+
+        // the new state is now the current state
+        st = stNew;
+    }
+    
+    // timer for input timestamps
+    Timer timer;
+};
+
+#endif