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
AltAnalogIn.h
00001 #ifndef ALTANALOGIN_H 00002 #define ALTANALOGIN_H 00003 00004 // This is a modified version of Scissors's FastAnalogIn, customized 00005 // for the needs of the Pinscape linear image sensor interfaces. This 00006 // class has a bunch of features to make it even faster than FastAnalogIn, 00007 // including support for 8-bit and 12-bit resolution modes, continuous 00008 // sampling mode, coordination with DMA to move samples into memory 00009 // asynchronously, and client selection of the ADC timing modes. 00010 // 00011 // We need all of this special ADC handling because the image sensors 00012 // have special timing requirements that we can only meet with the 00013 // fastest modes offered by the KL25Z ADC. The image sensors all 00014 // operate by sending pixel data as a serial stream of analog samples, 00015 // so the minimum time to read a frame is approximately <number of 00016 // pixels in the frame> times <ADC sampling time per sample>. The 00017 // sensors we currently support vary from 1280 to 1546 pixels per frame. 00018 // With the fastest KL25Z modes, that works out to about 3ms per frame, 00019 // which is just fast enough for our purposes. Using only the default 00020 // modes in the mbed libraries, frame times are around 30ms, which is 00021 // much too slow to accurately track a fast-moving plunger. 00022 // 00023 // This class works ONLY with the KL25Z. 00024 // 00025 // Important! This class can't coexist at run-time with the standard 00026 // mbed library version of AnalogIn, or with the original version of 00027 // FastAnalogIn. All of these classes program the ADC configuration 00028 // registers with their own custom settings. These registers are a 00029 // global resource, and the different classes all assume they have 00030 // exclusive control, so they don't try to coordinate with anyone else 00031 // programming the registers. A program that uses AltAnalogIn in one 00032 // place will have to use AltAnalogIn exclusively throughout the 00033 // program for all ADC interaction. (It *is* okay to statically link 00034 // the different classes, as long as only one is actually used at 00035 // run-time. The Pinscape software does this, and selects the one to 00036 // use at run-time according to which plunger class is selected.) 00037 00038 /* 00039 * Includes 00040 */ 00041 #include "mbed.h" 00042 #include "pinmap.h" 00043 #include "SimpleDMA.h" 00044 00045 // KL25Z definitions 00046 #if defined TARGET_KLXX 00047 00048 // Maximum ADC clock for KL25Z in <= 12-bit mode - 18 MHz per the data sheet 00049 #define MAX_FADC_12BIT 18000000 00050 00051 // Maximum ADC clock for KL25Z in 16-bit mode - 12 MHz per the data sheet 00052 #define MAX_FADC_16BIT 12000000 00053 00054 #define CHANNELS_A_SHIFT 5 // bit position in ADC channel number of A/B mux 00055 #define ADC_CFG1_ADLSMP 0x10 // long sample time mode 00056 #define ADC_SC1_AIEN 0x40 // interrupt enable 00057 #define ADC_SC2_ADLSTS(mode) (mode) // long sample time select - bits 1:0 of CFG2 00058 #define ADC_SC2_DMAEN 0x04 // DMA enable 00059 #define ADC_SC2_ADTRG 0x40 // Hardware conversion trigger 00060 #define ADC_SC3_CONTINUOUS 0x08 // continuous conversion mode 00061 #define ADC_SC3_AVGE 0x04 // averaging enabled 00062 #define ADC_SC3_AVGS_4 0x00 // 4-sample averaging 00063 #define ADC_SC3_AVGS_8 0x01 // 8-sample averaging 00064 #define ADC_SC3_AVGS_16 0x02 // 16-sample averaging 00065 #define ADC_SC3_AVGS_32 0x03 // 32-sample averaging 00066 #define ADC_SC3_CAL 0x80 // calibration - set to begin calibration 00067 #define ADC_SC3_CALF 0x40 // calibration failed flag 00068 00069 #define ADC_8BIT 0 // 8-bit resolution 00070 #define ADC_12BIT 1 // 12-bit resolution 00071 #define ADC_10BIT 2 // 10-bit resolution 00072 #define ADC_16BIT 3 // 16-bit resolution 00073 00074 // SIM_SOPT7 - enable alternative conversion triggers 00075 #define ADC0ALTTRGEN 0x80 00076 00077 // SIM_SOPT7 ADC0TRGSEL bits for TPMn, n = 0..2 00078 #define ADC0TRGSEL_TPM(n) (0x08 | (n)) // select TPMn overflow 00079 00080 00081 #else 00082 #error "This target is not currently supported" 00083 #endif 00084 00085 #if !defined TARGET_LPC1768 && !defined TARGET_KLXX && !defined TARGET_LPC408X && !defined TARGET_LPC11UXX && !defined TARGET_K20D5M 00086 #error "Target not supported" 00087 #endif 00088 00089 00090 class AltAnalogIn { 00091 00092 public: 00093 /** Create an AltAnalogIn, connected to the specified pin 00094 * 00095 * @param pin AnalogIn pin to connect to 00096 * @param continuous true to enable continue sampling mode 00097 * @param long_sample_clocks long sample mode: 0 to disable, ADC clock count to enable (6, 10, 16, or 24) 00098 * @param averaging number of averaging cycles (1, 4, 8, 16, 32) 00099 * @param sample_bits sample size in bits (8, 10, 12, 16) 00100 */ 00101 AltAnalogIn(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1, int sample_bits = 8); 00102 00103 ~AltAnalogIn( void ) 00104 { 00105 } 00106 00107 // Calibrate the ADC. Per the KL25Z reference manual, this should be 00108 // done after each CPU reset to get the best accuracy from the ADC. 00109 // 00110 // The calibration process runs synchronously (blocking) and takes 00111 // about 2ms. Per the reference manual guidelines, we calibrate 00112 // using the same timing parameters configured in the constructor, 00113 // but we use the maximum averaging rounds. 00114 // 00115 // The calibration depends on the timing parameters, so if multiple 00116 // AltAnalogIn objects will be used in the same application, the 00117 // configuration established for one object might not be ideal for 00118 // another. The advice in the reference manual is to calibrate once 00119 // at the settings where the highest accuracy will be needed. It's 00120 // also possible to capture the configuration data from the ADC 00121 // registers after a configuration and restore them later by writing 00122 // the same values back to the registers, for relatively fast switching 00123 // between calibration sets, but that's beyond the scope of this class. 00124 void calibrate(); 00125 00126 // Initialize DMA. This connects the ADC port to the given DMA 00127 // channel. This doesn't actually initiate a transfer; this just 00128 // connects the ADC to the DMA channel for later transfers. Use 00129 // the DMA object to set up a transfer, and use one of the trigger 00130 // modes (e.g., start() for software triggering) to initiate a 00131 // sample. 00132 void initDMA(SimpleDMA *dma); 00133 00134 // Enable interrupts. This doesn't actually set up a handler; the 00135 // caller is responsible for that. This merely sets the ADC registers 00136 // so that the ADC generates an ADC0_IRQ interrupt request each time 00137 // the sample completes. 00138 // 00139 // Note that the interrupt handler must read from ADC0->R[0] before 00140 // returning, which has the side effect of clearning the COCO (conversion 00141 // complete) flag in the ADC registers. When interrupts are enabled, 00142 // the ADC asserts the ADC0_IRQ interrupt continuously as long as the 00143 // COCO flag is set, so if the ISR doesn't explicitly clear COCO before 00144 // it returns, another ADC0_IRQ interrupt will immediate occur as soon 00145 // as the ISR returns, so we'll be stuck in an infinite loop of calling 00146 // the ISR over and over. 00147 void enableInterrupts(); 00148 00149 // Start a sample. This sets the ADC multiplexer to read from 00150 // this input and activates the sampler. 00151 inline void start() 00152 { 00153 // select my channel 00154 selectChannel(); 00155 00156 // set our SC1 bits - this initiates the sample 00157 ADC0->SC1[1] = sc1; 00158 ADC0->SC1[0] = sc1; 00159 } 00160 00161 // Set the ADC to trigger on a TPM channel, and start sampling on 00162 // the trigger. This can be used to start ADC samples in sync with a 00163 // clock signal we're generating via a TPM. The ADC is triggered each 00164 // time the TPM counter overflows, which makes it trigger at the start 00165 // of each PWM period on the unit. 00166 void setTriggerTPM(int tpmUnitNumber); 00167 00168 // stop sampling 00169 void stop() 00170 { 00171 // set the channel bits to binary 11111 to disable sampling 00172 ADC0->SC1[0] = 0x1F; 00173 } 00174 00175 // Resume sampling after a pause. 00176 inline void resume() 00177 { 00178 // restore our SC1 bits 00179 ADC0->SC1[1] = sc1; 00180 ADC0->SC1[0] = sc1; 00181 } 00182 00183 // Wait for the current sample to complete. 00184 // 00185 // IMPORTANT! DO NOT use this if DMA is enabled on the ADC. It'll 00186 // always gets stuck in an infinite loop, because the CPU will never 00187 // be able to observe the COCO bit being set when DMA is enabled. The 00188 // reason is that the DMA controller always reads its configured source 00189 // address when triggered. The DMA source address for the ADC is the 00190 // ADC result register ADC0->R[0], and reading that register by any 00191 // means clears COCO. And the DMA controller ALWAYS gets to it first, 00192 // so the CPU will never see COCO set when DMA is enabled. It doesn't 00193 // matter whether or not a DMA transfer is actually running, either - 00194 // it's enough to merely enable DMA on the ADC. 00195 inline void wait() 00196 { 00197 while (!isReady()) ; 00198 } 00199 00200 // Is the sample ready? 00201 // 00202 // NOTE: As with wait(), the CPU will NEVER observe the COCO bit being 00203 // set if DMA is enabled on the ADC. This will always return false if 00204 // DMA is enabled. (Not our choice - it's a hardware feature.) 00205 inline bool isReady() 00206 { 00207 return (ADC0->SC1[0] & ADC_SC1_COCO_MASK) != 0; 00208 } 00209 00210 00211 private: 00212 uint32_t id; // unique ID 00213 SimpleDMA *dma; // DMA controller, if used 00214 char ADCnumber; // ADC number of our input pin 00215 char ADCmux; // multiplexer for our input pin (0=A, 1=B) 00216 uint32_t sc1; // SC1 register settings for this input 00217 uint32_t sc1_aien; 00218 uint32_t sc2; // SC2 register settings for this input 00219 uint32_t sc3; // SC3 register settings for this input 00220 00221 // Switch to this channel if it's not the currently selected channel. 00222 // We do this as part of start() (software triggering) or any hardware 00223 // trigger setup. 00224 static int lastMux; 00225 static uint32_t lastId; 00226 void selectChannel() 00227 { 00228 // update the MUX bit in the CFG2 register only if necessary 00229 if (lastMux != ADCmux) 00230 { 00231 // remember the new register value 00232 lastMux = ADCmux; 00233 00234 // select the multiplexer for our ADC channel 00235 if (ADCmux) 00236 ADC0->CFG2 |= ADC_CFG2_MUXSEL_MASK; 00237 else 00238 ADC0->CFG2 &= ~ADC_CFG2_MUXSEL_MASK; 00239 } 00240 00241 // update the SC2 and SC3 bits only if we're changing inputs 00242 if (id != lastId) 00243 { 00244 // set our ADC0 SC2 and SC3 configuration bits 00245 ADC0->SC2 = sc2; 00246 ADC0->SC3 = sc3; 00247 00248 // we're the active one now 00249 lastId = id; 00250 } 00251 } 00252 00253 // Unselect the channel. This clears our internal flag for which 00254 // configuration was selected last, so that we restore settings on 00255 // the next start or trigger operation. 00256 void unselectChannel() { lastId = 0; } 00257 }; 00258 00259 // 8-bit sampler subclass 00260 class AltAnalogIn_8bit : public AltAnalogIn 00261 { 00262 public: 00263 AltAnalogIn_8bit(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1) : 00264 AltAnalogIn(pin, continuous, long_sample_clocks, averaging, 8) { } 00265 00266 /** Returns the raw value 00267 * 00268 * @param return Unsigned integer with converted value 00269 */ 00270 inline uint16_t read_u16() 00271 { 00272 // wait for the hardware to signal that the sample is completed 00273 wait(); 00274 00275 // return the result register value 00276 return (uint16_t)ADC0->R[0] << 8; // convert 16-bit to 16-bit, padding with zeroes 00277 } 00278 00279 /** Returns the scaled value 00280 * 00281 * @param return Float with scaled converted value to 0.0-1.0 00282 */ 00283 float read(void) 00284 { 00285 unsigned short value = read_u16(); 00286 return value / 65535.0f; 00287 } 00288 00289 /** An operator shorthand for read() 00290 */ 00291 operator float() { return read(); } 00292 }; 00293 00294 // 16-bit sampler subclass 00295 class AltAnalogIn_16bit : public AltAnalogIn 00296 { 00297 public: 00298 AltAnalogIn_16bit(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1) : 00299 AltAnalogIn(pin, continuous, long_sample_clocks, averaging, 16) { } 00300 00301 /** Returns the raw value 00302 * 00303 * @param return Unsigned integer with converted value 00304 */ 00305 inline uint16_t read_u16() 00306 { 00307 // wait for the hardware to signal that the sample is completed 00308 wait(); 00309 00310 // return the result register value 00311 return (uint16_t)ADC0->R[0]; 00312 } 00313 00314 /** Returns the scaled value 00315 * 00316 * @param return Float with scaled converted value to 0.0-1.0 00317 */ 00318 float read(void) 00319 { 00320 unsigned short value = read_u16(); 00321 return value / 65535.0f; 00322 } 00323 00324 /** An operator shorthand for read() 00325 */ 00326 operator float() { return read(); } 00327 }; 00328 00329 #endif
Generated on Wed Jul 13 2022 03:30:10 by 1.7.2