Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
TSL14xx/TSL14xx.h@100:1ff35c07217c, 2019-11-28 (annotated)
- Committer:
- mjr
- Date:
- Thu Nov 28 23:18:23 2019 +0000
- Revision:
- 100:1ff35c07217c
- Parent:
- 87:8d35c74403af
- Child:
- 101:755f44622abc
Added preliminary support for AEAT-6012 and TCD1103 sensors; use continuous averaging for pot sensor analog in; more AltAnalogIn options for timing and resolution
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 | 100:1ff35c07217c | 266 | : adc_dma(DMAch_TSL_ADC), |
mjr | 100:1ff35c07217c | 267 | clkUp_dma(DMAch_TSL_CLKUP), |
mjr | 100:1ff35c07217c | 268 | clkDn_dma(DMAch_TSL_CLKDN), |
mjr | 82:4f6209cb5c33 | 269 | si(siPin), |
mjr | 82:4f6209cb5c33 | 270 | clock(clockPin), |
mjr | 100:1ff35c07217c | 271 | ao(aoPin, true, 0), // continuous sampling, fast sampling mode |
mjr | 82:4f6209cb5c33 | 272 | nPixSensor(nPixSensor) |
mjr | 82:4f6209cb5c33 | 273 | { |
mjr | 100:1ff35c07217c | 274 | // Calibrate the ADC for best accuracy |
mjr | 100:1ff35c07217c | 275 | ao.calibrate(); |
mjr | 100:1ff35c07217c | 276 | |
mjr | 82:4f6209cb5c33 | 277 | // start the sample timer with an arbitrary zero point of 'now' |
mjr | 82:4f6209cb5c33 | 278 | t.start(); |
mjr | 82:4f6209cb5c33 | 279 | |
mjr | 82:4f6209cb5c33 | 280 | // allocate our double pixel buffers |
mjr | 82:4f6209cb5c33 | 281 | pix1 = new uint8_t[nPixSensor*2]; |
mjr | 82:4f6209cb5c33 | 282 | pix2 = pix1 + nPixSensor; |
mjr | 82:4f6209cb5c33 | 283 | |
mjr | 82:4f6209cb5c33 | 284 | // put the first DMA transfer into the first buffer (pix1) |
mjr | 82:4f6209cb5c33 | 285 | pixDMA = 0; |
mjr | 82:4f6209cb5c33 | 286 | running = false; |
mjr | 82:4f6209cb5c33 | 287 | |
mjr | 82:4f6209cb5c33 | 288 | // remember the clock pin port base and pin mask for fast access |
mjr | 82:4f6209cb5c33 | 289 | clockPort = GPIO_PORT_BASE(clockPin); |
mjr | 82:4f6209cb5c33 | 290 | clockMask = GPIO_PINMASK(clockPin); |
mjr | 82:4f6209cb5c33 | 291 | |
mjr | 82:4f6209cb5c33 | 292 | // clear out power-on random data by clocking through all pixels twice |
mjr | 82:4f6209cb5c33 | 293 | clear(); |
mjr | 82:4f6209cb5c33 | 294 | clear(); |
mjr | 82:4f6209cb5c33 | 295 | |
mjr | 82:4f6209cb5c33 | 296 | // Set up the Clock Up DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 297 | // clock high by writing the clock bit to the PSOR (set output) |
mjr | 82:4f6209cb5c33 | 298 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 299 | clkUp_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 300 | clkUp_dma.destination(&clockPort->PSOR, false, 32); |
mjr | 82:4f6209cb5c33 | 301 | |
mjr | 82:4f6209cb5c33 | 302 | // Set up the Clock Down DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 303 | // clock low by writing the clock bit to the PCOR (clear output) |
mjr | 82:4f6209cb5c33 | 304 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 305 | clkDn_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 306 | clkDn_dma.destination(&clockPort->PCOR, false, 32); |
mjr | 82:4f6209cb5c33 | 307 | |
mjr | 82:4f6209cb5c33 | 308 | // Set up the ADC transfer DMA channel. This channel transfers |
mjr | 82:4f6209cb5c33 | 309 | // the current analog sampling result from the ADC output register |
mjr | 82:4f6209cb5c33 | 310 | // to our pixel array. |
mjr | 82:4f6209cb5c33 | 311 | ao.initDMA(&adc_dma); |
mjr | 82:4f6209cb5c33 | 312 | |
mjr | 82:4f6209cb5c33 | 313 | // Set up our chain of linked DMA channel: |
mjr | 82:4f6209cb5c33 | 314 | // |
mjr | 82:4f6209cb5c33 | 315 | // ADC sample completion triggers Clock Up |
mjr | 82:4f6209cb5c33 | 316 | // ...which triggers the ADC transfer |
mjr | 82:4f6209cb5c33 | 317 | // ...which triggers Clock Down |
mjr | 82:4f6209cb5c33 | 318 | // |
mjr | 82:4f6209cb5c33 | 319 | // We operate the ADC in "continuous mode", meaning that it starts |
mjr | 82:4f6209cb5c33 | 320 | // a new sample immediately after the last one completes. This is |
mjr | 82:4f6209cb5c33 | 321 | // what keeps the cycle going after the Clock Down, since the Clock |
mjr | 82:4f6209cb5c33 | 322 | // Down transfer itself doesn't trigger another DMA operation. |
mjr | 82:4f6209cb5c33 | 323 | clkUp_dma.trigger(Trigger_ADC0); |
mjr | 82:4f6209cb5c33 | 324 | clkUp_dma.link(adc_dma); |
mjr | 82:4f6209cb5c33 | 325 | adc_dma.link(clkDn_dma, false); |
mjr | 82:4f6209cb5c33 | 326 | |
mjr | 82:4f6209cb5c33 | 327 | // Set the trigger on the downstream links to NONE - these are |
mjr | 82:4f6209cb5c33 | 328 | // triggered by their upstream links, so they don't need separate |
mjr | 82:4f6209cb5c33 | 329 | // peripheral or software triggers. |
mjr | 82:4f6209cb5c33 | 330 | adc_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 331 | clkDn_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 332 | |
mjr | 82:4f6209cb5c33 | 333 | // Register an interrupt callback so that we're notified when |
mjr | 82:4f6209cb5c33 | 334 | // the last transfer completes. |
mjr | 82:4f6209cb5c33 | 335 | clkDn_dma.attach(this, &TSL14xx::transferDone); |
mjr | 82:4f6209cb5c33 | 336 | |
mjr | 82:4f6209cb5c33 | 337 | // clear the timing statistics |
mjr | 82:4f6209cb5c33 | 338 | totalTime = 0.0; |
mjr | 82:4f6209cb5c33 | 339 | nRuns = 0; |
mjr | 82:4f6209cb5c33 | 340 | } |
mjr | 82:4f6209cb5c33 | 341 | |
mjr | 82:4f6209cb5c33 | 342 | // Get the stable pixel array. This is the image array from the |
mjr | 82:4f6209cb5c33 | 343 | // previous capture. It remains valid until the next startCapture() |
mjr | 82:4f6209cb5c33 | 344 | // call, at which point this buffer will be reused for the new capture. |
mjr | 82:4f6209cb5c33 | 345 | void getPix(uint8_t * &pix, uint32_t &t) |
mjr | 82:4f6209cb5c33 | 346 | { |
mjr | 82:4f6209cb5c33 | 347 | // return the pixel array that ISN'T assigned to the DMA |
mjr | 82:4f6209cb5c33 | 348 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 349 | { |
mjr | 82:4f6209cb5c33 | 350 | // DMA owns pix2, so the stable array is pix1 |
mjr | 82:4f6209cb5c33 | 351 | pix = pix1; |
mjr | 82:4f6209cb5c33 | 352 | t = t1; |
mjr | 82:4f6209cb5c33 | 353 | } |
mjr | 82:4f6209cb5c33 | 354 | else |
mjr | 82:4f6209cb5c33 | 355 | { |
mjr | 82:4f6209cb5c33 | 356 | // DMA owns pix1, so the stable array is pix2 |
mjr | 82:4f6209cb5c33 | 357 | pix = pix2; |
mjr | 82:4f6209cb5c33 | 358 | t = t2; |
mjr | 82:4f6209cb5c33 | 359 | } |
mjr | 82:4f6209cb5c33 | 360 | } |
mjr | 82:4f6209cb5c33 | 361 | |
mjr | 86:e30a1f60f783 | 362 | // Wait for the current DMA transfer to finish, and retrieve its |
mjr | 86:e30a1f60f783 | 363 | // pixel array buffer. This provides access to the latest image |
mjr | 86:e30a1f60f783 | 364 | // without starting a new transfer. These pixels are valid throughout |
mjr | 86:e30a1f60f783 | 365 | // the next transfer (started via startCapture()) and remain valid |
mjr | 86:e30a1f60f783 | 366 | // until the next transfer after that. |
mjr | 86:e30a1f60f783 | 367 | void waitPix(uint8_t * &pix, uint32_t &t) |
mjr | 86:e30a1f60f783 | 368 | { |
mjr | 86:e30a1f60f783 | 369 | // wait for the current transfer to finish |
mjr | 86:e30a1f60f783 | 370 | wait(); |
mjr | 86:e30a1f60f783 | 371 | |
mjr | 86:e30a1f60f783 | 372 | // Return the pixel array that IS assigned to DMA, since this |
mjr | 86:e30a1f60f783 | 373 | // is the latest buffer filled. This buffer is stable, even |
mjr | 86:e30a1f60f783 | 374 | // though it's assigned to DMA, because the last transfer is |
mjr | 86:e30a1f60f783 | 375 | // already finished and thus DMA is no longer accessing the |
mjr | 86:e30a1f60f783 | 376 | // buffer. |
mjr | 86:e30a1f60f783 | 377 | if (pixDMA) |
mjr | 86:e30a1f60f783 | 378 | { |
mjr | 86:e30a1f60f783 | 379 | // DMA owns pix2 |
mjr | 86:e30a1f60f783 | 380 | pix = pix2; |
mjr | 86:e30a1f60f783 | 381 | t = t2; |
mjr | 86:e30a1f60f783 | 382 | } |
mjr | 86:e30a1f60f783 | 383 | else |
mjr | 86:e30a1f60f783 | 384 | { |
mjr | 86:e30a1f60f783 | 385 | // DMA owns pix1 |
mjr | 86:e30a1f60f783 | 386 | pix = pix1; |
mjr | 86:e30a1f60f783 | 387 | t = t1; |
mjr | 86:e30a1f60f783 | 388 | } |
mjr | 86:e30a1f60f783 | 389 | } |
mjr | 86:e30a1f60f783 | 390 | |
mjr | 82:4f6209cb5c33 | 391 | // Start an image capture from the sensor. Waits the previous |
mjr | 82:4f6209cb5c33 | 392 | // capture to finish if it's still running, then starts a new one |
mjr | 82:4f6209cb5c33 | 393 | // and returns immediately. The new capture proceeds autonomously |
mjr | 82:4f6209cb5c33 | 394 | // via the DMA hardware, so the caller can continue with other |
mjr | 82:4f6209cb5c33 | 395 | // processing during the capture. |
mjr | 82:4f6209cb5c33 | 396 | void startCapture(uint32_t minIntTime_us = 0) |
mjr | 82:4f6209cb5c33 | 397 | { |
mjr | 82:4f6209cb5c33 | 398 | IF_DIAG(uint32_t tDiag0 = mainLoopTimer.read_us();) |
mjr | 82:4f6209cb5c33 | 399 | |
mjr | 82:4f6209cb5c33 | 400 | // wait for the last current capture to finish |
mjr | 82:4f6209cb5c33 | 401 | while (running) { } |
mjr | 82:4f6209cb5c33 | 402 | |
mjr | 82:4f6209cb5c33 | 403 | // we're starting a new capture immediately |
mjr | 82:4f6209cb5c33 | 404 | running = true; |
mjr | 82:4f6209cb5c33 | 405 | |
mjr | 82:4f6209cb5c33 | 406 | // collect timing diagnostics |
mjr | 82:4f6209cb5c33 | 407 | IF_DIAG(mainLoopIterCheckpt[8] += uint32_t(mainLoopTimer.read_us() - tDiag0);) |
mjr | 82:4f6209cb5c33 | 408 | |
mjr | 82:4f6209cb5c33 | 409 | // If the elapsed time since the start of the last integration |
mjr | 82:4f6209cb5c33 | 410 | // hasn't reached the specified minimum yet, wait. This allows |
mjr | 82:4f6209cb5c33 | 411 | // the caller to control the integration time to optimize the |
mjr | 82:4f6209cb5c33 | 412 | // exposure level. |
mjr | 82:4f6209cb5c33 | 413 | uint32_t dt = uint32_t(t.read_us() - tInt); |
mjr | 82:4f6209cb5c33 | 414 | if (dt < minIntTime_us) |
mjr | 82:4f6209cb5c33 | 415 | { |
mjr | 82:4f6209cb5c33 | 416 | // we haven't reached the required minimum yet - wait for the |
mjr | 82:4f6209cb5c33 | 417 | // remaining interval |
mjr | 82:4f6209cb5c33 | 418 | wait_us(minIntTime_us - dt); |
mjr | 82:4f6209cb5c33 | 419 | } |
mjr | 82:4f6209cb5c33 | 420 | |
mjr | 82:4f6209cb5c33 | 421 | // swap to the other DMA buffer for reading the new pixel samples |
mjr | 82:4f6209cb5c33 | 422 | pixDMA ^= 1; |
mjr | 82:4f6209cb5c33 | 423 | |
mjr | 82:4f6209cb5c33 | 424 | // Set up the active pixel array as the destination buffer for |
mjr | 82:4f6209cb5c33 | 425 | // the ADC DMA channel. |
mjr | 82:4f6209cb5c33 | 426 | adc_dma.destination(pixDMA ? pix2 : pix1, true); |
mjr | 82:4f6209cb5c33 | 427 | |
mjr | 82:4f6209cb5c33 | 428 | // start the DMA transfers |
mjr | 82:4f6209cb5c33 | 429 | clkDn_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 430 | adc_dma.start(nPixSensor, true); |
mjr | 82:4f6209cb5c33 | 431 | clkUp_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 432 | |
mjr | 82:4f6209cb5c33 | 433 | // note the start time of this transfer |
mjr | 82:4f6209cb5c33 | 434 | t0 = t.read_us(); |
mjr | 82:4f6209cb5c33 | 435 | |
mjr | 82:4f6209cb5c33 | 436 | // start the next integration cycle by pulsing SI and one clock |
mjr | 82:4f6209cb5c33 | 437 | si = 1; |
mjr | 82:4f6209cb5c33 | 438 | clock = 1; |
mjr | 82:4f6209cb5c33 | 439 | si = 0; |
mjr | 82:4f6209cb5c33 | 440 | clock = 0; |
mjr | 82:4f6209cb5c33 | 441 | |
mjr | 82:4f6209cb5c33 | 442 | // Set the timestamp for the current active buffer. The SI pulse |
mjr | 86:e30a1f60f783 | 443 | // we just did performed the HOLD operation, which takes a snapshot |
mjr | 86:e30a1f60f783 | 444 | // of the photo receptors and stores it in the sensor's shift |
mjr | 86:e30a1f60f783 | 445 | // register. We noted the start of the current integration cycle |
mjr | 86:e30a1f60f783 | 446 | // in tInt when we started it during the previous scan. The image |
mjr | 86:e30a1f60f783 | 447 | // we're about to transfer therefore represents the light collected |
mjr | 86:e30a1f60f783 | 448 | // between tInt and right now (actually, the SI pulse above, but |
mjr | 86:e30a1f60f783 | 449 | // close enough). The image covers a time range rather than a |
mjr | 86:e30a1f60f783 | 450 | // single point in time, but we still have to give it a single |
mjr | 86:e30a1f60f783 | 451 | // timestamp. Use the midpoint of the integration period. |
mjr | 82:4f6209cb5c33 | 452 | uint32_t tmid = (t0 + tInt) >> 1; |
mjr | 82:4f6209cb5c33 | 453 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 454 | t2 = tmid; |
mjr | 82:4f6209cb5c33 | 455 | else |
mjr | 82:4f6209cb5c33 | 456 | t1 = tmid; |
mjr | 82:4f6209cb5c33 | 457 | |
mjr | 82:4f6209cb5c33 | 458 | // Start the ADC sampler. The ADC will read samples continuously |
mjr | 82:4f6209cb5c33 | 459 | // until we tell it to stop. Each sample completion will trigger |
mjr | 82:4f6209cb5c33 | 460 | // our linked DMA channel, which will store the next sample in our |
mjr | 82:4f6209cb5c33 | 461 | // pixel array and pulse the CCD serial data clock to load the next |
mjr | 82:4f6209cb5c33 | 462 | // pixel onto the analog sampler pin. This will all happen without |
mjr | 82:4f6209cb5c33 | 463 | // any CPU involvement, so we can continue with other work. |
mjr | 82:4f6209cb5c33 | 464 | ao.start(); |
mjr | 82:4f6209cb5c33 | 465 | |
mjr | 82:4f6209cb5c33 | 466 | // The new integration cycle starts with the 19th clock pulse |
mjr | 82:4f6209cb5c33 | 467 | // after the SI pulse. We offload all of the transfer work (including |
mjr | 86:e30a1f60f783 | 468 | // the clock pulse generation) to the DMA controller, which doesn't |
mjr | 86:e30a1f60f783 | 469 | // notify when that 19th pulse occurs, so we have to approximate. |
mjr | 86:e30a1f60f783 | 470 | // Based on empirical measurements, each pixel transfer in our DMA |
mjr | 86:e30a1f60f783 | 471 | // setup takes about 2us, so clocking 19 pixels takes about 38us. |
mjr | 86:e30a1f60f783 | 472 | // In addition, the ADC takes about 4us extra for the first read. |
mjr | 86:e30a1f60f783 | 473 | tInt = t.read_us() + 19*2 + 4; |
mjr | 82:4f6209cb5c33 | 474 | |
mjr | 82:4f6209cb5c33 | 475 | IF_DIAG(mainLoopIterCheckpt[9] += uint32_t(mainLoopTimer.read_us() - tDiag0);) |
mjr | 82:4f6209cb5c33 | 476 | } |
mjr | 82:4f6209cb5c33 | 477 | |
mjr | 82:4f6209cb5c33 | 478 | // Wait for the current capture to finish |
mjr | 82:4f6209cb5c33 | 479 | void wait() |
mjr | 82:4f6209cb5c33 | 480 | { |
mjr | 82:4f6209cb5c33 | 481 | while (running) { } |
mjr | 82:4f6209cb5c33 | 482 | } |
mjr | 82:4f6209cb5c33 | 483 | |
mjr | 86:e30a1f60f783 | 484 | // Is the latest reading ready? |
mjr | 82:4f6209cb5c33 | 485 | bool ready() const { return !running; } |
mjr | 87:8d35c74403af | 486 | |
mjr | 87:8d35c74403af | 487 | // Is a DMA transfer in progress? |
mjr | 87:8d35c74403af | 488 | bool dmaBusy() const { return running; } |
mjr | 82:4f6209cb5c33 | 489 | |
mjr | 82:4f6209cb5c33 | 490 | // Clock through all pixels to clear the array. Pulses SI at the |
mjr | 82:4f6209cb5c33 | 491 | // beginning of the operation, which starts a new integration cycle. |
mjr | 82:4f6209cb5c33 | 492 | void clear() |
mjr | 82:4f6209cb5c33 | 493 | { |
mjr | 82:4f6209cb5c33 | 494 | // get the clock toggle register |
mjr | 82:4f6209cb5c33 | 495 | volatile uint32_t *ptor = &clockPort->PTOR; |
mjr | 82:4f6209cb5c33 | 496 | |
mjr | 82:4f6209cb5c33 | 497 | // make sure any DMA run is completed |
mjr | 82:4f6209cb5c33 | 498 | wait(); |
mjr | 82:4f6209cb5c33 | 499 | |
mjr | 82:4f6209cb5c33 | 500 | // clock in an SI pulse |
mjr | 82:4f6209cb5c33 | 501 | si = 1; |
mjr | 82:4f6209cb5c33 | 502 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 503 | clockPort->PSOR = clockMask; |
mjr | 82:4f6209cb5c33 | 504 | si = 0; |
mjr | 82:4f6209cb5c33 | 505 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 506 | |
mjr | 82:4f6209cb5c33 | 507 | // This starts a new integration period. Or more precisely, the |
mjr | 82:4f6209cb5c33 | 508 | // 19th clock pulse will start the new integration period. We're |
mjr | 82:4f6209cb5c33 | 509 | // going to blast the clock signal as fast as we can, at about |
mjr | 82:4f6209cb5c33 | 510 | // 100ns intervals (50ns up and 50ns down), so the 19th clock |
mjr | 82:4f6209cb5c33 | 511 | // will be about 2us from now. |
mjr | 82:4f6209cb5c33 | 512 | tInt = t.read_us() + 2; |
mjr | 82:4f6209cb5c33 | 513 | |
mjr | 82:4f6209cb5c33 | 514 | // clock out all pixels, plus an extra one to clock past the last |
mjr | 82:4f6209cb5c33 | 515 | // pixel and reset the last pixel's internal sampling switch in |
mjr | 82:4f6209cb5c33 | 516 | // the sensor |
mjr | 82:4f6209cb5c33 | 517 | for (int i = 0 ; i < nPixSensor + 1 ; ) |
mjr | 82:4f6209cb5c33 | 518 | { |
mjr | 82:4f6209cb5c33 | 519 | // toggle the clock to take it high |
mjr | 82:4f6209cb5c33 | 520 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 521 | |
mjr | 82:4f6209cb5c33 | 522 | // increment our loop variable here to pad the timing, to |
mjr | 82:4f6209cb5c33 | 523 | // keep our pulse width long enough for the sensor |
mjr | 82:4f6209cb5c33 | 524 | ++i; |
mjr | 82:4f6209cb5c33 | 525 | |
mjr | 82:4f6209cb5c33 | 526 | // toggle the clock to take it low |
mjr | 82:4f6209cb5c33 | 527 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 528 | } |
mjr | 82:4f6209cb5c33 | 529 | } |
mjr | 82:4f6209cb5c33 | 530 | |
mjr | 82:4f6209cb5c33 | 531 | // get the timing statistics - sum of scan time for all scans so far |
mjr | 82:4f6209cb5c33 | 532 | // in microseconds, and total number of scans so far |
mjr | 82:4f6209cb5c33 | 533 | void getTimingStats(uint64_t &totalTime, uint32_t &nRuns) const |
mjr | 82:4f6209cb5c33 | 534 | { |
mjr | 82:4f6209cb5c33 | 535 | totalTime = this->totalTime; |
mjr | 82:4f6209cb5c33 | 536 | nRuns = this->nRuns; |
mjr | 82:4f6209cb5c33 | 537 | } |
mjr | 82:4f6209cb5c33 | 538 | |
mjr | 82:4f6209cb5c33 | 539 | // get the average scan time in microseconds |
mjr | 82:4f6209cb5c33 | 540 | uint32_t getAvgScanTime() const |
mjr | 82:4f6209cb5c33 | 541 | { |
mjr | 82:4f6209cb5c33 | 542 | return uint32_t(totalTime / nRuns); |
mjr | 82:4f6209cb5c33 | 543 | } |
mjr | 82:4f6209cb5c33 | 544 | |
mjr | 82:4f6209cb5c33 | 545 | private: |
mjr | 82:4f6209cb5c33 | 546 | // end of transfer notification |
mjr | 82:4f6209cb5c33 | 547 | void transferDone() |
mjr | 82:4f6209cb5c33 | 548 | { |
mjr | 82:4f6209cb5c33 | 549 | // stop the ADC sampler |
mjr | 82:4f6209cb5c33 | 550 | ao.stop(); |
mjr | 82:4f6209cb5c33 | 551 | |
mjr | 82:4f6209cb5c33 | 552 | // clock out one extra pixel to leave A1 in the high-Z state |
mjr | 82:4f6209cb5c33 | 553 | clock = 1; |
mjr | 82:4f6209cb5c33 | 554 | clock = 0; |
mjr | 86:e30a1f60f783 | 555 | |
mjr | 86:e30a1f60f783 | 556 | // add this sample to the timing statistics (for diagnostics and |
mjr | 86:e30a1f60f783 | 557 | // performance measurement) |
mjr | 86:e30a1f60f783 | 558 | uint32_t now = t.read_us(); |
mjr | 86:e30a1f60f783 | 559 | totalTime += uint32_t(now - t0); |
mjr | 82:4f6209cb5c33 | 560 | nRuns += 1; |
mjr | 82:4f6209cb5c33 | 561 | |
mjr | 82:4f6209cb5c33 | 562 | // the sampler is no long running |
mjr | 82:4f6209cb5c33 | 563 | running = false; |
mjr | 86:e30a1f60f783 | 564 | |
mjr | 86:e30a1f60f783 | 565 | // note the ending time of the transfer |
mjr | 86:e30a1f60f783 | 566 | tDone = now; |
mjr | 82:4f6209cb5c33 | 567 | } |
mjr | 82:4f6209cb5c33 | 568 | |
mjr | 82:4f6209cb5c33 | 569 | // DMA controller interfaces |
mjr | 82:4f6209cb5c33 | 570 | SimpleDMA adc_dma; // DMA channel for reading the analog input |
mjr | 82:4f6209cb5c33 | 571 | SimpleDMA clkUp_dma; // "Clock Up" channel |
mjr | 82:4f6209cb5c33 | 572 | SimpleDMA clkDn_dma; // "Clock Down" channel |
mjr | 82:4f6209cb5c33 | 573 | |
mjr | 82:4f6209cb5c33 | 574 | // Sensor interface pins |
mjr | 82:4f6209cb5c33 | 575 | DigitalOut si; // GPIO pin for sensor SI (serial data) |
mjr | 82:4f6209cb5c33 | 576 | DigitalOut clock; // GPIO pin for sensor SCLK (serial clock) |
mjr | 82:4f6209cb5c33 | 577 | GPIO_Type *clockPort; // IOPORT base address for clock pin - cached for DMA writes |
mjr | 82:4f6209cb5c33 | 578 | uint32_t clockMask; // IOPORT register bit mask for clock pin |
mjr | 100:1ff35c07217c | 579 | AltAnalogIn_8bit ao; // GPIO pin for sensor AO (analog output) |
mjr | 82:4f6209cb5c33 | 580 | |
mjr | 82:4f6209cb5c33 | 581 | // number of pixels in the physical sensor array |
mjr | 82:4f6209cb5c33 | 582 | int nPixSensor; // number of pixels in physical sensor array |
mjr | 82:4f6209cb5c33 | 583 | |
mjr | 82:4f6209cb5c33 | 584 | // pixel buffers - we keep two buffers so that we can transfer the |
mjr | 82:4f6209cb5c33 | 585 | // current sensor data into one buffer via DMA while we concurrently |
mjr | 82:4f6209cb5c33 | 586 | // process the last buffer |
mjr | 82:4f6209cb5c33 | 587 | uint8_t *pix1; // pixel array 1 |
mjr | 82:4f6209cb5c33 | 588 | uint8_t *pix2; // pixel array 2 |
mjr | 82:4f6209cb5c33 | 589 | |
mjr | 82:4f6209cb5c33 | 590 | // Timestamps of pix1 and pix2 arrays, in microseconds, in terms of the |
mjr | 100:1ff35c07217c | 591 | // sample timer (this->t). |
mjr | 82:4f6209cb5c33 | 592 | uint32_t t1; |
mjr | 82:4f6209cb5c33 | 593 | uint32_t t2; |
mjr | 82:4f6209cb5c33 | 594 | |
mjr | 82:4f6209cb5c33 | 595 | // DMA target buffer. This is the buffer for the next DMA transfer. |
mjr | 82:4f6209cb5c33 | 596 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
mjr | 82:4f6209cb5c33 | 597 | // data from the last transfer. |
mjr | 82:4f6209cb5c33 | 598 | uint8_t pixDMA; |
mjr | 82:4f6209cb5c33 | 599 | |
mjr | 82:4f6209cb5c33 | 600 | // flag: sample is running |
mjr | 82:4f6209cb5c33 | 601 | volatile bool running; |
mjr | 82:4f6209cb5c33 | 602 | |
mjr | 82:4f6209cb5c33 | 603 | // timing statistics |
mjr | 82:4f6209cb5c33 | 604 | Timer t; // sample timer |
mjr | 82:4f6209cb5c33 | 605 | uint32_t t0; // start time (us) of current sample |
mjr | 82:4f6209cb5c33 | 606 | uint32_t tInt; // start time (us) of current integration period |
mjr | 86:e30a1f60f783 | 607 | uint32_t tDone; // end time of latest finished transfer |
mjr | 82:4f6209cb5c33 | 608 | uint64_t totalTime; // total time consumed by all reads so far |
mjr | 82:4f6209cb5c33 | 609 | uint32_t nRuns; // number of runs so far |
mjr | 82:4f6209cb5c33 | 610 | }; |
mjr | 82:4f6209cb5c33 | 611 | |
mjr | 82:4f6209cb5c33 | 612 | #endif /* TSL14XX_H */ |