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

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