Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
AltAnalogIn/AltAnalogIn.h
- Committer:
- arnoz
- Date:
- 2021-10-01
- Revision:
- 116:7a67265d7c19
- Parent:
- 104:6e06e0f4b476
File content as of revision 116:7a67265d7c19:
#ifndef ALTANALOGIN_H #define ALTANALOGIN_H // This is a modified version of Scissors's FastAnalogIn, customized // for the needs of the Pinscape linear image sensor interfaces. This // class has a bunch of features to make it even faster than FastAnalogIn, // including support for 8-bit and 12-bit resolution modes, continuous // sampling mode, coordination with DMA to move samples into memory // asynchronously, and client selection of the ADC timing modes. // // We need all of this special ADC handling because the image sensors // have special timing requirements that we can only meet with the // fastest modes offered by the KL25Z ADC. The image sensors all // operate by sending pixel data as a serial stream of analog samples, // so the minimum time to read a frame is approximately <number of // pixels in the frame> times <ADC sampling time per sample>. The // sensors we currently support vary from 1280 to 1546 pixels per frame. // With the fastest KL25Z modes, that works out to about 3ms per frame, // which is just fast enough for our purposes. Using only the default // modes in the mbed libraries, frame times are around 30ms, which is // much too slow to accurately track a fast-moving plunger. // // This class works ONLY with the KL25Z. // // Important! This class can't coexist at run-time with the standard // mbed library version of AnalogIn, or with the original version of // FastAnalogIn. All of these classes program the ADC configuration // registers with their own custom settings. These registers are a // global resource, and the different classes all assume they have // exclusive control, so they don't try to coordinate with anyone else // programming the registers. A program that uses AltAnalogIn in one // place will have to use AltAnalogIn exclusively throughout the // program for all ADC interaction. (It *is* okay to statically link // the different classes, as long as only one is actually used at // run-time. The Pinscape software does this, and selects the one to // use at run-time according to which plunger class is selected.) /* * Includes */ #include "mbed.h" #include "pinmap.h" #include "SimpleDMA.h" // KL25Z definitions #if defined TARGET_KLXX // Maximum ADC clock for KL25Z in <= 12-bit mode - 18 MHz per the data sheet #define MAX_FADC_12BIT 18000000 // Maximum ADC clock for KL25Z in 16-bit mode - 12 MHz per the data sheet #define MAX_FADC_16BIT 12000000 #define CHANNELS_A_SHIFT 5 // bit position in ADC channel number of A/B mux #define ADC_CFG1_ADLSMP 0x10 // long sample time mode #define ADC_SC1_AIEN 0x40 // interrupt enable #define ADC_SC2_ADLSTS(mode) (mode) // long sample time select - bits 1:0 of CFG2 #define ADC_SC2_DMAEN 0x04 // DMA enable #define ADC_SC2_ADTRG 0x40 // Hardware conversion trigger #define ADC_SC3_CONTINUOUS 0x08 // continuous conversion mode #define ADC_SC3_AVGE 0x04 // averaging enabled #define ADC_SC3_AVGS_4 0x00 // 4-sample averaging #define ADC_SC3_AVGS_8 0x01 // 8-sample averaging #define ADC_SC3_AVGS_16 0x02 // 16-sample averaging #define ADC_SC3_AVGS_32 0x03 // 32-sample averaging #define ADC_SC3_CAL 0x80 // calibration - set to begin calibration #define ADC_SC3_CALF 0x40 // calibration failed flag #define ADC_8BIT 0 // 8-bit resolution #define ADC_12BIT 1 // 12-bit resolution #define ADC_10BIT 2 // 10-bit resolution #define ADC_16BIT 3 // 16-bit resolution // SIM_SOPT7 - enable alternative conversion triggers #define ADC0ALTTRGEN 0x80 // SIM_SOPT7 ADC0TRGSEL bits for TPMn, n = 0..2 #define ADC0TRGSEL_TPM(n) (0x08 | (n)) // select TPMn overflow #else #error "This target is not currently supported" #endif #if !defined TARGET_LPC1768 && !defined TARGET_KLXX && !defined TARGET_LPC408X && !defined TARGET_LPC11UXX && !defined TARGET_K20D5M #error "Target not supported" #endif class AltAnalogIn { public: /** Create an AltAnalogIn, connected to the specified pin * * @param pin AnalogIn pin to connect to * @param continuous true to enable continue sampling mode * @param long_sample_clocks long sample mode: 0 to disable, ADC clock count to enable (6, 10, 16, or 24) * @param averaging number of averaging cycles (1, 4, 8, 16, 32) * @param sample_bits sample size in bits (8, 10, 12, 16) */ AltAnalogIn(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1, int sample_bits = 8); ~AltAnalogIn( void ) { } // Calibrate the ADC. Per the KL25Z reference manual, this should be // done after each CPU reset to get the best accuracy from the ADC. // // The calibration process runs synchronously (blocking) and takes // about 2ms. Per the reference manual guidelines, we calibrate // using the same timing parameters configured in the constructor, // but we use the maximum averaging rounds. // // The calibration depends on the timing parameters, so if multiple // AltAnalogIn objects will be used in the same application, the // configuration established for one object might not be ideal for // another. The advice in the reference manual is to calibrate once // at the settings where the highest accuracy will be needed. It's // also possible to capture the configuration data from the ADC // registers after a configuration and restore them later by writing // the same values back to the registers, for relatively fast switching // between calibration sets, but that's beyond the scope of this class. void calibrate(); // Initialize DMA. This connects the ADC port to the given DMA // channel. This doesn't actually initiate a transfer; this just // connects the ADC to the DMA channel for later transfers. Use // the DMA object to set up a transfer, and use one of the trigger // modes (e.g., start() for software triggering) to initiate a // sample. void initDMA(SimpleDMA *dma); // Enable interrupts. This doesn't actually set up a handler; the // caller is responsible for that. This merely sets the ADC registers // so that the ADC generates an ADC0_IRQ interrupt request each time // the sample completes. // // Note that the interrupt handler must read from ADC0->R[0] before // returning, which has the side effect of clearning the COCO (conversion // complete) flag in the ADC registers. When interrupts are enabled, // the ADC asserts the ADC0_IRQ interrupt continuously as long as the // COCO flag is set, so if the ISR doesn't explicitly clear COCO before // it returns, another ADC0_IRQ interrupt will immediate occur as soon // as the ISR returns, so we'll be stuck in an infinite loop of calling // the ISR over and over. void enableInterrupts(); // Start a sample. This sets the ADC multiplexer to read from // this input and activates the sampler. inline void start() { // select my channel selectChannel(); // set our SC1 bits - this initiates the sample ADC0->SC1[1] = sc1; ADC0->SC1[0] = sc1; } // Set the ADC to trigger on a TPM channel, and start sampling on // the trigger. This can be used to start ADC samples in sync with a // clock signal we're generating via a TPM. The ADC is triggered each // time the TPM counter overflows, which makes it trigger at the start // of each PWM period on the unit. void setTriggerTPM(int tpmUnitNumber); // stop sampling void stop() { // set the channel bits to binary 11111 to disable sampling ADC0->SC1[0] = 0x1F; } // Resume sampling after a pause. inline void resume() { // restore our SC1 bits ADC0->SC1[1] = sc1; ADC0->SC1[0] = sc1; } // Wait for the current sample to complete. // // IMPORTANT! DO NOT use this if DMA is enabled on the ADC. It'll // always gets stuck in an infinite loop, because the CPU will never // be able to observe the COCO bit being set when DMA is enabled. The // reason is that the DMA controller always reads its configured source // address when triggered. The DMA source address for the ADC is the // ADC result register ADC0->R[0], and reading that register by any // means clears COCO. And the DMA controller ALWAYS gets to it first, // so the CPU will never see COCO set when DMA is enabled. It doesn't // matter whether or not a DMA transfer is actually running, either - // it's enough to merely enable DMA on the ADC. inline void wait() { while (!isReady()) ; } // Is the sample ready? // // NOTE: As with wait(), the CPU will NEVER observe the COCO bit being // set if DMA is enabled on the ADC. This will always return false if // DMA is enabled. (Not our choice - it's a hardware feature.) inline bool isReady() { return (ADC0->SC1[0] & ADC_SC1_COCO_MASK) != 0; } private: uint32_t id; // unique ID SimpleDMA *dma; // DMA controller, if used char ADCnumber; // ADC number of our input pin char ADCmux; // multiplexer for our input pin (0=A, 1=B) uint32_t sc1; // SC1 register settings for this input uint32_t sc1_aien; uint32_t sc2; // SC2 register settings for this input uint32_t sc3; // SC3 register settings for this input // Switch to this channel if it's not the currently selected channel. // We do this as part of start() (software triggering) or any hardware // trigger setup. static int lastMux; static uint32_t lastId; void selectChannel() { // update the MUX bit in the CFG2 register only if necessary if (lastMux != ADCmux) { // remember the new register value lastMux = ADCmux; // select the multiplexer for our ADC channel if (ADCmux) ADC0->CFG2 |= ADC_CFG2_MUXSEL_MASK; else ADC0->CFG2 &= ~ADC_CFG2_MUXSEL_MASK; } // update the SC2 and SC3 bits only if we're changing inputs if (id != lastId) { // set our ADC0 SC2 and SC3 configuration bits ADC0->SC2 = sc2; ADC0->SC3 = sc3; // we're the active one now lastId = id; } } // Unselect the channel. This clears our internal flag for which // configuration was selected last, so that we restore settings on // the next start or trigger operation. void unselectChannel() { lastId = 0; } }; // 8-bit sampler subclass class AltAnalogIn_8bit : public AltAnalogIn { public: AltAnalogIn_8bit(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1) : AltAnalogIn(pin, continuous, long_sample_clocks, averaging, 8) { } /** Returns the raw value * * @param return Unsigned integer with converted value */ inline uint16_t read_u16() { // wait for the hardware to signal that the sample is completed wait(); // return the result register value return (uint16_t)ADC0->R[0] << 8; // convert 16-bit to 16-bit, padding with zeroes } /** Returns the scaled value * * @param return Float with scaled converted value to 0.0-1.0 */ float read(void) { unsigned short value = read_u16(); return value / 65535.0f; } /** An operator shorthand for read() */ operator float() { return read(); } }; // 16-bit sampler subclass class AltAnalogIn_16bit : public AltAnalogIn { public: AltAnalogIn_16bit(PinName pin, bool continuous = false, int long_sample_clocks = 0, int averaging = 1) : AltAnalogIn(pin, continuous, long_sample_clocks, averaging, 16) { } /** Returns the raw value * * @param return Unsigned integer with converted value */ inline uint16_t read_u16() { // wait for the hardware to signal that the sample is completed wait(); // return the result register value return (uint16_t)ADC0->R[0]; } /** Returns the scaled value * * @param return Float with scaled converted value to 0.0-1.0 */ float read(void) { unsigned short value = read_u16(); return value / 65535.0f; } /** An operator shorthand for read() */ operator float() { return read(); } }; #endif