Arnaud VALLEY / Mbed 2 deprecated Pinscape_Controller_V2_arnoz

Dependencies:   mbed FastIO FastPWM USBDevice

Committer:
mjr
Date:
Thu Apr 13 23:20:28 2017 +0000
Revision:
82:4f6209cb5c33
Child:
86:e30a1f60f783
Plunger refactoring; AEDR-8300 added; TSL1401CL in progress; VL6180X added

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 82:4f6209cb5c33 1 // AEDR-8300-1K2 optical encoder / generic quadrature sensor plunger
mjr 82:4f6209cb5c33 2 // implementation
mjr 82:4f6209cb5c33 3 //
mjr 82:4f6209cb5c33 4 // This class implements the Pinscape plunger interface for the
mjr 82:4f6209cb5c33 5 // AEDR-8300-1K2 optical encoder in particular, and quadrature sensors
mjr 82:4f6209cb5c33 6 // in general. The code was written specifically for the AEDR-8300-1K2,
mjr 82:4f6209cb5c33 7 // but it should work with any other quadrature sensor that's electrically
mjr 82:4f6209cb5c33 8 // compatible and that doesn't exceed the maximum interrupt rate we can
mjr 82:4f6209cb5c33 9 // handle on the KL25Z. To be electrically compatible, the device must
mjr 82:4f6209cb5c33 10 // be 3.3V compatible, have logic type outputs (basically square waves
mjr 82:4f6209cb5c33 11 // for the signals), and provide two outputs 90 degrees out of phase.
mjr 82:4f6209cb5c33 12 // The maximum interrupt rate that the KL25Z can handle (with our
mjr 82:4f6209cb5c33 13 // FastInterruptIn class) is about 150 kHz.
mjr 82:4f6209cb5c33 14 //
mjr 82:4f6209cb5c33 15 // A quadrature sensor works by detecting transitions along a bar-coded
mjr 82:4f6209cb5c33 16 // scale. Most position encoders (including the AEDR-8300) are optical,
mjr 82:4f6209cb5c33 17 // but the same principle can be used with other technologies, such as
mjr 82:4f6209cb5c33 18 // magnetic pole strips. Whatever the underlying physical "bar" type,
mjr 82:4f6209cb5c33 19 // the device detects transitions between the bars and the spaces between
mjr 82:4f6209cb5c33 20 // the bars and relays them to the microcontroller via its outputs. A
mjr 82:4f6209cb5c33 21 // quadrature device actually consists of two such sensors, slightly offset
mjr 82:4f6209cb5c33 22 // from each other relative to the direction of motion of the scale, so
mjr 82:4f6209cb5c33 23 // that their bar transitions are 90 degrees out of phase. The phase
mjr 82:4f6209cb5c33 24 // shift in the two signals is what allows the microcontroller to sense
mjr 82:4f6209cb5c33 25 // the direction of motion. The controller figures the current position
mjr 82:4f6209cb5c33 26 // by counting bar transitions (incrementing the count when moving in one
mjr 82:4f6209cb5c33 27 // direction and decrement it in the other direction), so it knows the
mjr 82:4f6209cb5c33 28 // location at any given time as an offset in units of bar widths from the
mjr 82:4f6209cb5c33 29 // starting position. The position reading is always relative, because
mjr 82:4f6209cb5c33 30 // we can only count up or down from the initial point.
mjr 82:4f6209cb5c33 31 //
mjr 82:4f6209cb5c33 32 // In many applications involving quadrature sensors, the relative
mjr 82:4f6209cb5c33 33 // quadrature reading is augmented with a separate sensor for absolute
mjr 82:4f6209cb5c33 34 // positioning. This is usually something simple and low-res, like an
mjr 82:4f6209cb5c33 35 // end-of-stroke switch or a zero-crossing switch. The idea is that you
mjr 82:4f6209cb5c33 36 // use the low-res absolute sensor to tell when you're at a known reference
mjr 82:4f6209cb5c33 37 // point, and then use the high-res quadrature data to get the precise
mjr 82:4f6209cb5c33 38 // location relative to the reference point. To keep things simple, we
mjr 82:4f6209cb5c33 39 // don't use any such supplemental absolute sensor. It's not really
mjr 82:4f6209cb5c33 40 // necessary for a plunger, because a plunger has the special property
mjr 82:4f6209cb5c33 41 // that it always returns to the same point when not being manipulated.
mjr 82:4f6209cb5c33 42 // It's almost as good as having a sensor at the park position, because
mjr 82:4f6209cb5c33 43 // even though we can't know for sure the plunger is there at any given
mjr 82:4f6209cb5c33 44 // time, it's a good bet that that's where it is at startup and any time
mjr 82:4f6209cb5c33 45 // we haven't seen any motion in a while. Note that we could easily add
mjr 82:4f6209cb5c33 46 // support in the software for some kind of absolute sensing if it became
mjr 82:4f6209cb5c33 47 // desirable; the only challenge is the complexity it would add to the
mjr 82:4f6209cb5c33 48 // physical system.
mjr 82:4f6209cb5c33 49 //
mjr 82:4f6209cb5c33 50 // The AEDR-8300 lets us collect some very precise data on the
mjr 82:4f6209cb5c33 51 // instantaneous speed of the plunger thanks to its high resolution and
mjr 82:4f6209cb5c33 52 // real-time position updates. The shortest observed time between pulses
mjr 82:4f6209cb5c33 53 // (so far, with my test rig) is 19us. Pulses are generated at 4 per
mjr 82:4f6209cb5c33 54 // bar, with bars at 75 per inch, yielding 300 pulses per inch. The 19us
mjr 82:4f6209cb5c33 55 // pulse time translates to an instantaneous plunger speed of 0.175
mjr 82:4f6209cb5c33 56 // inches/millisecond, or 4.46 mm/ms, or 4.46 m/s, or 9.97 mph.
mjr 82:4f6209cb5c33 57 //
mjr 82:4f6209cb5c33 58 // The peak interrupt rate of 19us is well within the KL25Z's comfort
mjr 82:4f6209cb5c33 59 // zone, as long as we take reasonable measures to minimize latency. In
mjr 82:4f6209cb5c33 60 // particular, we have to elevate the GPIO port IRQ priority above all
mjr 82:4f6209cb5c33 61 // other hardware interrupts. That's vital because there are some
mjr 82:4f6209cb5c33 62 // relatively long-running interrupt handlers in the system, particularly
mjr 82:4f6209cb5c33 63 // the USB handlers and the microsecond timer. It's also vital to keep
mjr 82:4f6209cb5c33 64 // other GPIO interrupt handlers very fast, since the ports all share
mjr 82:4f6209cb5c33 65 // a priority level and thus can't preempt one another. Fortunately, the
mjr 82:4f6209cb5c33 66 // rest of the Pinscape system make very little use of GPIO interrupts;
mjr 82:4f6209cb5c33 67 // the only current use is in the IR receiver, and that code is designed
mjr 82:4f6209cb5c33 68 // to do minimal work in IRQ context.
mjr 82:4f6209cb5c33 69 //
mjr 82:4f6209cb5c33 70 // We use our custom FastInterruptIn class instead of the original mbed
mjr 82:4f6209cb5c33 71 // InterruptIn. FastInterruptIn gives us a modest speed improvement: it
mjr 82:4f6209cb5c33 72 // has a measured overhead time per interrupt of about 6.5us compared with
mjr 82:4f6209cb5c33 73 // the mbed libary's 8.9us, which gives us a maximum interrupt rate of
mjr 82:4f6209cb5c33 74 // about 159kHz vs mbed's 112kHz. The AEDR-8300's maximum 19us is well
mjr 82:4f6209cb5c33 75 // within both limits, but FastInterruptIn gives us a little more headroom
mjr 82:4f6209cb5c33 76 // for substituting other sensors with higher pulse rates.
mjr 82:4f6209cb5c33 77 //
mjr 82:4f6209cb5c33 78
mjr 82:4f6209cb5c33 79 #ifndef _QUADSENSOR_H_
mjr 82:4f6209cb5c33 80 #define _QUADSENSOR_H_
mjr 82:4f6209cb5c33 81
mjr 82:4f6209cb5c33 82 #include "FastInterruptIn.h"
mjr 82:4f6209cb5c33 83
mjr 82:4f6209cb5c33 84 class PlungerSensorQuad: public PlungerSensor
mjr 82:4f6209cb5c33 85 {
mjr 82:4f6209cb5c33 86 public:
mjr 82:4f6209cb5c33 87 // Construct.
mjr 82:4f6209cb5c33 88 //
mjr 82:4f6209cb5c33 89 // 'dpi' is the approximate number of dots per inch of linear travel
mjr 82:4f6209cb5c33 90 // that the sensor can distinguish. This is equivalent to the number
mjr 82:4f6209cb5c33 91 // of pulses it generates per inch. This doesn't have to be exact,
mjr 82:4f6209cb5c33 92 // since the main loop rescales it anyway via calibration. But it's
mjr 82:4f6209cb5c33 93 // helpful to have the approximate figure so that we can scale the
mjr 82:4f6209cb5c33 94 // raw data readings appropriately for the interface datatypes.
mjr 82:4f6209cb5c33 95 PlungerSensorQuad(int dpi, PinName pinA, PinName pinB)
mjr 82:4f6209cb5c33 96 : chA(pinA), chB(pinB)
mjr 82:4f6209cb5c33 97 {
mjr 82:4f6209cb5c33 98 // remember the dpi setting
mjr 82:4f6209cb5c33 99 this->dpi = dpi;
mjr 82:4f6209cb5c33 100
mjr 82:4f6209cb5c33 101 // Use 1" as the reference park position
mjr 82:4f6209cb5c33 102 parkPos = dpi;
mjr 82:4f6209cb5c33 103
mjr 82:4f6209cb5c33 104 // Figure the scale factor for reports. We want a 3" range to
mjr 82:4f6209cb5c33 105 // mostly cover the 16-bit unsigned range (0..65535) of the read()
mjr 82:4f6209cb5c33 106 // reports. To leave a little cushion to avoid overflow, figure
mjr 82:4f6209cb5c33 107 // the actual factor using a 4" range.
mjr 82:4f6209cb5c33 108 posScale = 65535/(dpi*4);
mjr 82:4f6209cb5c33 109
mjr 82:4f6209cb5c33 110 // start at the park position
mjr 82:4f6209cb5c33 111 pos = parkPos;
mjr 82:4f6209cb5c33 112
mjr 82:4f6209cb5c33 113 // get the initial pin states
mjr 82:4f6209cb5c33 114 st = (chA.read() ? 0x01 : 0x00)
mjr 82:4f6209cb5c33 115 | (chB.read() ? 0x02 : 0x00);
mjr 82:4f6209cb5c33 116
mjr 82:4f6209cb5c33 117 // set up the interrupt handlers
mjr 82:4f6209cb5c33 118 chA.rise(&PlungerSensorQuad::aUp, this);
mjr 82:4f6209cb5c33 119 chA.fall(&PlungerSensorQuad::aDown, this);
mjr 82:4f6209cb5c33 120 chB.rise(&PlungerSensorQuad::bUp, this);
mjr 82:4f6209cb5c33 121 chB.fall(&PlungerSensorQuad::bDown, this);
mjr 82:4f6209cb5c33 122
mjr 82:4f6209cb5c33 123 // start our sample timer with an arbitrary zero point of now
mjr 82:4f6209cb5c33 124 timer.start();
mjr 82:4f6209cb5c33 125 }
mjr 82:4f6209cb5c33 126
mjr 82:4f6209cb5c33 127 // Auto-zero. Return to the park position
mjr 82:4f6209cb5c33 128 virtual void autoZero()
mjr 82:4f6209cb5c33 129 {
mjr 82:4f6209cb5c33 130 pos = parkPos;
mjr 82:4f6209cb5c33 131 }
mjr 82:4f6209cb5c33 132
mjr 82:4f6209cb5c33 133 // Begin calibration. We can assume that the plunger is at the
mjr 82:4f6209cb5c33 134 // park position when calibration starts.
mjr 82:4f6209cb5c33 135 virtual void beginCalibration()
mjr 82:4f6209cb5c33 136 {
mjr 82:4f6209cb5c33 137 pos = parkPos;
mjr 82:4f6209cb5c33 138 }
mjr 82:4f6209cb5c33 139
mjr 82:4f6209cb5c33 140 // read the sensor
mjr 82:4f6209cb5c33 141 virtual bool read(PlungerReading &r)
mjr 82:4f6209cb5c33 142 {
mjr 82:4f6209cb5c33 143 // Return the current position, adjusted for our dpi scaling
mjr 82:4f6209cb5c33 144 r.pos = uint16_t(pos * posScale);
mjr 82:4f6209cb5c33 145
mjr 82:4f6209cb5c33 146 // Set the timestamp on the reading to right now. Our internal
mjr 82:4f6209cb5c33 147 // position counter reflects the position in real time, since it's
mjr 82:4f6209cb5c33 148 // updated in the interrupt handlers for the change signals from
mjr 82:4f6209cb5c33 149 // the sensor.
mjr 82:4f6209cb5c33 150 r.t = timer.read_us();
mjr 82:4f6209cb5c33 151
mjr 82:4f6209cb5c33 152 // success
mjr 82:4f6209cb5c33 153 return true;
mjr 82:4f6209cb5c33 154 }
mjr 82:4f6209cb5c33 155
mjr 82:4f6209cb5c33 156 // figure the average scan time in microseconds
mjr 82:4f6209cb5c33 157 virtual uint32_t getAvgScanTime()
mjr 82:4f6209cb5c33 158 {
mjr 82:4f6209cb5c33 159 // we're updated by interrupts rather than scanning, so our
mjr 82:4f6209cb5c33 160 // "scan time" is exactly zero
mjr 82:4f6209cb5c33 161 return 0;
mjr 82:4f6209cb5c33 162 }
mjr 82:4f6209cb5c33 163
mjr 82:4f6209cb5c33 164 private:
mjr 82:4f6209cb5c33 165 // interrupt inputs for our channel pins
mjr 82:4f6209cb5c33 166 FastInterruptIn chA, chB;
mjr 82:4f6209cb5c33 167
mjr 82:4f6209cb5c33 168 // "Dots per inch" for the sensor. This reflects the approximate
mjr 82:4f6209cb5c33 169 // number of quadrature transition pulses we expect per inch of
mjr 82:4f6209cb5c33 170 // physical travel. This is usually a function of the "scale"
mjr 82:4f6209cb5c33 171 // (the reference guide that the sensor moves across to sense
mjr 82:4f6209cb5c33 172 // its motion). Quadrature sensors usually generate four pulses
mjr 82:4f6209cb5c33 173 // per "bar/window pair" on the scale, and the scale is usually
mjr 82:4f6209cb5c33 174 // measured in terms of "lines per inch" (or something analogous
mjr 82:4f6209cb5c33 175 // for non-optical sensors, such as "poles per inch" for a magnetic
mjr 82:4f6209cb5c33 176 // sensor). So the effective "dots per inch" is usually equal to
mjr 82:4f6209cb5c33 177 // 4x the scale marks per inch. It's not critical for us to know
mjr 82:4f6209cb5c33 178 // the exact dpi rating, since the main loop rescales the raw
mjr 82:4f6209cb5c33 179 // readings via its calibration mechanism, but it's helpful to
mjr 82:4f6209cb5c33 180 // know at least an approximate dpi so that the raw readings fit
mjr 82:4f6209cb5c33 181 // in the interface datatypes.
mjr 82:4f6209cb5c33 182 int dpi;
mjr 82:4f6209cb5c33 183
mjr 82:4f6209cb5c33 184 // Position report scaling factor. The read() interface uses 16-bit
mjr 82:4f6209cb5c33 185 // ints, so we need to report positions on a 0..65535 scale. For
mjr 82:4f6209cb5c33 186 // maximum precision in downstream calculations, we should use as
mjr 82:4f6209cb5c33 187 // much of the range as possible, so we need to rescale our raw
mjr 82:4f6209cb5c33 188 // readings to fill the range. We figure this based on our sensor
mjr 82:4f6209cb5c33 189 // dpi.
mjr 82:4f6209cb5c33 190 int posScale;
mjr 82:4f6209cb5c33 191
mjr 82:4f6209cb5c33 192 // current position - this is the cumulate counter for all
mjr 82:4f6209cb5c33 193 // transitions so far
mjr 82:4f6209cb5c33 194 int pos;
mjr 82:4f6209cb5c33 195
mjr 82:4f6209cb5c33 196 // Park position. This is essentially arbitrary, since our readings
mjr 82:4f6209cb5c33 197 // are entirely relative, but for interface purposes we have to keep
mjr 82:4f6209cb5c33 198 // our raw readings positive. We need an initial park position that's
mjr 82:4f6209cb5c33 199 // non-zero so that plunger motion forward of the park position remains
mjr 82:4f6209cb5c33 200 // positive.
mjr 82:4f6209cb5c33 201 int parkPos;
mjr 82:4f6209cb5c33 202
mjr 82:4f6209cb5c33 203 // Channel state on last read. This is a bit vector combining
mjr 82:4f6209cb5c33 204 // the two channel states:
mjr 82:4f6209cb5c33 205 // 0x01 = channel A state
mjr 82:4f6209cb5c33 206 // 0x02 = channel B state
mjr 82:4f6209cb5c33 207 uint8_t st;
mjr 82:4f6209cb5c33 208
mjr 82:4f6209cb5c33 209 // interrupt handlers
mjr 82:4f6209cb5c33 210 static void aUp(void *obj) {
mjr 82:4f6209cb5c33 211 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 212 self->transition(self->st | 0x01);
mjr 82:4f6209cb5c33 213 }
mjr 82:4f6209cb5c33 214 static void aDown(void *obj) {
mjr 82:4f6209cb5c33 215 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 216 self->transition(self->st & 0x02);
mjr 82:4f6209cb5c33 217 }
mjr 82:4f6209cb5c33 218 static void bUp(void *obj) {
mjr 82:4f6209cb5c33 219 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 220 self->transition(self->st | 0x02);
mjr 82:4f6209cb5c33 221 }
mjr 82:4f6209cb5c33 222 static void bDown(void *obj) {
mjr 82:4f6209cb5c33 223 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 224 self->transition(self->st & 0x01);
mjr 82:4f6209cb5c33 225 }
mjr 82:4f6209cb5c33 226
mjr 82:4f6209cb5c33 227 // Transition handler. The interrupt handlers call this, so
mjr 82:4f6209cb5c33 228 // it's critical that this run as fast as possible. The observed
mjr 82:4f6209cb5c33 229 // peak interrupt rate is one interrupt per 19us. Fortunately,
mjr 82:4f6209cb5c33 230 // our work here is simple: we just have to count the pulse in
mjr 82:4f6209cb5c33 231 // the appropriate direction according to the state transition
mjr 82:4f6209cb5c33 232 // that the pulse represents. We can do this with a simple table
mjr 82:4f6209cb5c33 233 // lookup.
mjr 82:4f6209cb5c33 234 inline void transition(int stNew)
mjr 82:4f6209cb5c33 235 {
mjr 82:4f6209cb5c33 236 // Transition matrix: this gives the direction of motion
mjr 82:4f6209cb5c33 237 // when we switch from state dir[n] to state dir[n][m].
mjr 82:4f6209cb5c33 238 // The state number is formed by the two-bit number B:A,
mjr 82:4f6209cb5c33 239 // where each bit is 1 if the channel pulse is on and 0
mjr 82:4f6209cb5c33 240 // if the channel pulse is off. E.g., if chA is OFF and
mjr 82:4f6209cb5c33 241 // chB is ON, B:A = 1:0, so the state number is 0b10 = 2.
mjr 82:4f6209cb5c33 242 // Slots with 'NV' are Not Valid: it's impossible to make
mjr 82:4f6209cb5c33 243 // this transition (unless we missed an interrupt). 'NC'
mjr 82:4f6209cb5c33 244 // means No Change; these are the slots on the matrix
mjr 82:4f6209cb5c33 245 // diagonal, which represent the same state on both input
mjr 82:4f6209cb5c33 246 // and output. Like NV transitions, NC transitions should
mjr 82:4f6209cb5c33 247 // never happen, in this case because no interrupt should
mjr 82:4f6209cb5c33 248 // be generated when nothing has changed.
mjr 82:4f6209cb5c33 249 const int NV = 0, NC = 0;
mjr 82:4f6209cb5c33 250 static const int dir[][4] = {
mjr 82:4f6209cb5c33 251 { NC, 1, -1, NV },
mjr 82:4f6209cb5c33 252 { -1, NC, NV, 1 },
mjr 82:4f6209cb5c33 253 { 1, NV, NC, -1 },
mjr 82:4f6209cb5c33 254 { NV, -1, 1, NC }
mjr 82:4f6209cb5c33 255 };
mjr 82:4f6209cb5c33 256
mjr 82:4f6209cb5c33 257 // increment or decrement the position counter by one notch,
mjr 82:4f6209cb5c33 258 // according to the direction of motion implied by the transition
mjr 82:4f6209cb5c33 259 pos += dir[st][stNew];
mjr 82:4f6209cb5c33 260
mjr 82:4f6209cb5c33 261 // the new state is now the current state
mjr 82:4f6209cb5c33 262 st = stNew;
mjr 82:4f6209cb5c33 263 }
mjr 82:4f6209cb5c33 264
mjr 82:4f6209cb5c33 265 // timer for input timestamps
mjr 82:4f6209cb5c33 266 Timer timer;
mjr 82:4f6209cb5c33 267 };
mjr 82:4f6209cb5c33 268
mjr 82:4f6209cb5c33 269 #endif