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
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 };
Generated on Wed Jul 13 2022 03:30:11 by 1.7.2