Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
TSL14xx/TSL14xx.h@82:4f6209cb5c33, 2017-04-13 (annotated)
- Committer:
- mjr
- Date:
- Thu Apr 13 23:20:28 2017 +0000
- Revision:
- 82:4f6209cb5c33
- Child:
- 86:e30a1f60f783
Plunger refactoring; AEDR-8300 added; TSL1401CL in progress; VL6180X added
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 82:4f6209cb5c33 | 1 | /* |
mjr | 82:4f6209cb5c33 | 2 | * AMS/TAOS TSL14xx series photodiode array interface class. |
mjr | 82:4f6209cb5c33 | 3 | * |
mjr | 82:4f6209cb5c33 | 4 | * This provides a high-level interface for the AMS/TAOS TSLxx series |
mjr | 82:4f6209cb5c33 | 5 | * of photodiode arrays. This class works with most of the sensors |
mjr | 82:4f6209cb5c33 | 6 | * in this series, which differ only in pixel array sizes. This code |
mjr | 82:4f6209cb5c33 | 7 | * has been tested with the following sensors from the series: |
mjr | 82:4f6209cb5c33 | 8 | * |
mjr | 82:4f6209cb5c33 | 9 | * TSL1410R - 1280 pixels, 400dpi |
mjr | 82:4f6209cb5c33 | 10 | * TSL1412S - 1536 pixels, 400dpi |
mjr | 82:4f6209cb5c33 | 11 | * TSL1401CL - 128 pixels, 400dpi |
mjr | 82:4f6209cb5c33 | 12 | * |
mjr | 82:4f6209cb5c33 | 13 | * All of these sensors have the same electrical interface, consisting |
mjr | 82:4f6209cb5c33 | 14 | * of a clock input (CLK), start pulse input (SI), and analog pixel |
mjr | 82:4f6209cb5c33 | 15 | * output (AO). The sensors are equipped with hold capacitors and |
mjr | 82:4f6209cb5c33 | 16 | * shift registers that allow simultaneous sampling of all pixels, and |
mjr | 82:4f6209cb5c33 | 17 | * serial access to the pixel values. |
mjr | 82:4f6209cb5c33 | 18 | * |
mjr | 82:4f6209cb5c33 | 19 | * (Note on the plunger sensor class hierarchy: this class is for the |
mjr | 82:4f6209cb5c33 | 20 | * sensor only, not for the plunger application. This class is meant |
mjr | 82:4f6209cb5c33 | 21 | * to be reusable in other contexts that just need to read raw pixel |
mjr | 82:4f6209cb5c33 | 22 | * data from the sensor. Plunger/tslxxSensor.h implements the next |
mjr | 82:4f6209cb5c33 | 23 | * level up, which is the implementation of the generic plunger sensor |
mjr | 82:4f6209cb5c33 | 24 | * interface for TSL14xx sensors. That's still an abstract class, since |
mjr | 82:4f6209cb5c33 | 25 | * it only provides the plunger class specialization for these sensor |
mjr | 82:4f6209cb5c33 | 26 | * types, without any image analysis component. The final concrete |
mjr | 82:4f6209cb5c33 | 27 | * classes are in Plunger/edgeSensor.h and Plunger/barCodeSensor.h, |
mjr | 82:4f6209cb5c33 | 28 | * which add the image processing that analyzes the image data to |
mjr | 82:4f6209cb5c33 | 29 | * determine the plunger position.) |
mjr | 82:4f6209cb5c33 | 30 | * |
mjr | 82:4f6209cb5c33 | 31 | * Our API is based on a double-buffered asynchronous read. The caller |
mjr | 82:4f6209cb5c33 | 32 | * can access a completed buffer, containing the pixels from the last image |
mjr | 82:4f6209cb5c33 | 33 | * frame, while the sensor is transferring data asynchronously (using the |
mjr | 82:4f6209cb5c33 | 34 | * microcontroller's DMA capability) into the other buffer. Each time a |
mjr | 82:4f6209cb5c33 | 35 | * new read is started, we swap buffers, making the last completed buffer |
mjr | 82:4f6209cb5c33 | 36 | * available to the client and handing the other buffer to the DMA |
mjr | 82:4f6209cb5c33 | 37 | * controller to fill asynchronously. |
mjr | 82:4f6209cb5c33 | 38 | * |
mjr | 82:4f6209cb5c33 | 39 | * The photodiodes in these sensors gather light very rapidly, allowing |
mjr | 82:4f6209cb5c33 | 40 | * for extremely short exposure times. The "shutter" is electronic; |
mjr | 82:4f6209cb5c33 | 41 | * a signal on the pulse input resets the pixels and begins an integration |
mjr | 82:4f6209cb5c33 | 42 | * period, and a subsequent signal ends the integration and transfers the |
mjr | 82:4f6209cb5c33 | 43 | * pixel voltages to the hold capacitors. Minimum exposure times are less |
mjr | 82:4f6209cb5c33 | 44 | * than a millisecond. The actual timing is under software control, since |
mjr | 82:4f6209cb5c33 | 45 | * we determine the start and end of the integration period via the pulse |
mjr | 82:4f6209cb5c33 | 46 | * input. Longer integration periods gather more light, like a longer |
mjr | 82:4f6209cb5c33 | 47 | * exposure on a conventional camera. For our purposes in the Pinscape |
mjr | 82:4f6209cb5c33 | 48 | * Controller, we want the highest possible frame rate, as we're trying to |
mjr | 82:4f6209cb5c33 | 49 | * capture the motion of a fast-moving object (the plunger). The KL25Z |
mjr | 82:4f6209cb5c33 | 50 | * can't actually keep up with shortest integration time the sensor can |
mjr | 82:4f6209cb5c33 | 51 | * achieve - the limiting factor is the KL25Z ADC, which needs at least |
mjr | 82:4f6209cb5c33 | 52 | * 2.5us to collect each sample. The sensor transfers pixels to the MCU |
mjr | 82:4f6209cb5c33 | 53 | * serially, and each pixel is transferred as an analog voltage level, so |
mjr | 82:4f6209cb5c33 | 54 | * we have to collect one ADC sample per pixel. Our maximum frame rate |
mjr | 82:4f6209cb5c33 | 55 | * is therefore determined by the product of the minimum ADC sample time |
mjr | 82:4f6209cb5c33 | 56 | * and the number of pixels. |
mjr | 82:4f6209cb5c33 | 57 | * |
mjr | 82:4f6209cb5c33 | 58 | * The fastest operating mode for the KL25Z ADC is its "continuous" |
mjr | 82:4f6209cb5c33 | 59 | * mode, where it automatically starts taking a new sample every time |
mjr | 82:4f6209cb5c33 | 60 | * it completes the previous one. The fastest way to transfer the |
mjr | 82:4f6209cb5c33 | 61 | * samples to memory in this mode is via the hardware DMA controller. |
mjr | 82:4f6209cb5c33 | 62 | * |
mjr | 82:4f6209cb5c33 | 63 | * It takes a pretty tricky setup to make this work. I don't like tricky |
mjr | 82:4f6209cb5c33 | 64 | * setups - I prefer something easy to understand - but in this case it's |
mjr | 82:4f6209cb5c33 | 65 | * justified because of the importance in this application of maximizing |
mjr | 82:4f6209cb5c33 | 66 | * the frame rate. I'm pretty sure there's no other way to even get close |
mjr | 82:4f6209cb5c33 | 67 | * to the rate we can achieve with the continuous ADC/DMA combination. |
mjr | 82:4f6209cb5c33 | 68 | * The ADC/DMA mode gives us pixel read times of about 2us, vs a minimum |
mjr | 82:4f6209cb5c33 | 69 | * of about 14us for the next best method I've found. Using this mode, we |
mjr | 82:4f6209cb5c33 | 70 | * can read the TSL1410R's 1280 pixels at full resolution in about 2.5ms. |
mjr | 82:4f6209cb5c33 | 71 | * That's a frame rate of 400 frames per second, which is fast enough to |
mjr | 82:4f6209cb5c33 | 72 | * capture a fast-moving plunger with minimal motion blur. |
mjr | 82:4f6209cb5c33 | 73 | * |
mjr | 82:4f6209cb5c33 | 74 | * Note that some of the sensors in this series (TSL1410R, TSL1412S) have |
mjr | 82:4f6209cb5c33 | 75 | * a "parallel" readout mode that lets them physically deliver two pixels |
mjr | 82:4f6209cb5c33 | 76 | * at once the MCU, via separate physical connections. This could provide |
mjr | 82:4f6209cb5c33 | 77 | * a 2X speedup on an MCU equipped with two independent ADC samplers. |
mjr | 82:4f6209cb5c33 | 78 | * Unfortunately, the KL25Z is not so equipped; even though it might appear |
mjr | 82:4f6209cb5c33 | 79 | * at first glance to support multiple ADC "channels", all of the channels |
mjr | 82:4f6209cb5c33 | 80 | * internally connect to a single ADC sampler, so the hardware can ultimately |
mjr | 82:4f6209cb5c33 | 81 | * perform only one conversion at a time. Paradoxically, using the sensor's |
mjr | 82:4f6209cb5c33 | 82 | * parallel mode is actually *slower* with a KL25Z than using its serial |
mjr | 82:4f6209cb5c33 | 83 | * mode, because we can only maintain the higher throughput of the KL25Z |
mjr | 82:4f6209cb5c33 | 84 | * ADC's "continuous sampling mode" by reading all samples thorugh a single |
mjr | 82:4f6209cb5c33 | 85 | * channel. |
mjr | 82:4f6209cb5c33 | 86 | * |
mjr | 82:4f6209cb5c33 | 87 | * Here's the tricky approach we use: |
mjr | 82:4f6209cb5c33 | 88 | * |
mjr | 82:4f6209cb5c33 | 89 | * First, we put the analog input port (the ADC == Analog-to-Digital |
mjr | 82:4f6209cb5c33 | 90 | * Converter) in "continuous" mode, at the highest clock speed we can |
mjr | 82:4f6209cb5c33 | 91 | * program with the available clocks and the fastest read cycle |
mjr | 82:4f6209cb5c33 | 92 | * available in the ADC hardware. (The analog input port is the |
mjr | 82:4f6209cb5c33 | 93 | * GPIO pin attached to the sensor's AO == Analog Output pin, where |
mjr | 82:4f6209cb5c33 | 94 | * it outputs each pixel's value, one at a time, as an analog voltage |
mjr | 82:4f6209cb5c33 | 95 | * level.) In continuous mode, every time the ADC finishes taking a |
mjr | 82:4f6209cb5c33 | 96 | * sample, it stores the result value in its output register and then |
mjr | 82:4f6209cb5c33 | 97 | * immediately starts taking a new sample. This means that no MCU |
mjr | 82:4f6209cb5c33 | 98 | * (or even DMA) action is required to start each new sample. This |
mjr | 82:4f6209cb5c33 | 99 | * is where most of the speedup comes from, since it takes significant |
mjr | 82:4f6209cb5c33 | 100 | * time (multiple microseconds) to move data through the peripheral |
mjr | 82:4f6209cb5c33 | 101 | * registers, and it takes more time (also multiple microseconds) for |
mjr | 82:4f6209cb5c33 | 102 | * the ADC to spin up for each new sample when in single-sample mode. |
mjr | 82:4f6209cb5c33 | 103 | * We cut out about 7us this way and get the time per sample down to |
mjr | 82:4f6209cb5c33 | 104 | * about 2us. This is close to the documented maximum speed for the |
mjr | 82:4f6209cb5c33 | 105 | * ADC hardware. |
mjr | 82:4f6209cb5c33 | 106 | * |
mjr | 82:4f6209cb5c33 | 107 | * Second, we use the DMA controller to read the ADC result register |
mjr | 82:4f6209cb5c33 | 108 | * and store each sample in a memory array for processing. The ADC |
mjr | 82:4f6209cb5c33 | 109 | * hardware is designed to work with the DMA controller by signaling |
mjr | 82:4f6209cb5c33 | 110 | * the DMA controller when a new sample is ready; this allows DMA to |
mjr | 82:4f6209cb5c33 | 111 | * move each sample immediately when it's available without any CPU |
mjr | 82:4f6209cb5c33 | 112 | * involvement. |
mjr | 82:4f6209cb5c33 | 113 | * |
mjr | 82:4f6209cb5c33 | 114 | * Third - and this is where it really gets tricky - we use two |
mjr | 82:4f6209cb5c33 | 115 | * additional "linked" DMA channels to generate the clock signal |
mjr | 82:4f6209cb5c33 | 116 | * to the CCD sensor. The clock signal is how we tell the CCD when |
mjr | 82:4f6209cb5c33 | 117 | * to place the next pixel voltage on its AO pin, so the clock has |
mjr | 82:4f6209cb5c33 | 118 | * to be generated in lock step with the ADC sampling cycle. The |
mjr | 82:4f6209cb5c33 | 119 | * ADC timing isn't perfectly uniform or predictable, so we can't |
mjr | 82:4f6209cb5c33 | 120 | * just generate the pixel clock with a *real* clock. We have to |
mjr | 82:4f6209cb5c33 | 121 | * time the signal exactly with the ADC, which means that we have |
mjr | 82:4f6209cb5c33 | 122 | * to generate it from the ADC "sample is ready" signal. Fortunately, |
mjr | 82:4f6209cb5c33 | 123 | * there is just such a signal, and in fact we're already using it, |
mjr | 82:4f6209cb5c33 | 124 | * as described above, to tell the DMA when to move each result from |
mjr | 82:4f6209cb5c33 | 125 | * the ADC output register to our memory array. So how do we use this |
mjr | 82:4f6209cb5c33 | 126 | * to generate the CCD clock? The answer lies in the DMA controller's |
mjr | 82:4f6209cb5c33 | 127 | * channel linking feature. This allows one DMA channel to trigger a |
mjr | 82:4f6209cb5c33 | 128 | * second DMA channel each time the first channel completes one |
mjr | 82:4f6209cb5c33 | 129 | * transfer. And we can use DMA to control our clock GPIO pin by |
mjr | 82:4f6209cb5c33 | 130 | * using the pin's GPIO IPORT register as the DMA destination address. |
mjr | 82:4f6209cb5c33 | 131 | * Specifically, we can take the clock high by writing our pin's bit |
mjr | 82:4f6209cb5c33 | 132 | * pattern to the PSOR ("set output") register, and we can take the |
mjr | 82:4f6209cb5c33 | 133 | * clock low by writing to the PCOR ("clear output") register. We |
mjr | 82:4f6209cb5c33 | 134 | * use one DMA channel for each of these operations. |
mjr | 82:4f6209cb5c33 | 135 | * |
mjr | 82:4f6209cb5c33 | 136 | * Putting it all together, the cascade of linked DMA channels |
mjr | 82:4f6209cb5c33 | 137 | * works like this: |
mjr | 82:4f6209cb5c33 | 138 | * |
mjr | 82:4f6209cb5c33 | 139 | * - We kick off the first ADC sample. |
mjr | 82:4f6209cb5c33 | 140 | * |
mjr | 82:4f6209cb5c33 | 141 | * - When the ADC sample completes, the ADC DMA trigger fires, |
mjr | 82:4f6209cb5c33 | 142 | * which triggers channel 1, the "Clock Up" channel. This |
mjr | 82:4f6209cb5c33 | 143 | * performs one transfer of the clock GPIO bit to the clock |
mjr | 82:4f6209cb5c33 | 144 | * PSOR register, taking the clock high, which causes the CCD |
mjr | 82:4f6209cb5c33 | 145 | * to move the next pixel onto AO. |
mjr | 82:4f6209cb5c33 | 146 | * |
mjr | 82:4f6209cb5c33 | 147 | * - After the Clock Up channel does its transfer, it triggers |
mjr | 82:4f6209cb5c33 | 148 | * its link to channel 2, the ADC transfer channel. This |
mjr | 82:4f6209cb5c33 | 149 | * channel moves the ADC output register value to our memory |
mjr | 82:4f6209cb5c33 | 150 | * array. |
mjr | 82:4f6209cb5c33 | 151 | * |
mjr | 82:4f6209cb5c33 | 152 | * - After the ADC channel does its transfer, it triggers channel |
mjr | 82:4f6209cb5c33 | 153 | * 3, the "Clock Down" channel. This performs one transfer of |
mjr | 82:4f6209cb5c33 | 154 | * the clock GPIO bit to the clock PCOR register, taking the |
mjr | 82:4f6209cb5c33 | 155 | * clock low. |
mjr | 82:4f6209cb5c33 | 156 | * |
mjr | 82:4f6209cb5c33 | 157 | * Note that the order of the channels - Clock Up, ADC, Clock Down - |
mjr | 82:4f6209cb5c33 | 158 | * is important. It ensures that we don't toggle the clock line |
mjr | 82:4f6209cb5c33 | 159 | * too quickly. The CCD has a minimum pulse duration of 50ns for |
mjr | 82:4f6209cb5c33 | 160 | * the clock signal. The DMA controller is so fast that it might |
mjr | 82:4f6209cb5c33 | 161 | * toggle the clock faster than this limit if we did the Up and |
mjr | 82:4f6209cb5c33 | 162 | * Down transfers back-to-back. |
mjr | 82:4f6209cb5c33 | 163 | * |
mjr | 82:4f6209cb5c33 | 164 | * Note also that it's important for Clock Up to be the very first |
mjr | 82:4f6209cb5c33 | 165 | * operation after the DMA trigger. The ADC is in continuous mode, |
mjr | 82:4f6209cb5c33 | 166 | * meaning that it starts taking a new sample immediately upon |
mjr | 82:4f6209cb5c33 | 167 | * finishing the previous one. So when the ADC DMA signal fires, |
mjr | 82:4f6209cb5c33 | 168 | * the new sample is already starting. We therefore have to get |
mjr | 82:4f6209cb5c33 | 169 | * the next pixel onto the sampling pin immediately, or as close |
mjr | 82:4f6209cb5c33 | 170 | * to immediately as possible. The sensor's "analog output |
mjr | 82:4f6209cb5c33 | 171 | * settling time" is 120ns - this is the time for a new pixel |
mjr | 82:4f6209cb5c33 | 172 | * voltage to stabilize on AO after a clock rising edge. So |
mjr | 82:4f6209cb5c33 | 173 | * assuming that the ADC raises the DMA signal immediately on |
mjr | 82:4f6209cb5c33 | 174 | * sample completion, and the DMA controller responds within a |
mjr | 82:4f6209cb5c33 | 175 | * couple of MCU clock cycles, we should have the new pixel voltage |
mjr | 82:4f6209cb5c33 | 176 | * stable on the sampling pin by about 200ns after the new ADC |
mjr | 82:4f6209cb5c33 | 177 | * sample cycle starts. The sampling cycle with our current |
mjr | 82:4f6209cb5c33 | 178 | * parameters is about 2us, so the voltage level is stable for |
mjr | 82:4f6209cb5c33 | 179 | * 90% of the cycle. |
mjr | 82:4f6209cb5c33 | 180 | * |
mjr | 82:4f6209cb5c33 | 181 | * Also, note that it's okay that the ADC sample transfer doesn't |
mjr | 82:4f6209cb5c33 | 182 | * happen until after the Clock Up DMA transfer. The ADC output |
mjr | 82:4f6209cb5c33 | 183 | * register holds the last result until the next sample completes, |
mjr | 82:4f6209cb5c33 | 184 | * so we have about 2us to grab it. The first Clock Up DMA |
mjr | 82:4f6209cb5c33 | 185 | * transfer only takes a couple of clocks - order of 100ns - so |
mjr | 82:4f6209cb5c33 | 186 | * we get to it with time to spare. |
mjr | 82:4f6209cb5c33 | 187 | * |
mjr | 82:4f6209cb5c33 | 188 | * (Note that it would nicer to handle the clock with a single DMA |
mjr | 82:4f6209cb5c33 | 189 | * channel, since DMA channels are a limited resource. We could |
mjr | 82:4f6209cb5c33 | 190 | * conceivably consolidate the clock generator one DMA channel by |
mjr | 82:4f6209cb5c33 | 191 | * switching the DMA destination to the PTOR "toggle" register, and |
mjr | 82:4f6209cb5c33 | 192 | * writing *two* times per trigger - once to toggle the clock up, |
mjr | 82:4f6209cb5c33 | 193 | * and a second time to toggle it down. But I haven't found a way |
mjr | 82:4f6209cb5c33 | 194 | * to make this work. The obstacle is that the DMA controller can |
mjr | 82:4f6209cb5c33 | 195 | * only do one transfer per trigger in the fully autonomous mode |
mjr | 82:4f6209cb5c33 | 196 | * we're using, and to make this toggle scheme work, we'd have to do |
mjr | 82:4f6209cb5c33 | 197 | * two writes per trigger. Maybe even three or four: I think we'd |
mjr | 82:4f6209cb5c33 | 198 | * have to throw in one or two no-op writes (of all zeroes) between |
mjr | 82:4f6209cb5c33 | 199 | * the two toggles, to pad the timing to ensure that the clock pulse |
mjr | 82:4f6209cb5c33 | 200 | * width is over the sensor's 50ns minimum. But it's the same issue |
mjr | 82:4f6209cb5c33 | 201 | * whether it's two writes or four. The DMA controller does have a |
mjr | 82:4f6209cb5c33 | 202 | * "continuous" mode that does an entire transfer on a single trigger, |
mjr | 82:4f6209cb5c33 | 203 | * but it can't reset itself after such a transfer; CPU intervention |
mjr | 82:4f6209cb5c33 | 204 | * is required to do that, which means we'd have to service an |
mjr | 82:4f6209cb5c33 | 205 | * interrupt on every ADC cycle to set up the next clock write. |
mjr | 82:4f6209cb5c33 | 206 | * Given the 2us cycle time, an interrupt would create a ton of CPU |
mjr | 82:4f6209cb5c33 | 207 | * load, and I don't think the CPU is fast enough to reliably complete |
mjr | 82:4f6209cb5c33 | 208 | * the work we'd have to do on each 2us cycle. Fortunately, at |
mjr | 82:4f6209cb5c33 | 209 | * the moment we can afford to dedicate three channels to this |
mjr | 82:4f6209cb5c33 | 210 | * module. We only have one other module using the DMA at all |
mjr | 82:4f6209cb5c33 | 211 | * (the TLC5940 PWM controller interface), and it only needs one |
mjr | 82:4f6209cb5c33 | 212 | * channel. So the KL25Z's complement of four DMA channels is just |
mjr | 82:4f6209cb5c33 | 213 | * enough for all of our needs for the moment.) |
mjr | 82:4f6209cb5c33 | 214 | */ |
mjr | 82:4f6209cb5c33 | 215 | |
mjr | 82:4f6209cb5c33 | 216 | #include "mbed.h" |
mjr | 82:4f6209cb5c33 | 217 | #include "config.h" |
mjr | 82:4f6209cb5c33 | 218 | #include "AltAnalogIn.h" |
mjr | 82:4f6209cb5c33 | 219 | #include "SimpleDMA.h" |
mjr | 82:4f6209cb5c33 | 220 | #include "DMAChannels.h" |
mjr | 82:4f6209cb5c33 | 221 | |
mjr | 82:4f6209cb5c33 | 222 | #ifndef TSL14XX_H |
mjr | 82:4f6209cb5c33 | 223 | #define TSL14XX_H |
mjr | 82:4f6209cb5c33 | 224 | |
mjr | 82:4f6209cb5c33 | 225 | |
mjr | 82:4f6209cb5c33 | 226 | // To allow DMA access to the clock pin, we need to point the DMA |
mjr | 82:4f6209cb5c33 | 227 | // controller to the IOPORT registers that control the pin. PORT_BASE() |
mjr | 82:4f6209cb5c33 | 228 | // gives us the address of the register group for the 32 GPIO pins with |
mjr | 82:4f6209cb5c33 | 229 | // the same letter name as our target pin (e.g., PTA0 through PTA31), |
mjr | 82:4f6209cb5c33 | 230 | // and PINMASK gives us the bit pattern to write to those registers to |
mjr | 82:4f6209cb5c33 | 231 | // access our single GPIO pin. Each register group has three special |
mjr | 82:4f6209cb5c33 | 232 | // registers that update the pin in particular ways: PSOR ("set output |
mjr | 82:4f6209cb5c33 | 233 | // register") turns pins on, PCOR ("clear output register") turns pins |
mjr | 82:4f6209cb5c33 | 234 | // off, and PTOR ("toggle output register") toggle pins to the opposite |
mjr | 82:4f6209cb5c33 | 235 | // of their current values. These registers have special semantics: |
mjr | 82:4f6209cb5c33 | 236 | // writing a bit as 0 has no effect on the corresponding pin, while |
mjr | 82:4f6209cb5c33 | 237 | // writing a bit as 1 performs the register's action on the pin. This |
mjr | 82:4f6209cb5c33 | 238 | // allows a single GPIO pin to be set, cleared, or toggled with a |
mjr | 82:4f6209cb5c33 | 239 | // 32-bit write to one of these registers, without affecting any of the |
mjr | 82:4f6209cb5c33 | 240 | // other pins addressed by the register. (It also allows changing any |
mjr | 82:4f6209cb5c33 | 241 | // group of pins with a single write, although we don't use that |
mjr | 82:4f6209cb5c33 | 242 | // feature here.) |
mjr | 82:4f6209cb5c33 | 243 | // |
mjr | 82:4f6209cb5c33 | 244 | // - To turn a pin ON: PORT_BASE(pin)->PSOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 245 | // - To turn a pin OFF: PORT_BASE(pin)->PCOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 246 | // - To toggle a pin: PORT_BASE(pin)->PTOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 247 | // |
mjr | 82:4f6209cb5c33 | 248 | #define GPIO_PORT(pin) (((unsigned int)(pin)) >> PORT_SHIFT) |
mjr | 82:4f6209cb5c33 | 249 | #define GPIO_PORT_BASE(pin) ((GPIO_Type *)(PTA_BASE + GPIO_PORT(pin) * 0x40)) |
mjr | 82:4f6209cb5c33 | 250 | #define GPIO_PINMASK(pin) gpio_set(pin) |
mjr | 82:4f6209cb5c33 | 251 | |
mjr | 82:4f6209cb5c33 | 252 | IF_DIAG( |
mjr | 82:4f6209cb5c33 | 253 | extern uint64_t mainLoopIterCheckpt[]; |
mjr | 82:4f6209cb5c33 | 254 | extern Timer mainLoopTimer;) |
mjr | 82:4f6209cb5c33 | 255 | |
mjr | 82:4f6209cb5c33 | 256 | class TSL14xx |
mjr | 82:4f6209cb5c33 | 257 | { |
mjr | 82:4f6209cb5c33 | 258 | public: |
mjr | 82:4f6209cb5c33 | 259 | // Set up the interface. |
mjr | 82:4f6209cb5c33 | 260 | // |
mjr | 82:4f6209cb5c33 | 261 | // nPixSensor = native number of pixels on sensor |
mjr | 82:4f6209cb5c33 | 262 | // siPin = SI pin (GPIO, digital out) |
mjr | 82:4f6209cb5c33 | 263 | // clockPin = CLK pin (GPIO, digital out) |
mjr | 82:4f6209cb5c33 | 264 | // aoPin = AO pin (GPIO, analog in - must be ADC-capable) |
mjr | 82:4f6209cb5c33 | 265 | TSL14xx(int nPixSensor, PinName siPin, PinName clockPin, PinName aoPin) |
mjr | 82:4f6209cb5c33 | 266 | : adc_dma(DMAch_ADC), |
mjr | 82:4f6209cb5c33 | 267 | clkUp_dma(DMAch_CLKUP), |
mjr | 82:4f6209cb5c33 | 268 | clkDn_dma(DMAch_CLKDN), |
mjr | 82:4f6209cb5c33 | 269 | si(siPin), |
mjr | 82:4f6209cb5c33 | 270 | clock(clockPin), |
mjr | 82:4f6209cb5c33 | 271 | ao(aoPin, true), |
mjr | 82:4f6209cb5c33 | 272 | nPixSensor(nPixSensor) |
mjr | 82:4f6209cb5c33 | 273 | { |
mjr | 82:4f6209cb5c33 | 274 | // start the sample timer with an arbitrary zero point of 'now' |
mjr | 82:4f6209cb5c33 | 275 | t.start(); |
mjr | 82:4f6209cb5c33 | 276 | |
mjr | 82:4f6209cb5c33 | 277 | // allocate our double pixel buffers |
mjr | 82:4f6209cb5c33 | 278 | pix1 = new uint8_t[nPixSensor*2]; |
mjr | 82:4f6209cb5c33 | 279 | pix2 = pix1 + nPixSensor; |
mjr | 82:4f6209cb5c33 | 280 | |
mjr | 82:4f6209cb5c33 | 281 | // put the first DMA transfer into the first buffer (pix1) |
mjr | 82:4f6209cb5c33 | 282 | pixDMA = 0; |
mjr | 82:4f6209cb5c33 | 283 | running = false; |
mjr | 82:4f6209cb5c33 | 284 | |
mjr | 82:4f6209cb5c33 | 285 | // remember the clock pin port base and pin mask for fast access |
mjr | 82:4f6209cb5c33 | 286 | clockPort = GPIO_PORT_BASE(clockPin); |
mjr | 82:4f6209cb5c33 | 287 | clockMask = GPIO_PINMASK(clockPin); |
mjr | 82:4f6209cb5c33 | 288 | |
mjr | 82:4f6209cb5c33 | 289 | // clear out power-on random data by clocking through all pixels twice |
mjr | 82:4f6209cb5c33 | 290 | clear(); |
mjr | 82:4f6209cb5c33 | 291 | clear(); |
mjr | 82:4f6209cb5c33 | 292 | |
mjr | 82:4f6209cb5c33 | 293 | // Set up the Clock Up DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 294 | // clock high by writing the clock bit to the PSOR (set output) |
mjr | 82:4f6209cb5c33 | 295 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 296 | clkUp_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 297 | clkUp_dma.destination(&clockPort->PSOR, false, 32); |
mjr | 82:4f6209cb5c33 | 298 | |
mjr | 82:4f6209cb5c33 | 299 | // Set up the Clock Down DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 300 | // clock low by writing the clock bit to the PCOR (clear output) |
mjr | 82:4f6209cb5c33 | 301 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 302 | clkDn_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 303 | clkDn_dma.destination(&clockPort->PCOR, false, 32); |
mjr | 82:4f6209cb5c33 | 304 | |
mjr | 82:4f6209cb5c33 | 305 | // Set up the ADC transfer DMA channel. This channel transfers |
mjr | 82:4f6209cb5c33 | 306 | // the current analog sampling result from the ADC output register |
mjr | 82:4f6209cb5c33 | 307 | // to our pixel array. |
mjr | 82:4f6209cb5c33 | 308 | ao.initDMA(&adc_dma); |
mjr | 82:4f6209cb5c33 | 309 | |
mjr | 82:4f6209cb5c33 | 310 | // Set up our chain of linked DMA channel: |
mjr | 82:4f6209cb5c33 | 311 | // |
mjr | 82:4f6209cb5c33 | 312 | // ADC sample completion triggers Clock Up |
mjr | 82:4f6209cb5c33 | 313 | // ...which triggers the ADC transfer |
mjr | 82:4f6209cb5c33 | 314 | // ...which triggers Clock Down |
mjr | 82:4f6209cb5c33 | 315 | // |
mjr | 82:4f6209cb5c33 | 316 | // We operate the ADC in "continuous mode", meaning that it starts |
mjr | 82:4f6209cb5c33 | 317 | // a new sample immediately after the last one completes. This is |
mjr | 82:4f6209cb5c33 | 318 | // what keeps the cycle going after the Clock Down, since the Clock |
mjr | 82:4f6209cb5c33 | 319 | // Down transfer itself doesn't trigger another DMA operation. |
mjr | 82:4f6209cb5c33 | 320 | clkUp_dma.trigger(Trigger_ADC0); |
mjr | 82:4f6209cb5c33 | 321 | clkUp_dma.link(adc_dma); |
mjr | 82:4f6209cb5c33 | 322 | adc_dma.link(clkDn_dma, false); |
mjr | 82:4f6209cb5c33 | 323 | |
mjr | 82:4f6209cb5c33 | 324 | // Set the trigger on the downstream links to NONE - these are |
mjr | 82:4f6209cb5c33 | 325 | // triggered by their upstream links, so they don't need separate |
mjr | 82:4f6209cb5c33 | 326 | // peripheral or software triggers. |
mjr | 82:4f6209cb5c33 | 327 | adc_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 328 | clkDn_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 329 | |
mjr | 82:4f6209cb5c33 | 330 | // Register an interrupt callback so that we're notified when |
mjr | 82:4f6209cb5c33 | 331 | // the last transfer completes. |
mjr | 82:4f6209cb5c33 | 332 | clkDn_dma.attach(this, &TSL14xx::transferDone); |
mjr | 82:4f6209cb5c33 | 333 | |
mjr | 82:4f6209cb5c33 | 334 | // clear the timing statistics |
mjr | 82:4f6209cb5c33 | 335 | totalTime = 0.0; |
mjr | 82:4f6209cb5c33 | 336 | nRuns = 0; |
mjr | 82:4f6209cb5c33 | 337 | } |
mjr | 82:4f6209cb5c33 | 338 | |
mjr | 82:4f6209cb5c33 | 339 | // Get the stable pixel array. This is the image array from the |
mjr | 82:4f6209cb5c33 | 340 | // previous capture. It remains valid until the next startCapture() |
mjr | 82:4f6209cb5c33 | 341 | // call, at which point this buffer will be reused for the new capture. |
mjr | 82:4f6209cb5c33 | 342 | void getPix(uint8_t * &pix, uint32_t &t) |
mjr | 82:4f6209cb5c33 | 343 | { |
mjr | 82:4f6209cb5c33 | 344 | // return the pixel array that ISN'T assigned to the DMA |
mjr | 82:4f6209cb5c33 | 345 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 346 | { |
mjr | 82:4f6209cb5c33 | 347 | // DMA owns pix2, so the stable array is pix1 |
mjr | 82:4f6209cb5c33 | 348 | pix = pix1; |
mjr | 82:4f6209cb5c33 | 349 | t = t1; |
mjr | 82:4f6209cb5c33 | 350 | } |
mjr | 82:4f6209cb5c33 | 351 | else |
mjr | 82:4f6209cb5c33 | 352 | { |
mjr | 82:4f6209cb5c33 | 353 | // DMA owns pix1, so the stable array is pix2 |
mjr | 82:4f6209cb5c33 | 354 | pix = pix2; |
mjr | 82:4f6209cb5c33 | 355 | t = t2; |
mjr | 82:4f6209cb5c33 | 356 | } |
mjr | 82:4f6209cb5c33 | 357 | } |
mjr | 82:4f6209cb5c33 | 358 | |
mjr | 82:4f6209cb5c33 | 359 | // Start an image capture from the sensor. Waits the previous |
mjr | 82:4f6209cb5c33 | 360 | // capture to finish if it's still running, then starts a new one |
mjr | 82:4f6209cb5c33 | 361 | // and returns immediately. The new capture proceeds autonomously |
mjr | 82:4f6209cb5c33 | 362 | // via the DMA hardware, so the caller can continue with other |
mjr | 82:4f6209cb5c33 | 363 | // processing during the capture. |
mjr | 82:4f6209cb5c33 | 364 | void startCapture(uint32_t minIntTime_us = 0) |
mjr | 82:4f6209cb5c33 | 365 | { |
mjr | 82:4f6209cb5c33 | 366 | IF_DIAG(uint32_t tDiag0 = mainLoopTimer.read_us();) |
mjr | 82:4f6209cb5c33 | 367 | |
mjr | 82:4f6209cb5c33 | 368 | // wait for the last current capture to finish |
mjr | 82:4f6209cb5c33 | 369 | while (running) { } |
mjr | 82:4f6209cb5c33 | 370 | |
mjr | 82:4f6209cb5c33 | 371 | // we're starting a new capture immediately |
mjr | 82:4f6209cb5c33 | 372 | running = true; |
mjr | 82:4f6209cb5c33 | 373 | |
mjr | 82:4f6209cb5c33 | 374 | // collect timing diagnostics |
mjr | 82:4f6209cb5c33 | 375 | IF_DIAG(mainLoopIterCheckpt[8] += uint32_t(mainLoopTimer.read_us() - tDiag0);) |
mjr | 82:4f6209cb5c33 | 376 | |
mjr | 82:4f6209cb5c33 | 377 | // If the elapsed time since the start of the last integration |
mjr | 82:4f6209cb5c33 | 378 | // hasn't reached the specified minimum yet, wait. This allows |
mjr | 82:4f6209cb5c33 | 379 | // the caller to control the integration time to optimize the |
mjr | 82:4f6209cb5c33 | 380 | // exposure level. |
mjr | 82:4f6209cb5c33 | 381 | uint32_t dt = uint32_t(t.read_us() - tInt); |
mjr | 82:4f6209cb5c33 | 382 | if (dt < minIntTime_us) |
mjr | 82:4f6209cb5c33 | 383 | { |
mjr | 82:4f6209cb5c33 | 384 | // we haven't reached the required minimum yet - wait for the |
mjr | 82:4f6209cb5c33 | 385 | // remaining interval |
mjr | 82:4f6209cb5c33 | 386 | wait_us(minIntTime_us - dt); |
mjr | 82:4f6209cb5c33 | 387 | } |
mjr | 82:4f6209cb5c33 | 388 | |
mjr | 82:4f6209cb5c33 | 389 | // swap to the other DMA buffer for reading the new pixel samples |
mjr | 82:4f6209cb5c33 | 390 | pixDMA ^= 1; |
mjr | 82:4f6209cb5c33 | 391 | |
mjr | 82:4f6209cb5c33 | 392 | // Set up the active pixel array as the destination buffer for |
mjr | 82:4f6209cb5c33 | 393 | // the ADC DMA channel. |
mjr | 82:4f6209cb5c33 | 394 | adc_dma.destination(pixDMA ? pix2 : pix1, true); |
mjr | 82:4f6209cb5c33 | 395 | |
mjr | 82:4f6209cb5c33 | 396 | // start the DMA transfers |
mjr | 82:4f6209cb5c33 | 397 | clkDn_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 398 | adc_dma.start(nPixSensor, true); |
mjr | 82:4f6209cb5c33 | 399 | clkUp_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 400 | |
mjr | 82:4f6209cb5c33 | 401 | // note the start time of this transfer |
mjr | 82:4f6209cb5c33 | 402 | t0 = t.read_us(); |
mjr | 82:4f6209cb5c33 | 403 | |
mjr | 82:4f6209cb5c33 | 404 | // start the next integration cycle by pulsing SI and one clock |
mjr | 82:4f6209cb5c33 | 405 | si = 1; |
mjr | 82:4f6209cb5c33 | 406 | clock = 1; |
mjr | 82:4f6209cb5c33 | 407 | si = 0; |
mjr | 82:4f6209cb5c33 | 408 | clock = 0; |
mjr | 82:4f6209cb5c33 | 409 | |
mjr | 82:4f6209cb5c33 | 410 | // Set the timestamp for the current active buffer. The SI pulse |
mjr | 82:4f6209cb5c33 | 411 | // we just did performed the HOLD operation, which transfers the |
mjr | 82:4f6209cb5c33 | 412 | // current integration cycle's pixel charges to the output |
mjr | 82:4f6209cb5c33 | 413 | // capacitors in the sensor. We noted the start of the current |
mjr | 82:4f6209cb5c33 | 414 | // integration cycle in tInt when we started it during the previous |
mjr | 82:4f6209cb5c33 | 415 | // scan. The image we're about to transfer therefore represents |
mjr | 82:4f6209cb5c33 | 416 | // the photons collected between tInt and right now (actually, the |
mjr | 82:4f6209cb5c33 | 417 | // SI pulse above, but close enough). Set the timestamp to the |
mjr | 82:4f6209cb5c33 | 418 | // midpoint between tInt and now. |
mjr | 82:4f6209cb5c33 | 419 | uint32_t tmid = (t0 + tInt) >> 1; |
mjr | 82:4f6209cb5c33 | 420 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 421 | t2 = tmid; |
mjr | 82:4f6209cb5c33 | 422 | else |
mjr | 82:4f6209cb5c33 | 423 | t1 = tmid; |
mjr | 82:4f6209cb5c33 | 424 | |
mjr | 82:4f6209cb5c33 | 425 | //$$$ |
mjr | 82:4f6209cb5c33 | 426 | // pad the timing slightly |
mjr | 82:4f6209cb5c33 | 427 | // clock = 0; |
mjr | 82:4f6209cb5c33 | 428 | |
mjr | 82:4f6209cb5c33 | 429 | // // clock in the first pixel |
mjr | 82:4f6209cb5c33 | 430 | // clock = 1; |
mjr | 82:4f6209cb5c33 | 431 | // clock = 0; |
mjr | 82:4f6209cb5c33 | 432 | //$$$ |
mjr | 82:4f6209cb5c33 | 433 | |
mjr | 82:4f6209cb5c33 | 434 | // Start the ADC sampler. The ADC will read samples continuously |
mjr | 82:4f6209cb5c33 | 435 | // until we tell it to stop. Each sample completion will trigger |
mjr | 82:4f6209cb5c33 | 436 | // our linked DMA channel, which will store the next sample in our |
mjr | 82:4f6209cb5c33 | 437 | // pixel array and pulse the CCD serial data clock to load the next |
mjr | 82:4f6209cb5c33 | 438 | // pixel onto the analog sampler pin. This will all happen without |
mjr | 82:4f6209cb5c33 | 439 | // any CPU involvement, so we can continue with other work. |
mjr | 82:4f6209cb5c33 | 440 | ao.start(); |
mjr | 82:4f6209cb5c33 | 441 | |
mjr | 82:4f6209cb5c33 | 442 | // The new integration cycle starts with the 19th clock pulse |
mjr | 82:4f6209cb5c33 | 443 | // after the SI pulse. We offload all of the transfer work (including |
mjr | 82:4f6209cb5c33 | 444 | // the clock pulse generation) to the DMA controller, so we won't |
mjr | 82:4f6209cb5c33 | 445 | // be notified of exactly when that 19th clock occurs. To keep things |
mjr | 82:4f6209cb5c33 | 446 | // simple, aproximate it as now plus 19 2us sample times. This isn't |
mjr | 82:4f6209cb5c33 | 447 | // exact, since it will vary according to the ADC spin-up time and the |
mjr | 82:4f6209cb5c33 | 448 | // actual sampling time, but 19*2us is close enough given that the |
mjr | 82:4f6209cb5c33 | 449 | // overall integration time we're measuring will be about 64x longer |
mjr | 82:4f6209cb5c33 | 450 | // (around 2.5ms), so even if the 19*2us estimate is off by 100%, our |
mjr | 82:4f6209cb5c33 | 451 | // overall time estimate will still be accurate to about 1.5%. |
mjr | 82:4f6209cb5c33 | 452 | tInt = t.read_us() + 38; |
mjr | 82:4f6209cb5c33 | 453 | |
mjr | 82:4f6209cb5c33 | 454 | IF_DIAG(mainLoopIterCheckpt[9] += uint32_t(mainLoopTimer.read_us() - tDiag0);) |
mjr | 82:4f6209cb5c33 | 455 | } |
mjr | 82:4f6209cb5c33 | 456 | |
mjr | 82:4f6209cb5c33 | 457 | // Wait for the current capture to finish |
mjr | 82:4f6209cb5c33 | 458 | void wait() |
mjr | 82:4f6209cb5c33 | 459 | { |
mjr | 82:4f6209cb5c33 | 460 | while (running) { } |
mjr | 82:4f6209cb5c33 | 461 | } |
mjr | 82:4f6209cb5c33 | 462 | |
mjr | 82:4f6209cb5c33 | 463 | // Is a reading ready? |
mjr | 82:4f6209cb5c33 | 464 | bool ready() const { return !running; } |
mjr | 82:4f6209cb5c33 | 465 | |
mjr | 82:4f6209cb5c33 | 466 | // Clock through all pixels to clear the array. Pulses SI at the |
mjr | 82:4f6209cb5c33 | 467 | // beginning of the operation, which starts a new integration cycle. |
mjr | 82:4f6209cb5c33 | 468 | void clear() |
mjr | 82:4f6209cb5c33 | 469 | { |
mjr | 82:4f6209cb5c33 | 470 | // get the clock toggle register |
mjr | 82:4f6209cb5c33 | 471 | volatile uint32_t *ptor = &clockPort->PTOR; |
mjr | 82:4f6209cb5c33 | 472 | |
mjr | 82:4f6209cb5c33 | 473 | // make sure any DMA run is completed |
mjr | 82:4f6209cb5c33 | 474 | wait(); |
mjr | 82:4f6209cb5c33 | 475 | |
mjr | 82:4f6209cb5c33 | 476 | // clock in an SI pulse |
mjr | 82:4f6209cb5c33 | 477 | si = 1; |
mjr | 82:4f6209cb5c33 | 478 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 479 | clockPort->PSOR = clockMask; |
mjr | 82:4f6209cb5c33 | 480 | si = 0; |
mjr | 82:4f6209cb5c33 | 481 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 482 | |
mjr | 82:4f6209cb5c33 | 483 | // This starts a new integration period. Or more precisely, the |
mjr | 82:4f6209cb5c33 | 484 | // 19th clock pulse will start the new integration period. We're |
mjr | 82:4f6209cb5c33 | 485 | // going to blast the clock signal as fast as we can, at about |
mjr | 82:4f6209cb5c33 | 486 | // 100ns intervals (50ns up and 50ns down), so the 19th clock |
mjr | 82:4f6209cb5c33 | 487 | // will be about 2us from now. |
mjr | 82:4f6209cb5c33 | 488 | tInt = t.read_us() + 2; |
mjr | 82:4f6209cb5c33 | 489 | |
mjr | 82:4f6209cb5c33 | 490 | // clock out all pixels, plus an extra one to clock past the last |
mjr | 82:4f6209cb5c33 | 491 | // pixel and reset the last pixel's internal sampling switch in |
mjr | 82:4f6209cb5c33 | 492 | // the sensor |
mjr | 82:4f6209cb5c33 | 493 | for (int i = 0 ; i < nPixSensor + 1 ; ) |
mjr | 82:4f6209cb5c33 | 494 | { |
mjr | 82:4f6209cb5c33 | 495 | // toggle the clock to take it high |
mjr | 82:4f6209cb5c33 | 496 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 497 | |
mjr | 82:4f6209cb5c33 | 498 | // increment our loop variable here to pad the timing, to |
mjr | 82:4f6209cb5c33 | 499 | // keep our pulse width long enough for the sensor |
mjr | 82:4f6209cb5c33 | 500 | ++i; |
mjr | 82:4f6209cb5c33 | 501 | |
mjr | 82:4f6209cb5c33 | 502 | // toggle the clock to take it low |
mjr | 82:4f6209cb5c33 | 503 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 504 | } |
mjr | 82:4f6209cb5c33 | 505 | } |
mjr | 82:4f6209cb5c33 | 506 | |
mjr | 82:4f6209cb5c33 | 507 | // get the timing statistics - sum of scan time for all scans so far |
mjr | 82:4f6209cb5c33 | 508 | // in microseconds, and total number of scans so far |
mjr | 82:4f6209cb5c33 | 509 | void getTimingStats(uint64_t &totalTime, uint32_t &nRuns) const |
mjr | 82:4f6209cb5c33 | 510 | { |
mjr | 82:4f6209cb5c33 | 511 | totalTime = this->totalTime; |
mjr | 82:4f6209cb5c33 | 512 | nRuns = this->nRuns; |
mjr | 82:4f6209cb5c33 | 513 | } |
mjr | 82:4f6209cb5c33 | 514 | |
mjr | 82:4f6209cb5c33 | 515 | // get the average scan time in microseconds |
mjr | 82:4f6209cb5c33 | 516 | uint32_t getAvgScanTime() const |
mjr | 82:4f6209cb5c33 | 517 | { |
mjr | 82:4f6209cb5c33 | 518 | return uint32_t(totalTime / nRuns); |
mjr | 82:4f6209cb5c33 | 519 | } |
mjr | 82:4f6209cb5c33 | 520 | |
mjr | 82:4f6209cb5c33 | 521 | private: |
mjr | 82:4f6209cb5c33 | 522 | // end of transfer notification |
mjr | 82:4f6209cb5c33 | 523 | void transferDone() |
mjr | 82:4f6209cb5c33 | 524 | { |
mjr | 82:4f6209cb5c33 | 525 | // stop the ADC sampler |
mjr | 82:4f6209cb5c33 | 526 | ao.stop(); |
mjr | 82:4f6209cb5c33 | 527 | |
mjr | 82:4f6209cb5c33 | 528 | // clock out one extra pixel to leave A1 in the high-Z state |
mjr | 82:4f6209cb5c33 | 529 | clock = 1; |
mjr | 82:4f6209cb5c33 | 530 | clock = 0; |
mjr | 82:4f6209cb5c33 | 531 | |
mjr | 82:4f6209cb5c33 | 532 | // add this sample to the timing statistics (we collect the data |
mjr | 82:4f6209cb5c33 | 533 | // merely to report to the config tool, for diagnostic purposes) |
mjr | 82:4f6209cb5c33 | 534 | totalTime += uint32_t(t.read_us() - t0); |
mjr | 82:4f6209cb5c33 | 535 | nRuns += 1; |
mjr | 82:4f6209cb5c33 | 536 | |
mjr | 82:4f6209cb5c33 | 537 | // the sampler is no long running |
mjr | 82:4f6209cb5c33 | 538 | running = false; |
mjr | 82:4f6209cb5c33 | 539 | } |
mjr | 82:4f6209cb5c33 | 540 | |
mjr | 82:4f6209cb5c33 | 541 | // DMA controller interfaces |
mjr | 82:4f6209cb5c33 | 542 | SimpleDMA adc_dma; // DMA channel for reading the analog input |
mjr | 82:4f6209cb5c33 | 543 | SimpleDMA clkUp_dma; // "Clock Up" channel |
mjr | 82:4f6209cb5c33 | 544 | SimpleDMA clkDn_dma; // "Clock Down" channel |
mjr | 82:4f6209cb5c33 | 545 | |
mjr | 82:4f6209cb5c33 | 546 | // Sensor interface pins |
mjr | 82:4f6209cb5c33 | 547 | DigitalOut si; // GPIO pin for sensor SI (serial data) |
mjr | 82:4f6209cb5c33 | 548 | DigitalOut clock; // GPIO pin for sensor SCLK (serial clock) |
mjr | 82:4f6209cb5c33 | 549 | GPIO_Type *clockPort; // IOPORT base address for clock pin - cached for DMA writes |
mjr | 82:4f6209cb5c33 | 550 | uint32_t clockMask; // IOPORT register bit mask for clock pin |
mjr | 82:4f6209cb5c33 | 551 | AltAnalogIn ao; // GPIO pin for sensor AO (analog output) |
mjr | 82:4f6209cb5c33 | 552 | |
mjr | 82:4f6209cb5c33 | 553 | // number of pixels in the physical sensor array |
mjr | 82:4f6209cb5c33 | 554 | int nPixSensor; // number of pixels in physical sensor array |
mjr | 82:4f6209cb5c33 | 555 | |
mjr | 82:4f6209cb5c33 | 556 | // pixel buffers - we keep two buffers so that we can transfer the |
mjr | 82:4f6209cb5c33 | 557 | // current sensor data into one buffer via DMA while we concurrently |
mjr | 82:4f6209cb5c33 | 558 | // process the last buffer |
mjr | 82:4f6209cb5c33 | 559 | uint8_t *pix1; // pixel array 1 |
mjr | 82:4f6209cb5c33 | 560 | uint8_t *pix2; // pixel array 2 |
mjr | 82:4f6209cb5c33 | 561 | |
mjr | 82:4f6209cb5c33 | 562 | // Timestamps of pix1 and pix2 arrays, in microseconds, in terms of the |
mjr | 82:4f6209cb5c33 | 563 | // ample timer (this->t). |
mjr | 82:4f6209cb5c33 | 564 | uint32_t t1; |
mjr | 82:4f6209cb5c33 | 565 | uint32_t t2; |
mjr | 82:4f6209cb5c33 | 566 | |
mjr | 82:4f6209cb5c33 | 567 | // DMA target buffer. This is the buffer for the next DMA transfer. |
mjr | 82:4f6209cb5c33 | 568 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
mjr | 82:4f6209cb5c33 | 569 | // data from the last transfer. |
mjr | 82:4f6209cb5c33 | 570 | uint8_t pixDMA; |
mjr | 82:4f6209cb5c33 | 571 | |
mjr | 82:4f6209cb5c33 | 572 | // flag: sample is running |
mjr | 82:4f6209cb5c33 | 573 | volatile bool running; |
mjr | 82:4f6209cb5c33 | 574 | |
mjr | 82:4f6209cb5c33 | 575 | // timing statistics |
mjr | 82:4f6209cb5c33 | 576 | Timer t; // sample timer |
mjr | 82:4f6209cb5c33 | 577 | uint32_t t0; // start time (us) of current sample |
mjr | 82:4f6209cb5c33 | 578 | uint32_t tInt; // start time (us) of current integration period |
mjr | 82:4f6209cb5c33 | 579 | uint64_t totalTime; // total time consumed by all reads so far |
mjr | 82:4f6209cb5c33 | 580 | uint32_t nRuns; // number of runs so far |
mjr | 82:4f6209cb5c33 | 581 | }; |
mjr | 82:4f6209cb5c33 | 582 | |
mjr | 82:4f6209cb5c33 | 583 | #endif /* TSL14XX_H */ |