Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

Committer:
arnoz
Date:
Fri Oct 01 08:19:46 2021 +0000
Revision:
116:7a67265d7c19
Parent:
108:bd5d4bd4383b
- Correct information regarding your last merge

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 86:e30a1f60f783 95 //
mjr 86:e30a1f60f783 96 // For the native scale, we'll assume a 4" range at our dpi rating.
mjr 86:e30a1f60f783 97 // The actual plunger travel is constrainted to about a 3" range, but
mjr 86:e30a1f60f783 98 // we want to leave a little extra padding to reduce the chances of
mjr 86:e30a1f60f783 99 // going out of range in unusual situations.
mjr 82:4f6209cb5c33 100 PlungerSensorQuad(int dpi, PinName pinA, PinName pinB)
mjr 86:e30a1f60f783 101 : PlungerSensor(dpi*4),
mjr 86:e30a1f60f783 102 chA(pinA), chB(pinB)
mjr 82:4f6209cb5c33 103 {
mjr 82:4f6209cb5c33 104 // Use 1" as the reference park position
mjr 82:4f6209cb5c33 105 parkPos = dpi;
mjr 82:4f6209cb5c33 106
mjr 82:4f6209cb5c33 107 // start at the park position
mjr 82:4f6209cb5c33 108 pos = parkPos;
mjr 82:4f6209cb5c33 109
mjr 82:4f6209cb5c33 110 // get the initial pin states
mjr 82:4f6209cb5c33 111 st = (chA.read() ? 0x01 : 0x00)
mjr 82:4f6209cb5c33 112 | (chB.read() ? 0x02 : 0x00);
mjr 82:4f6209cb5c33 113
mjr 82:4f6209cb5c33 114 // set up the interrupt handlers
mjr 82:4f6209cb5c33 115 chA.rise(&PlungerSensorQuad::aUp, this);
mjr 82:4f6209cb5c33 116 chA.fall(&PlungerSensorQuad::aDown, this);
mjr 82:4f6209cb5c33 117 chB.rise(&PlungerSensorQuad::bUp, this);
mjr 82:4f6209cb5c33 118 chB.fall(&PlungerSensorQuad::bDown, this);
mjr 82:4f6209cb5c33 119
mjr 82:4f6209cb5c33 120 // start our sample timer with an arbitrary zero point of now
mjr 82:4f6209cb5c33 121 timer.start();
mjr 82:4f6209cb5c33 122 }
mjr 82:4f6209cb5c33 123
mjr 105:6a25bbfae1e4 124 // Auto-zero. Return to the park position. If we're using reverse
mjr 105:6a25bbfae1e4 125 // orientation, go to the park position distance from the top end
mjr 105:6a25bbfae1e4 126 // of the scale.
mjr 82:4f6209cb5c33 127 virtual void autoZero()
mjr 82:4f6209cb5c33 128 {
mjr 105:6a25bbfae1e4 129 pos = reverseOrientation ? nativeScale - parkPos : parkPos;
mjr 82:4f6209cb5c33 130 }
mjr 82:4f6209cb5c33 131
mjr 82:4f6209cb5c33 132 // Begin calibration. We can assume that the plunger is at the
mjr 105:6a25bbfae1e4 133 // park position when calibration starts, so perform an explicit
mjr 105:6a25bbfae1e4 134 // auto-zeroing operation.
mjr 100:1ff35c07217c 135 virtual void beginCalibration(Config &)
mjr 82:4f6209cb5c33 136 {
mjr 105:6a25bbfae1e4 137 autoZero();
mjr 82:4f6209cb5c33 138 }
mjr 82:4f6209cb5c33 139
mjr 82:4f6209cb5c33 140 // read the sensor
mjr 86:e30a1f60f783 141 virtual bool readRaw(PlungerReading &r)
mjr 82:4f6209cb5c33 142 {
mjr 86:e30a1f60f783 143 // Get the current position in native units
mjr 86:e30a1f60f783 144 r.pos = pos;
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 108:bd5d4bd4383b 156 virtual void sendStatusReport(class USBJoystick &js, uint8_t flags)
mjr 108:bd5d4bd4383b 157 {
mjr 108:bd5d4bd4383b 158 // send the common status report
mjr 108:bd5d4bd4383b 159 PlungerSensor::sendStatusReport(js, flags);
mjr 108:bd5d4bd4383b 160
mjr 108:bd5d4bd4383b 161 // send the extra quadrature sensor status report
mjr 108:bd5d4bd4383b 162 js.sendPlungerStatusQuadrature((st & 0x01) != 0, (st & 0x02) != 0);
mjr 108:bd5d4bd4383b 163 }
mjr 108:bd5d4bd4383b 164
mjr 82:4f6209cb5c33 165 // figure the average scan time in microseconds
mjr 82:4f6209cb5c33 166 virtual uint32_t getAvgScanTime()
mjr 82:4f6209cb5c33 167 {
mjr 82:4f6209cb5c33 168 // we're updated by interrupts rather than scanning, so our
mjr 82:4f6209cb5c33 169 // "scan time" is exactly zero
mjr 82:4f6209cb5c33 170 return 0;
mjr 82:4f6209cb5c33 171 }
mjr 101:755f44622abc 172
mjr 82:4f6209cb5c33 173 private:
mjr 82:4f6209cb5c33 174 // interrupt inputs for our channel pins
mjr 82:4f6209cb5c33 175 FastInterruptIn chA, chB;
mjr 82:4f6209cb5c33 176
mjr 82:4f6209cb5c33 177 // current position - this is the cumulate counter for all
mjr 82:4f6209cb5c33 178 // transitions so far
mjr 82:4f6209cb5c33 179 int pos;
mjr 82:4f6209cb5c33 180
mjr 82:4f6209cb5c33 181 // Park position. This is essentially arbitrary, since our readings
mjr 82:4f6209cb5c33 182 // are entirely relative, but for interface purposes we have to keep
mjr 82:4f6209cb5c33 183 // our raw readings positive. We need an initial park position that's
mjr 82:4f6209cb5c33 184 // non-zero so that plunger motion forward of the park position remains
mjr 82:4f6209cb5c33 185 // positive.
mjr 82:4f6209cb5c33 186 int parkPos;
mjr 82:4f6209cb5c33 187
mjr 82:4f6209cb5c33 188 // Channel state on last read. This is a bit vector combining
mjr 82:4f6209cb5c33 189 // the two channel states:
mjr 82:4f6209cb5c33 190 // 0x01 = channel A state
mjr 82:4f6209cb5c33 191 // 0x02 = channel B state
mjr 82:4f6209cb5c33 192 uint8_t st;
mjr 82:4f6209cb5c33 193
mjr 82:4f6209cb5c33 194 // interrupt handlers
mjr 82:4f6209cb5c33 195 static void aUp(void *obj) {
mjr 82:4f6209cb5c33 196 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 197 self->transition(self->st | 0x01);
mjr 82:4f6209cb5c33 198 }
mjr 82:4f6209cb5c33 199 static void aDown(void *obj) {
mjr 82:4f6209cb5c33 200 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 201 self->transition(self->st & 0x02);
mjr 82:4f6209cb5c33 202 }
mjr 82:4f6209cb5c33 203 static void bUp(void *obj) {
mjr 82:4f6209cb5c33 204 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 205 self->transition(self->st | 0x02);
mjr 82:4f6209cb5c33 206 }
mjr 82:4f6209cb5c33 207 static void bDown(void *obj) {
mjr 82:4f6209cb5c33 208 PlungerSensorQuad *self = (PlungerSensorQuad *)obj;
mjr 82:4f6209cb5c33 209 self->transition(self->st & 0x01);
mjr 82:4f6209cb5c33 210 }
mjr 82:4f6209cb5c33 211
mjr 82:4f6209cb5c33 212 // Transition handler. The interrupt handlers call this, so
mjr 82:4f6209cb5c33 213 // it's critical that this run as fast as possible. The observed
mjr 82:4f6209cb5c33 214 // peak interrupt rate is one interrupt per 19us. Fortunately,
mjr 82:4f6209cb5c33 215 // our work here is simple: we just have to count the pulse in
mjr 82:4f6209cb5c33 216 // the appropriate direction according to the state transition
mjr 82:4f6209cb5c33 217 // that the pulse represents. We can do this with a simple table
mjr 82:4f6209cb5c33 218 // lookup.
mjr 82:4f6209cb5c33 219 inline void transition(int stNew)
mjr 82:4f6209cb5c33 220 {
mjr 108:bd5d4bd4383b 221 // Transition matrix: dir[n][m] gives the direction of
mjr 108:bd5d4bd4383b 222 // motion when we switch from state 'n' to state 'm'.
mjr 82:4f6209cb5c33 223 // The state number is formed by the two-bit number B:A,
mjr 82:4f6209cb5c33 224 // where each bit is 1 if the channel pulse is on and 0
mjr 82:4f6209cb5c33 225 // if the channel pulse is off. E.g., if chA is OFF and
mjr 82:4f6209cb5c33 226 // chB is ON, B:A = 1:0, so the state number is 0b10 = 2.
mjr 82:4f6209cb5c33 227 // Slots with 'NV' are Not Valid: it's impossible to make
mjr 82:4f6209cb5c33 228 // this transition (unless we missed an interrupt). 'NC'
mjr 82:4f6209cb5c33 229 // means No Change; these are the slots on the matrix
mjr 82:4f6209cb5c33 230 // diagonal, which represent the same state on both input
mjr 82:4f6209cb5c33 231 // and output. Like NV transitions, NC transitions should
mjr 82:4f6209cb5c33 232 // never happen, in this case because no interrupt should
mjr 82:4f6209cb5c33 233 // be generated when nothing has changed.
mjr 82:4f6209cb5c33 234 const int NV = 0, NC = 0;
mjr 82:4f6209cb5c33 235 static const int dir[][4] = {
mjr 82:4f6209cb5c33 236 { NC, 1, -1, NV },
mjr 82:4f6209cb5c33 237 { -1, NC, NV, 1 },
mjr 82:4f6209cb5c33 238 { 1, NV, NC, -1 },
mjr 82:4f6209cb5c33 239 { NV, -1, 1, NC }
mjr 82:4f6209cb5c33 240 };
mjr 82:4f6209cb5c33 241
mjr 82:4f6209cb5c33 242 // increment or decrement the position counter by one notch,
mjr 82:4f6209cb5c33 243 // according to the direction of motion implied by the transition
mjr 82:4f6209cb5c33 244 pos += dir[st][stNew];
mjr 82:4f6209cb5c33 245
mjr 82:4f6209cb5c33 246 // the new state is now the current state
mjr 82:4f6209cb5c33 247 st = stNew;
mjr 82:4f6209cb5c33 248 }
mjr 82:4f6209cb5c33 249
mjr 82:4f6209cb5c33 250 // timer for input timestamps
mjr 82:4f6209cb5c33 251 Timer timer;
mjr 82:4f6209cb5c33 252 };
mjr 82:4f6209cb5c33 253
mjr 82:4f6209cb5c33 254 #endif