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 potSensor.h Source File

potSensor.h

00001 // Potentiometer plunger sensor
00002 //
00003 // This file implements our generic plunger sensor interface for a
00004 // potentiometer.  The potentiometer resistance must be linear in 
00005 // position.  To connect physically, wire the fixed ends of the
00006 // potentiometer to +3.3V and GND (respectively), and connect the 
00007 // wiper to an ADC-capable GPIO pin on the KL25Z.  The wiper voltage 
00008 // that we read on the ADC will vary linearly with the wiper position.
00009 // Mechanically attach the wiper to the plunger so that the wiper moves
00010 // in lock step with the plunger.
00011 //
00012 // In practice, the ADC readings from a potentiometer can be noisy,
00013 // varying by around 1% from reading to reading when the slider is
00014 // stationary.  One way to improve this is to use longer sampling times
00015 // in the ADC to improve the accuracy of the sampling.  We can tolerate
00016 // quite long ADC sampling times because even the slow modes are quite
00017 // a lot faster than the result rate we require.  Another way to reduce
00018 // noise is to apply some low-pass filtering.  The simplest low-pass 
00019 // filter is to average a number of samples together.  Since our ADC
00020 // sampling rate (even with long conversions) is quite a lot faster than
00021 // the needed output rate, we can simply average samples over the time
00022 // scale where we need discrete outputs.
00023 //
00024 // Note: even though this class is specifically for potentiometers, it
00025 // could also be used with any other type of sensor that represents its
00026 // position reading as a single analog voltage level that varies linearly
00027 // with the position, such as an LVDT.  Note that linearity is key here:
00028 // this code wouldn't work well with a sensor that produces an analog
00029 // voltage but has a NON-linear response curve with respect to measured
00030 // position.  For example, this code wouldn't work well with the old 
00031 // Sharp reflective IR proximity/distance sensors, since those have
00032 // power-law response curves.  To work with a non-linear sensor, you'd
00033 // have to subclass this class, override readRaw(), and add processing
00034 // that translates the non-linear sensor reading to a linear position
00035 // measurement.  Such processing is obviously a function of the physics
00036 // of the particular sensor, so it would have to be crafted for each
00037 // such sensor type.
00038 // 
00039 
00040 #include "plunger.h"
00041 #include "AltAnalogIn.h"
00042 
00043 class PlungerSensorPot: public PlungerSensor
00044 {
00045 public:
00046     // Our native readings are taken as 16-bit ADC samples, so
00047     // our native scale is an unsigned 16-bit int, 0..65535.
00048     //
00049     // Initialize the ADC to take continuous samples, interrupting us
00050     // when each conversion finishes so that we can collect the result
00051     // in an ISR.  For the sampling mode, use long conversions with
00052     // 24 ADCK cycles and 8x averaging; this gives us conversion times
00053     // of about 37.33us.
00054     //
00055     PlungerSensorPot(PinName ao) : 
00056         PlungerSensor(65535), 
00057         pot(ao, true, 24, 8)  // continuous, 24-cycle long samples, 8x averaging -> 37.33us/sample
00058     {
00059         // calibrate the ADC for best accuracy
00060         pot.calibrate();
00061         
00062         // clear the timing statistics
00063         totalConversionTime = 0;
00064         nSamples = 0;
00065 
00066         // start with everything zeroed
00067         history_write_idx = 0;
00068         running_sum = 0;
00069         for (int i = 0 ; i < countof(history); ++i)
00070             history[i] = 0;
00071             
00072         // set the initial timestamp to the arbitrary epoch on the timer
00073         current_timestamp = 0;
00074         
00075         // Set up an interrupt handler to collect the ADC results.  The
00076         // ADC will trigger the interrupt on each completed sample.
00077         isrThis = this;
00078         NVIC_SetVector(ADC0_IRQn, (uint32_t)&irq_handler_static);
00079         NVIC_EnableIRQ(ADC0_IRQn);
00080         pot.enableInterrupts();
00081         
00082         // Start the first asynchronous ADC sample.  The ADC will run
00083         // continuously once started, and we'll collect samples in the ISR.
00084         pot.start();
00085         timer.start();
00086     }
00087     
00088     virtual void init() 
00089     {
00090     }
00091     
00092     // samples are always ready
00093     virtual bool ready() { return true; }
00094     
00095     // read the sensor
00096     virtual bool readRaw(PlungerReading &r)
00097     {
00098         // read the current sample components atomically
00099         __disable_irq();
00100         
00101         // figure the current average reading over the history window
00102         r.pos = running_sum / countof(history);
00103         r.t = current_timestamp;
00104             
00105         // done with the atomic read
00106         __enable_irq();
00107         
00108         // we always have a result available
00109         return true;
00110     }
00111     
00112     // Figure the average scan time in microseconds
00113     virtual uint32_t getAvgScanTime() 
00114     { 
00115         // The effective time per sample is the raw sampling interval
00116         // times the averaging window size.
00117         if (nSamples == 0) 
00118             return 0;
00119         else
00120             return static_cast<uint32_t>(totalConversionTime/nSamples) * countof(history);
00121     }
00122         
00123 private:
00124     // analog input for the pot wiper
00125     AltAnalogIn_16bit pot;
00126     
00127     // timer for input timestamps
00128     Timer timer;
00129     
00130     // total sampling time and number of samples, for computing scan times
00131     uint64_t totalConversionTime;
00132     uint32_t nSamples;
00133 
00134     // interrupt handler
00135     static PlungerSensorPot *isrThis;
00136     static void irq_handler_static(void) { isrThis->irq_handler(); }
00137 
00138     void irq_handler()
00139     {
00140         // read the next sample
00141         uint16_t sample = pot.read_u16();
00142         
00143         // deduct the outgoing sample from the running sum
00144         running_sum -= history[history_write_idx];
00145         
00146         // add the new sample into the running sum
00147         running_sum += sample;
00148         
00149         // store the new sample in the history
00150         history[history_write_idx++] = sample;
00151         
00152         // wrap the history index at the end of the window
00153         if (history_write_idx >= countof(history))
00154             history_write_idx = 0;
00155             
00156         // calculate the elapsed time since the last sample
00157         uint32_t now = timer.read_us();
00158         totalConversionTime += now - current_timestamp;
00159         ++nSamples;
00160         
00161         // update the reading timestamp
00162         current_timestamp = now;
00163     }
00164     
00165     // Running sum of readings.  This is the sum of the readings in the
00166     // rolling 5ms window.
00167     uint32_t running_sum;
00168     
00169     // Rolling window of readings, for the averaging filter.  Our 
00170     // sampling time is about 37.33us; 128 of these add up to about
00171     // 4.8ms, which is a good interval between samples for our
00172     // internal tracking and sending USB data to the PC.
00173     uint16_t history[128];
00174     int history_write_idx;
00175     
00176     // current average reading and scan time
00177     uint32_t current_timestamp;
00178 };