Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Plunger/potSensor.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 104:6e06e0f4b476
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 17:ab3cec0c8bf4 | 1 | // Potentiometer plunger sensor |
mjr | 17:ab3cec0c8bf4 | 2 | // |
mjr | 17:ab3cec0c8bf4 | 3 | // This file implements our generic plunger sensor interface for a |
mjr | 43:7a6364d82a41 | 4 | // potentiometer. The potentiometer resistance must be linear in |
mjr | 43:7a6364d82a41 | 5 | // position. To connect physically, wire the fixed ends of the |
mjr | 43:7a6364d82a41 | 6 | // potentiometer to +3.3V and GND (respectively), and connect the |
mjr | 43:7a6364d82a41 | 7 | // wiper to an ADC-capable GPIO pin on the KL25Z. The wiper voltage |
mjr | 43:7a6364d82a41 | 8 | // that we read on the ADC will vary linearly with the wiper position. |
mjr | 43:7a6364d82a41 | 9 | // Mechanically attach the wiper to the plunger so that the wiper moves |
mjr | 43:7a6364d82a41 | 10 | // in lock step with the plunger. |
mjr | 43:7a6364d82a41 | 11 | // |
mjr | 100:1ff35c07217c | 12 | // In practice, the ADC readings from a potentiometer can be noisy, |
mjr | 100:1ff35c07217c | 13 | // varying by around 1% from reading to reading when the slider is |
mjr | 100:1ff35c07217c | 14 | // stationary. One way to improve this is to use longer sampling times |
mjr | 100:1ff35c07217c | 15 | // in the ADC to improve the accuracy of the sampling. We can tolerate |
mjr | 100:1ff35c07217c | 16 | // quite long ADC sampling times because even the slow modes are quite |
mjr | 100:1ff35c07217c | 17 | // a lot faster than the result rate we require. Another way to reduce |
mjr | 100:1ff35c07217c | 18 | // noise is to apply some low-pass filtering. The simplest low-pass |
mjr | 100:1ff35c07217c | 19 | // filter is to average a number of samples together. Since our ADC |
mjr | 100:1ff35c07217c | 20 | // sampling rate (even with long conversions) is quite a lot faster than |
mjr | 100:1ff35c07217c | 21 | // the needed output rate, we can simply average samples over the time |
mjr | 100:1ff35c07217c | 22 | // scale where we need discrete outputs. |
mjr | 100:1ff35c07217c | 23 | // |
mjr | 100:1ff35c07217c | 24 | // Note: even though this class is specifically for potentiometers, it |
mjr | 100:1ff35c07217c | 25 | // could also be used with any other type of sensor that represents its |
mjr | 100:1ff35c07217c | 26 | // position reading as a single analog voltage level that varies linearly |
mjr | 104:6e06e0f4b476 | 27 | // with the position, such as an LVDT. Note that linearity is key here: |
mjr | 104:6e06e0f4b476 | 28 | // this code wouldn't work well with a sensor that produces an analog |
mjr | 104:6e06e0f4b476 | 29 | // voltage but has a NON-linear response curve with respect to measured |
mjr | 104:6e06e0f4b476 | 30 | // position. For example, this code wouldn't work well with the old |
mjr | 104:6e06e0f4b476 | 31 | // Sharp reflective IR proximity/distance sensors, since those have |
mjr | 104:6e06e0f4b476 | 32 | // power-law response curves. To work with a non-linear sensor, you'd |
mjr | 104:6e06e0f4b476 | 33 | // have to subclass this class, override readRaw(), and add processing |
mjr | 104:6e06e0f4b476 | 34 | // that translates the non-linear sensor reading to a linear position |
mjr | 104:6e06e0f4b476 | 35 | // measurement. Such processing is obviously a function of the physics |
mjr | 104:6e06e0f4b476 | 36 | // of the particular sensor, so it would have to be crafted for each |
mjr | 104:6e06e0f4b476 | 37 | // such sensor type. |
mjr | 100:1ff35c07217c | 38 | // |
mjr | 17:ab3cec0c8bf4 | 39 | |
mjr | 100:1ff35c07217c | 40 | #include "plunger.h" |
mjr | 100:1ff35c07217c | 41 | #include "AltAnalogIn.h" |
mjr | 17:ab3cec0c8bf4 | 42 | |
mjr | 35:e959ffba78fd | 43 | class PlungerSensorPot: public PlungerSensor |
mjr | 17:ab3cec0c8bf4 | 44 | { |
mjr | 17:ab3cec0c8bf4 | 45 | public: |
mjr | 86:e30a1f60f783 | 46 | // Our native readings are taken as 16-bit ADC samples, so |
mjr | 86:e30a1f60f783 | 47 | // our native scale is an unsigned 16-bit int, 0..65535. |
mjr | 100:1ff35c07217c | 48 | // |
mjr | 100:1ff35c07217c | 49 | // Initialize the ADC to take continuous samples, interrupting us |
mjr | 100:1ff35c07217c | 50 | // when each conversion finishes so that we can collect the result |
mjr | 100:1ff35c07217c | 51 | // in an ISR. For the sampling mode, use long conversions with |
mjr | 100:1ff35c07217c | 52 | // 24 ADCK cycles and 8x averaging; this gives us conversion times |
mjr | 100:1ff35c07217c | 53 | // of about 37.33us. |
mjr | 100:1ff35c07217c | 54 | // |
mjr | 100:1ff35c07217c | 55 | PlungerSensorPot(PinName ao) : |
mjr | 100:1ff35c07217c | 56 | PlungerSensor(65535), |
mjr | 100:1ff35c07217c | 57 | pot(ao, true, 24, 8) // continuous, 24-cycle long samples, 8x averaging -> 37.33us/sample |
mjr | 17:ab3cec0c8bf4 | 58 | { |
mjr | 100:1ff35c07217c | 59 | // calibrate the ADC for best accuracy |
mjr | 100:1ff35c07217c | 60 | pot.calibrate(); |
mjr | 100:1ff35c07217c | 61 | |
mjr | 100:1ff35c07217c | 62 | // clear the timing statistics |
mjr | 100:1ff35c07217c | 63 | totalConversionTime = 0; |
mjr | 100:1ff35c07217c | 64 | nSamples = 0; |
mjr | 100:1ff35c07217c | 65 | |
mjr | 100:1ff35c07217c | 66 | // start with everything zeroed |
mjr | 100:1ff35c07217c | 67 | history_write_idx = 0; |
mjr | 100:1ff35c07217c | 68 | running_sum = 0; |
mjr | 100:1ff35c07217c | 69 | for (int i = 0 ; i < countof(history); ++i) |
mjr | 100:1ff35c07217c | 70 | history[i] = 0; |
mjr | 100:1ff35c07217c | 71 | |
mjr | 100:1ff35c07217c | 72 | // set the initial timestamp to the arbitrary epoch on the timer |
mjr | 100:1ff35c07217c | 73 | current_timestamp = 0; |
mjr | 100:1ff35c07217c | 74 | |
mjr | 100:1ff35c07217c | 75 | // Set up an interrupt handler to collect the ADC results. The |
mjr | 100:1ff35c07217c | 76 | // ADC will trigger the interrupt on each completed sample. |
mjr | 100:1ff35c07217c | 77 | isrThis = this; |
mjr | 100:1ff35c07217c | 78 | NVIC_SetVector(ADC0_IRQn, (uint32_t)&irq_handler_static); |
mjr | 100:1ff35c07217c | 79 | NVIC_EnableIRQ(ADC0_IRQn); |
mjr | 100:1ff35c07217c | 80 | pot.enableInterrupts(); |
mjr | 100:1ff35c07217c | 81 | |
mjr | 100:1ff35c07217c | 82 | // Start the first asynchronous ADC sample. The ADC will run |
mjr | 100:1ff35c07217c | 83 | // continuously once started, and we'll collect samples in the ISR. |
mjr | 100:1ff35c07217c | 84 | pot.start(); |
mjr | 48:058ace2aed1d | 85 | timer.start(); |
mjr | 17:ab3cec0c8bf4 | 86 | } |
mjr | 17:ab3cec0c8bf4 | 87 | |
mjr | 35:e959ffba78fd | 88 | virtual void init() |
mjr | 17:ab3cec0c8bf4 | 89 | { |
mjr | 17:ab3cec0c8bf4 | 90 | } |
mjr | 17:ab3cec0c8bf4 | 91 | |
mjr | 100:1ff35c07217c | 92 | // samples are always ready |
mjr | 100:1ff35c07217c | 93 | virtual bool ready() { return true; } |
mjr | 100:1ff35c07217c | 94 | |
mjr | 48:058ace2aed1d | 95 | // read the sensor |
mjr | 86:e30a1f60f783 | 96 | virtual bool readRaw(PlungerReading &r) |
mjr | 17:ab3cec0c8bf4 | 97 | { |
mjr | 100:1ff35c07217c | 98 | // read the current sample components atomically |
mjr | 100:1ff35c07217c | 99 | __disable_irq(); |
mjr | 48:058ace2aed1d | 100 | |
mjr | 100:1ff35c07217c | 101 | // figure the current average reading over the history window |
mjr | 100:1ff35c07217c | 102 | r.pos = running_sum / countof(history); |
mjr | 100:1ff35c07217c | 103 | r.t = current_timestamp; |
mjr | 48:058ace2aed1d | 104 | |
mjr | 100:1ff35c07217c | 105 | // done with the atomic read |
mjr | 100:1ff35c07217c | 106 | __enable_irq(); |
mjr | 52:8298b2a73eb2 | 107 | |
mjr | 100:1ff35c07217c | 108 | // we always have a result available |
mjr | 17:ab3cec0c8bf4 | 109 | return true; |
mjr | 17:ab3cec0c8bf4 | 110 | } |
mjr | 52:8298b2a73eb2 | 111 | |
mjr | 100:1ff35c07217c | 112 | // Figure the average scan time in microseconds |
mjr | 53:9b2611964afc | 113 | virtual uint32_t getAvgScanTime() |
mjr | 53:9b2611964afc | 114 | { |
mjr | 100:1ff35c07217c | 115 | // The effective time per sample is the raw sampling interval |
mjr | 100:1ff35c07217c | 116 | // times the averaging window size. |
mjr | 100:1ff35c07217c | 117 | if (nSamples == 0) |
mjr | 100:1ff35c07217c | 118 | return 0; |
mjr | 100:1ff35c07217c | 119 | else |
mjr | 100:1ff35c07217c | 120 | return static_cast<uint32_t>(totalConversionTime/nSamples) * countof(history); |
mjr | 53:9b2611964afc | 121 | } |
mjr | 23:14f8c5004cd0 | 122 | |
mjr | 17:ab3cec0c8bf4 | 123 | private: |
mjr | 48:058ace2aed1d | 124 | // analog input for the pot wiper |
mjr | 100:1ff35c07217c | 125 | AltAnalogIn_16bit pot; |
mjr | 48:058ace2aed1d | 126 | |
mjr | 48:058ace2aed1d | 127 | // timer for input timestamps |
mjr | 48:058ace2aed1d | 128 | Timer timer; |
mjr | 52:8298b2a73eb2 | 129 | |
mjr | 100:1ff35c07217c | 130 | // total sampling time and number of samples, for computing scan times |
mjr | 100:1ff35c07217c | 131 | uint64_t totalConversionTime; |
mjr | 100:1ff35c07217c | 132 | uint32_t nSamples; |
mjr | 100:1ff35c07217c | 133 | |
mjr | 100:1ff35c07217c | 134 | // interrupt handler |
mjr | 100:1ff35c07217c | 135 | static PlungerSensorPot *isrThis; |
mjr | 100:1ff35c07217c | 136 | static void irq_handler_static(void) { isrThis->irq_handler(); } |
mjr | 100:1ff35c07217c | 137 | |
mjr | 100:1ff35c07217c | 138 | void irq_handler() |
mjr | 100:1ff35c07217c | 139 | { |
mjr | 100:1ff35c07217c | 140 | // read the next sample |
mjr | 100:1ff35c07217c | 141 | uint16_t sample = pot.read_u16(); |
mjr | 100:1ff35c07217c | 142 | |
mjr | 100:1ff35c07217c | 143 | // deduct the outgoing sample from the running sum |
mjr | 100:1ff35c07217c | 144 | running_sum -= history[history_write_idx]; |
mjr | 100:1ff35c07217c | 145 | |
mjr | 100:1ff35c07217c | 146 | // add the new sample into the running sum |
mjr | 100:1ff35c07217c | 147 | running_sum += sample; |
mjr | 100:1ff35c07217c | 148 | |
mjr | 100:1ff35c07217c | 149 | // store the new sample in the history |
mjr | 100:1ff35c07217c | 150 | history[history_write_idx++] = sample; |
mjr | 100:1ff35c07217c | 151 | |
mjr | 100:1ff35c07217c | 152 | // wrap the history index at the end of the window |
mjr | 100:1ff35c07217c | 153 | if (history_write_idx >= countof(history)) |
mjr | 100:1ff35c07217c | 154 | history_write_idx = 0; |
mjr | 100:1ff35c07217c | 155 | |
mjr | 100:1ff35c07217c | 156 | // calculate the elapsed time since the last sample |
mjr | 100:1ff35c07217c | 157 | uint32_t now = timer.read_us(); |
mjr | 100:1ff35c07217c | 158 | totalConversionTime += now - current_timestamp; |
mjr | 100:1ff35c07217c | 159 | ++nSamples; |
mjr | 100:1ff35c07217c | 160 | |
mjr | 100:1ff35c07217c | 161 | // update the reading timestamp |
mjr | 100:1ff35c07217c | 162 | current_timestamp = now; |
mjr | 100:1ff35c07217c | 163 | } |
mjr | 100:1ff35c07217c | 164 | |
mjr | 100:1ff35c07217c | 165 | // Running sum of readings. This is the sum of the readings in the |
mjr | 100:1ff35c07217c | 166 | // rolling 5ms window. |
mjr | 100:1ff35c07217c | 167 | uint32_t running_sum; |
mjr | 100:1ff35c07217c | 168 | |
mjr | 100:1ff35c07217c | 169 | // Rolling window of readings, for the averaging filter. Our |
mjr | 100:1ff35c07217c | 170 | // sampling time is about 37.33us; 128 of these add up to about |
mjr | 100:1ff35c07217c | 171 | // 4.8ms, which is a good interval between samples for our |
mjr | 100:1ff35c07217c | 172 | // internal tracking and sending USB data to the PC. |
mjr | 100:1ff35c07217c | 173 | uint16_t history[128]; |
mjr | 100:1ff35c07217c | 174 | int history_write_idx; |
mjr | 100:1ff35c07217c | 175 | |
mjr | 100:1ff35c07217c | 176 | // current average reading and scan time |
mjr | 100:1ff35c07217c | 177 | uint32_t current_timestamp; |
mjr | 17:ab3cec0c8bf4 | 178 | }; |