Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
TSL14xx/TSL14xx.h@104:6e06e0f4b476, 2019-12-27 (annotated)
- Committer:
- mjr
- Date:
- Fri Dec 27 20:14:23 2019 +0000
- Revision:
- 104:6e06e0f4b476
- Parent:
- 101:755f44622abc
AEAT-6012, TCD1103 updates
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 | 104:6e06e0f4b476 | 22 | * data from the sensor. Plunger/tslxxSensor.h implements the |
mjr | 104:6e06e0f4b476 | 23 | * specializations of the plunger interface class for these sensors.) |
mjr | 104:6e06e0f4b476 | 24 | * |
mjr | 82:4f6209cb5c33 | 25 | * |
mjr | 101:755f44622abc | 26 | * *** Double buffering *** |
mjr | 101:755f44622abc | 27 | * |
mjr | 82:4f6209cb5c33 | 28 | * Our API is based on a double-buffered asynchronous read. The caller |
mjr | 82:4f6209cb5c33 | 29 | * can access a completed buffer, containing the pixels from the last image |
mjr | 82:4f6209cb5c33 | 30 | * frame, while the sensor is transferring data asynchronously (using the |
mjr | 82:4f6209cb5c33 | 31 | * microcontroller's DMA capability) into the other buffer. Each time a |
mjr | 82:4f6209cb5c33 | 32 | * new read is started, we swap buffers, making the last completed buffer |
mjr | 82:4f6209cb5c33 | 33 | * available to the client and handing the other buffer to the DMA |
mjr | 82:4f6209cb5c33 | 34 | * controller to fill asynchronously. |
mjr | 101:755f44622abc | 35 | * |
mjr | 101:755f44622abc | 36 | * In a way, there are actually THREE frames in our pipeline at any given |
mjr | 101:755f44622abc | 37 | * time: |
mjr | 101:755f44622abc | 38 | * |
mjr | 101:755f44622abc | 39 | * - a live image integrating light on the photo receptors on the sensor |
mjr | 101:755f44622abc | 40 | * - the prior image, held in the sensor's shift register and being |
mjr | 101:755f44622abc | 41 | * transferred via DMA into one of our buffers (the "DMA" buffer) |
mjr | 101:755f44622abc | 42 | * - the second prior image, in our other buffer (the "stable" buffer), |
mjr | 101:755f44622abc | 43 | * available for the client to process |
mjr | 101:755f44622abc | 44 | * |
mjr | 101:755f44622abc | 45 | * The integration process on the sensor starts when we begin the transfer |
mjr | 101:755f44622abc | 46 | * of an image via DMA. That frame's integration period ends when the next |
mjr | 101:755f44622abc | 47 | * transfer starts. So the minimum integration time is also the DMA pixel |
mjr | 101:755f44622abc | 48 | * transfer time. Longer integration times can be achieved by waiting |
mjr | 101:755f44622abc | 49 | * for an additional interval after a DMA transfer finishes, before starting |
mjr | 101:755f44622abc | 50 | * the next transfer. We make provision for this added time to allow for |
mjr | 101:755f44622abc | 51 | * longer exposure times to optimize image quality. |
mjr | 101:755f44622abc | 52 | * |
mjr | 82:4f6209cb5c33 | 53 | * |
mjr | 101:755f44622abc | 54 | * *** Optimizing pixel transfer speed *** |
mjr | 82:4f6209cb5c33 | 55 | * |
mjr | 101:755f44622abc | 56 | * For Pinscape purposes, we want the fastest possible frame rate, as we're |
mjr | 101:755f44622abc | 57 | * trying to accurately capture the motion of a fast-moving object (the |
mjr | 101:755f44622abc | 58 | * plunger). The TSL14xx sensors can achieve a frame rate up to about |
mjr | 101:755f44622abc | 59 | * 1000 frames per second, if everything is clocked at the limits in the |
mjr | 101:755f44622abc | 60 | * data sheet. The KL25Z, however, can't achieve that fast a rate. The |
mjr | 101:755f44622abc | 61 | * limiting factor is the KL25Z's ADC. We have to take an ADC sample for |
mjr | 101:755f44622abc | 62 | * every pixel, and the minimum sampling time for the ADC on the KL25Z is |
mjr | 101:755f44622abc | 63 | * about 2us. With the 1280-pixel TSL1410R, that gives us a minimum |
mjr | 101:755f44622abc | 64 | * pixel transfer time of about 2.6ms. And it's actually very difficult |
mjr | 101:755f44622abc | 65 | * to achieve that speed - my original, naive implementation took more |
mjr | 101:755f44622abc | 66 | * like 30ms (!!!) to transfer each frame. |
mjr | 82:4f6209cb5c33 | 67 | * |
mjr | 101:755f44622abc | 68 | * As a rule, I don't like tricky code, because it's hard to understand |
mjr | 101:755f44622abc | 69 | * and hard to debug. But in this case it's justified. For good plunger |
mjr | 101:755f44622abc | 70 | * tracking, it's critical to achieve a minimum frame rate of around 200 |
mjr | 101:755f44622abc | 71 | * fps (5ms per frame). I'm pretty sure there's no way to even get close |
mjr | 101:755f44622abc | 72 | * to this rate without the complex setup described below. |
mjr | 101:755f44622abc | 73 | * |
mjr | 101:755f44622abc | 74 | * Here's our approach for fast data transfer: |
mjr | 82:4f6209cb5c33 | 75 | * |
mjr | 82:4f6209cb5c33 | 76 | * First, we put the analog input port (the ADC == Analog-to-Digital |
mjr | 82:4f6209cb5c33 | 77 | * Converter) in "continuous" mode, at the highest clock speed we can |
mjr | 82:4f6209cb5c33 | 78 | * program with the available clocks and the fastest read cycle |
mjr | 82:4f6209cb5c33 | 79 | * available in the ADC hardware. (The analog input port is the |
mjr | 82:4f6209cb5c33 | 80 | * GPIO pin attached to the sensor's AO == Analog Output pin, where |
mjr | 82:4f6209cb5c33 | 81 | * it outputs each pixel's value, one at a time, as an analog voltage |
mjr | 82:4f6209cb5c33 | 82 | * level.) In continuous mode, every time the ADC finishes taking a |
mjr | 82:4f6209cb5c33 | 83 | * sample, it stores the result value in its output register and then |
mjr | 82:4f6209cb5c33 | 84 | * immediately starts taking a new sample. This means that no MCU |
mjr | 82:4f6209cb5c33 | 85 | * (or even DMA) action is required to start each new sample. This |
mjr | 82:4f6209cb5c33 | 86 | * is where most of the speedup comes from, since it takes significant |
mjr | 82:4f6209cb5c33 | 87 | * time (multiple microseconds) to move data through the peripheral |
mjr | 82:4f6209cb5c33 | 88 | * registers, and it takes more time (also multiple microseconds) for |
mjr | 82:4f6209cb5c33 | 89 | * the ADC to spin up for each new sample when in single-sample mode. |
mjr | 82:4f6209cb5c33 | 90 | * We cut out about 7us this way and get the time per sample down to |
mjr | 82:4f6209cb5c33 | 91 | * about 2us. This is close to the documented maximum speed for the |
mjr | 82:4f6209cb5c33 | 92 | * ADC hardware. |
mjr | 82:4f6209cb5c33 | 93 | * |
mjr | 82:4f6209cb5c33 | 94 | * Second, we use the DMA controller to read the ADC result register |
mjr | 82:4f6209cb5c33 | 95 | * and store each sample in a memory array for processing. The ADC |
mjr | 82:4f6209cb5c33 | 96 | * hardware is designed to work with the DMA controller by signaling |
mjr | 82:4f6209cb5c33 | 97 | * the DMA controller when a new sample is ready; this allows DMA to |
mjr | 82:4f6209cb5c33 | 98 | * move each sample immediately when it's available without any CPU |
mjr | 82:4f6209cb5c33 | 99 | * involvement. |
mjr | 82:4f6209cb5c33 | 100 | * |
mjr | 82:4f6209cb5c33 | 101 | * Third - and this is where it really gets tricky - we use two |
mjr | 82:4f6209cb5c33 | 102 | * additional "linked" DMA channels to generate the clock signal |
mjr | 82:4f6209cb5c33 | 103 | * to the CCD sensor. The clock signal is how we tell the CCD when |
mjr | 82:4f6209cb5c33 | 104 | * to place the next pixel voltage on its AO pin, so the clock has |
mjr | 82:4f6209cb5c33 | 105 | * to be generated in lock step with the ADC sampling cycle. The |
mjr | 82:4f6209cb5c33 | 106 | * ADC timing isn't perfectly uniform or predictable, so we can't |
mjr | 82:4f6209cb5c33 | 107 | * just generate the pixel clock with a *real* clock. We have to |
mjr | 82:4f6209cb5c33 | 108 | * time the signal exactly with the ADC, which means that we have |
mjr | 82:4f6209cb5c33 | 109 | * to generate it from the ADC "sample is ready" signal. Fortunately, |
mjr | 82:4f6209cb5c33 | 110 | * there is just such a signal, and in fact we're already using it, |
mjr | 82:4f6209cb5c33 | 111 | * as described above, to tell the DMA when to move each result from |
mjr | 82:4f6209cb5c33 | 112 | * the ADC output register to our memory array. So how do we use this |
mjr | 82:4f6209cb5c33 | 113 | * to generate the CCD clock? The answer lies in the DMA controller's |
mjr | 82:4f6209cb5c33 | 114 | * channel linking feature. This allows one DMA channel to trigger a |
mjr | 82:4f6209cb5c33 | 115 | * second DMA channel each time the first channel completes one |
mjr | 82:4f6209cb5c33 | 116 | * transfer. And we can use DMA to control our clock GPIO pin by |
mjr | 82:4f6209cb5c33 | 117 | * using the pin's GPIO IPORT register as the DMA destination address. |
mjr | 82:4f6209cb5c33 | 118 | * Specifically, we can take the clock high by writing our pin's bit |
mjr | 82:4f6209cb5c33 | 119 | * pattern to the PSOR ("set output") register, and we can take the |
mjr | 82:4f6209cb5c33 | 120 | * clock low by writing to the PCOR ("clear output") register. We |
mjr | 82:4f6209cb5c33 | 121 | * use one DMA channel for each of these operations. |
mjr | 82:4f6209cb5c33 | 122 | * |
mjr | 82:4f6209cb5c33 | 123 | * Putting it all together, the cascade of linked DMA channels |
mjr | 82:4f6209cb5c33 | 124 | * works like this: |
mjr | 82:4f6209cb5c33 | 125 | * |
mjr | 82:4f6209cb5c33 | 126 | * - We kick off the first ADC sample. |
mjr | 82:4f6209cb5c33 | 127 | * |
mjr | 82:4f6209cb5c33 | 128 | * - When the ADC sample completes, the ADC DMA trigger fires, |
mjr | 82:4f6209cb5c33 | 129 | * which triggers channel 1, the "Clock Up" channel. This |
mjr | 82:4f6209cb5c33 | 130 | * performs one transfer of the clock GPIO bit to the clock |
mjr | 82:4f6209cb5c33 | 131 | * PSOR register, taking the clock high, which causes the CCD |
mjr | 82:4f6209cb5c33 | 132 | * to move the next pixel onto AO. |
mjr | 82:4f6209cb5c33 | 133 | * |
mjr | 82:4f6209cb5c33 | 134 | * - After the Clock Up channel does its transfer, it triggers |
mjr | 82:4f6209cb5c33 | 135 | * its link to channel 2, the ADC transfer channel. This |
mjr | 82:4f6209cb5c33 | 136 | * channel moves the ADC output register value to our memory |
mjr | 82:4f6209cb5c33 | 137 | * array. |
mjr | 82:4f6209cb5c33 | 138 | * |
mjr | 82:4f6209cb5c33 | 139 | * - After the ADC channel does its transfer, it triggers channel |
mjr | 82:4f6209cb5c33 | 140 | * 3, the "Clock Down" channel. This performs one transfer of |
mjr | 82:4f6209cb5c33 | 141 | * the clock GPIO bit to the clock PCOR register, taking the |
mjr | 82:4f6209cb5c33 | 142 | * clock low. |
mjr | 82:4f6209cb5c33 | 143 | * |
mjr | 82:4f6209cb5c33 | 144 | * Note that the order of the channels - Clock Up, ADC, Clock Down - |
mjr | 82:4f6209cb5c33 | 145 | * is important. It ensures that we don't toggle the clock line |
mjr | 82:4f6209cb5c33 | 146 | * too quickly. The CCD has a minimum pulse duration of 50ns for |
mjr | 82:4f6209cb5c33 | 147 | * the clock signal. The DMA controller is so fast that it might |
mjr | 82:4f6209cb5c33 | 148 | * toggle the clock faster than this limit if we did the Up and |
mjr | 82:4f6209cb5c33 | 149 | * Down transfers back-to-back. |
mjr | 82:4f6209cb5c33 | 150 | * |
mjr | 82:4f6209cb5c33 | 151 | * Note also that it's important for Clock Up to be the very first |
mjr | 82:4f6209cb5c33 | 152 | * operation after the DMA trigger. The ADC is in continuous mode, |
mjr | 82:4f6209cb5c33 | 153 | * meaning that it starts taking a new sample immediately upon |
mjr | 82:4f6209cb5c33 | 154 | * finishing the previous one. So when the ADC DMA signal fires, |
mjr | 82:4f6209cb5c33 | 155 | * the new sample is already starting. We therefore have to get |
mjr | 82:4f6209cb5c33 | 156 | * the next pixel onto the sampling pin immediately, or as close |
mjr | 82:4f6209cb5c33 | 157 | * to immediately as possible. The sensor's "analog output |
mjr | 82:4f6209cb5c33 | 158 | * settling time" is 120ns - this is the time for a new pixel |
mjr | 82:4f6209cb5c33 | 159 | * voltage to stabilize on AO after a clock rising edge. So |
mjr | 82:4f6209cb5c33 | 160 | * assuming that the ADC raises the DMA signal immediately on |
mjr | 82:4f6209cb5c33 | 161 | * sample completion, and the DMA controller responds within a |
mjr | 82:4f6209cb5c33 | 162 | * couple of MCU clock cycles, we should have the new pixel voltage |
mjr | 82:4f6209cb5c33 | 163 | * stable on the sampling pin by about 200ns after the new ADC |
mjr | 82:4f6209cb5c33 | 164 | * sample cycle starts. The sampling cycle with our current |
mjr | 82:4f6209cb5c33 | 165 | * parameters is about 2us, so the voltage level is stable for |
mjr | 82:4f6209cb5c33 | 166 | * 90% of the cycle. |
mjr | 82:4f6209cb5c33 | 167 | * |
mjr | 82:4f6209cb5c33 | 168 | * Also, note that it's okay that the ADC sample transfer doesn't |
mjr | 82:4f6209cb5c33 | 169 | * happen until after the Clock Up DMA transfer. The ADC output |
mjr | 82:4f6209cb5c33 | 170 | * register holds the last result until the next sample completes, |
mjr | 82:4f6209cb5c33 | 171 | * so we have about 2us to grab it. The first Clock Up DMA |
mjr | 82:4f6209cb5c33 | 172 | * transfer only takes a couple of clocks - order of 100ns - so |
mjr | 82:4f6209cb5c33 | 173 | * we get to it with time to spare. |
mjr | 82:4f6209cb5c33 | 174 | * |
mjr | 82:4f6209cb5c33 | 175 | * (Note that it would nicer to handle the clock with a single DMA |
mjr | 82:4f6209cb5c33 | 176 | * channel, since DMA channels are a limited resource. We could |
mjr | 82:4f6209cb5c33 | 177 | * conceivably consolidate the clock generator one DMA channel by |
mjr | 82:4f6209cb5c33 | 178 | * switching the DMA destination to the PTOR "toggle" register, and |
mjr | 82:4f6209cb5c33 | 179 | * writing *two* times per trigger - once to toggle the clock up, |
mjr | 82:4f6209cb5c33 | 180 | * and a second time to toggle it down. But I haven't found a way |
mjr | 82:4f6209cb5c33 | 181 | * to make this work. The obstacle is that the DMA controller can |
mjr | 82:4f6209cb5c33 | 182 | * only do one transfer per trigger in the fully autonomous mode |
mjr | 82:4f6209cb5c33 | 183 | * we're using, and to make this toggle scheme work, we'd have to do |
mjr | 82:4f6209cb5c33 | 184 | * two writes per trigger. Maybe even three or four: I think we'd |
mjr | 82:4f6209cb5c33 | 185 | * have to throw in one or two no-op writes (of all zeroes) between |
mjr | 82:4f6209cb5c33 | 186 | * the two toggles, to pad the timing to ensure that the clock pulse |
mjr | 82:4f6209cb5c33 | 187 | * width is over the sensor's 50ns minimum. But it's the same issue |
mjr | 82:4f6209cb5c33 | 188 | * whether it's two writes or four. The DMA controller does have a |
mjr | 82:4f6209cb5c33 | 189 | * "continuous" mode that does an entire transfer on a single trigger, |
mjr | 82:4f6209cb5c33 | 190 | * but it can't reset itself after such a transfer; CPU intervention |
mjr | 82:4f6209cb5c33 | 191 | * is required to do that, which means we'd have to service an |
mjr | 82:4f6209cb5c33 | 192 | * interrupt on every ADC cycle to set up the next clock write. |
mjr | 82:4f6209cb5c33 | 193 | * Given the 2us cycle time, an interrupt would create a ton of CPU |
mjr | 82:4f6209cb5c33 | 194 | * load, and I don't think the CPU is fast enough to reliably complete |
mjr | 82:4f6209cb5c33 | 195 | * the work we'd have to do on each 2us cycle. Fortunately, at |
mjr | 82:4f6209cb5c33 | 196 | * the moment we can afford to dedicate three channels to this |
mjr | 82:4f6209cb5c33 | 197 | * module. We only have one other module using the DMA at all |
mjr | 82:4f6209cb5c33 | 198 | * (the TLC5940 PWM controller interface), and it only needs one |
mjr | 82:4f6209cb5c33 | 199 | * channel. So the KL25Z's complement of four DMA channels is just |
mjr | 82:4f6209cb5c33 | 200 | * enough for all of our needs for the moment.) |
mjr | 101:755f44622abc | 201 | * |
mjr | 101:755f44622abc | 202 | * Note that some of the sensors in this series (TSL1410R, TSL1412S) |
mjr | 101:755f44622abc | 203 | * have a "parallel" readout mode that lets them physically deliver |
mjr | 101:755f44622abc | 204 | * two pixels at once the MCU, via separate physical connections. This |
mjr | 101:755f44622abc | 205 | * could provide a 2X speedup on an MCU equipped with two independent |
mjr | 101:755f44622abc | 206 | * ADC samplers. Unfortunately, the KL25Z is not so equipped; even |
mjr | 101:755f44622abc | 207 | * though it might appear at first glance to support multiple ADC |
mjr | 101:755f44622abc | 208 | * "channels", all of the channels internally multiplex into a single |
mjr | 101:755f44622abc | 209 | * converter unit, so the hardware can ultimately perform only one |
mjr | 101:755f44622abc | 210 | * conversion at a time. Paradoxically, using the sensor's parallel |
mjr | 101:755f44622abc | 211 | * mode is actually *slower* with a KL25Z than using its serial mode, |
mjr | 101:755f44622abc | 212 | * because we can only maintain the higher throughput of the KL25Z ADC's |
mjr | 101:755f44622abc | 213 | * continuous sampling mode by reading all samples thorugh a single |
mjr | 101:755f44622abc | 214 | * channel. Switching channels on alternating samples involves a |
mjr | 101:755f44622abc | 215 | * bunch of setup overhead within the ADC hardware that adds lots of |
mjr | 101:755f44622abc | 216 | * clocks compared to single-channel continuous mode. |
mjr | 82:4f6209cb5c33 | 217 | */ |
mjr | 82:4f6209cb5c33 | 218 | |
mjr | 82:4f6209cb5c33 | 219 | #include "mbed.h" |
mjr | 82:4f6209cb5c33 | 220 | #include "config.h" |
mjr | 82:4f6209cb5c33 | 221 | #include "AltAnalogIn.h" |
mjr | 82:4f6209cb5c33 | 222 | #include "SimpleDMA.h" |
mjr | 82:4f6209cb5c33 | 223 | #include "DMAChannels.h" |
mjr | 82:4f6209cb5c33 | 224 | |
mjr | 82:4f6209cb5c33 | 225 | #ifndef TSL14XX_H |
mjr | 82:4f6209cb5c33 | 226 | #define TSL14XX_H |
mjr | 82:4f6209cb5c33 | 227 | |
mjr | 82:4f6209cb5c33 | 228 | |
mjr | 82:4f6209cb5c33 | 229 | // To allow DMA access to the clock pin, we need to point the DMA |
mjr | 82:4f6209cb5c33 | 230 | // controller to the IOPORT registers that control the pin. PORT_BASE() |
mjr | 82:4f6209cb5c33 | 231 | // gives us the address of the register group for the 32 GPIO pins with |
mjr | 82:4f6209cb5c33 | 232 | // the same letter name as our target pin (e.g., PTA0 through PTA31), |
mjr | 82:4f6209cb5c33 | 233 | // and PINMASK gives us the bit pattern to write to those registers to |
mjr | 82:4f6209cb5c33 | 234 | // access our single GPIO pin. Each register group has three special |
mjr | 82:4f6209cb5c33 | 235 | // registers that update the pin in particular ways: PSOR ("set output |
mjr | 82:4f6209cb5c33 | 236 | // register") turns pins on, PCOR ("clear output register") turns pins |
mjr | 82:4f6209cb5c33 | 237 | // off, and PTOR ("toggle output register") toggle pins to the opposite |
mjr | 82:4f6209cb5c33 | 238 | // of their current values. These registers have special semantics: |
mjr | 82:4f6209cb5c33 | 239 | // writing a bit as 0 has no effect on the corresponding pin, while |
mjr | 82:4f6209cb5c33 | 240 | // writing a bit as 1 performs the register's action on the pin. This |
mjr | 82:4f6209cb5c33 | 241 | // allows a single GPIO pin to be set, cleared, or toggled with a |
mjr | 82:4f6209cb5c33 | 242 | // 32-bit write to one of these registers, without affecting any of the |
mjr | 82:4f6209cb5c33 | 243 | // other pins addressed by the register. (It also allows changing any |
mjr | 82:4f6209cb5c33 | 244 | // group of pins with a single write, although we don't use that |
mjr | 82:4f6209cb5c33 | 245 | // feature here.) |
mjr | 82:4f6209cb5c33 | 246 | // |
mjr | 82:4f6209cb5c33 | 247 | // - To turn a pin ON: PORT_BASE(pin)->PSOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 248 | // - To turn a pin OFF: PORT_BASE(pin)->PCOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 249 | // - To toggle a pin: PORT_BASE(pin)->PTOR = PINMASK(pin) |
mjr | 82:4f6209cb5c33 | 250 | // |
mjr | 82:4f6209cb5c33 | 251 | #define GPIO_PORT(pin) (((unsigned int)(pin)) >> PORT_SHIFT) |
mjr | 82:4f6209cb5c33 | 252 | #define GPIO_PORT_BASE(pin) ((GPIO_Type *)(PTA_BASE + GPIO_PORT(pin) * 0x40)) |
mjr | 82:4f6209cb5c33 | 253 | #define GPIO_PINMASK(pin) gpio_set(pin) |
mjr | 82:4f6209cb5c33 | 254 | |
mjr | 82:4f6209cb5c33 | 255 | IF_DIAG( |
mjr | 82:4f6209cb5c33 | 256 | extern uint64_t mainLoopIterCheckpt[]; |
mjr | 82:4f6209cb5c33 | 257 | extern Timer mainLoopTimer;) |
mjr | 82:4f6209cb5c33 | 258 | |
mjr | 82:4f6209cb5c33 | 259 | class TSL14xx |
mjr | 82:4f6209cb5c33 | 260 | { |
mjr | 82:4f6209cb5c33 | 261 | public: |
mjr | 82:4f6209cb5c33 | 262 | // Set up the interface. |
mjr | 82:4f6209cb5c33 | 263 | // |
mjr | 82:4f6209cb5c33 | 264 | // nPixSensor = native number of pixels on sensor |
mjr | 82:4f6209cb5c33 | 265 | // siPin = SI pin (GPIO, digital out) |
mjr | 82:4f6209cb5c33 | 266 | // clockPin = CLK pin (GPIO, digital out) |
mjr | 82:4f6209cb5c33 | 267 | // aoPin = AO pin (GPIO, analog in - must be ADC-capable) |
mjr | 82:4f6209cb5c33 | 268 | TSL14xx(int nPixSensor, PinName siPin, PinName clockPin, PinName aoPin) |
mjr | 100:1ff35c07217c | 269 | : adc_dma(DMAch_TSL_ADC), |
mjr | 100:1ff35c07217c | 270 | clkUp_dma(DMAch_TSL_CLKUP), |
mjr | 100:1ff35c07217c | 271 | clkDn_dma(DMAch_TSL_CLKDN), |
mjr | 82:4f6209cb5c33 | 272 | si(siPin), |
mjr | 82:4f6209cb5c33 | 273 | clock(clockPin), |
mjr | 100:1ff35c07217c | 274 | ao(aoPin, true, 0), // continuous sampling, fast sampling mode |
mjr | 82:4f6209cb5c33 | 275 | nPixSensor(nPixSensor) |
mjr | 82:4f6209cb5c33 | 276 | { |
mjr | 100:1ff35c07217c | 277 | // Calibrate the ADC for best accuracy |
mjr | 100:1ff35c07217c | 278 | ao.calibrate(); |
mjr | 100:1ff35c07217c | 279 | |
mjr | 82:4f6209cb5c33 | 280 | // start the sample timer with an arbitrary zero point of 'now' |
mjr | 82:4f6209cb5c33 | 281 | t.start(); |
mjr | 82:4f6209cb5c33 | 282 | |
mjr | 101:755f44622abc | 283 | // start with no minimum integration time |
mjr | 101:755f44622abc | 284 | tIntMin = 0; |
mjr | 101:755f44622abc | 285 | |
mjr | 82:4f6209cb5c33 | 286 | // allocate our double pixel buffers |
mjr | 82:4f6209cb5c33 | 287 | pix1 = new uint8_t[nPixSensor*2]; |
mjr | 82:4f6209cb5c33 | 288 | pix2 = pix1 + nPixSensor; |
mjr | 82:4f6209cb5c33 | 289 | |
mjr | 82:4f6209cb5c33 | 290 | // put the first DMA transfer into the first buffer (pix1) |
mjr | 82:4f6209cb5c33 | 291 | pixDMA = 0; |
mjr | 101:755f44622abc | 292 | |
mjr | 101:755f44622abc | 293 | // DMA owns both buffers until the first transfer completes |
mjr | 101:755f44622abc | 294 | clientOwnsStablePix = true; |
mjr | 82:4f6209cb5c33 | 295 | |
mjr | 82:4f6209cb5c33 | 296 | // remember the clock pin port base and pin mask for fast access |
mjr | 82:4f6209cb5c33 | 297 | clockPort = GPIO_PORT_BASE(clockPin); |
mjr | 82:4f6209cb5c33 | 298 | clockMask = GPIO_PINMASK(clockPin); |
mjr | 82:4f6209cb5c33 | 299 | |
mjr | 82:4f6209cb5c33 | 300 | // clear out power-on random data by clocking through all pixels twice |
mjr | 82:4f6209cb5c33 | 301 | clear(); |
mjr | 82:4f6209cb5c33 | 302 | clear(); |
mjr | 82:4f6209cb5c33 | 303 | |
mjr | 82:4f6209cb5c33 | 304 | // Set up the Clock Up DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 305 | // clock high by writing the clock bit to the PSOR (set output) |
mjr | 82:4f6209cb5c33 | 306 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 307 | clkUp_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 308 | clkUp_dma.destination(&clockPort->PSOR, false, 32); |
mjr | 82:4f6209cb5c33 | 309 | |
mjr | 82:4f6209cb5c33 | 310 | // Set up the Clock Down DMA channel. This channel takes the |
mjr | 82:4f6209cb5c33 | 311 | // clock low by writing the clock bit to the PCOR (clear output) |
mjr | 82:4f6209cb5c33 | 312 | // register for the clock pin. |
mjr | 82:4f6209cb5c33 | 313 | clkDn_dma.source(&clockMask, false, 32); |
mjr | 82:4f6209cb5c33 | 314 | clkDn_dma.destination(&clockPort->PCOR, false, 32); |
mjr | 82:4f6209cb5c33 | 315 | |
mjr | 82:4f6209cb5c33 | 316 | // Set up the ADC transfer DMA channel. This channel transfers |
mjr | 82:4f6209cb5c33 | 317 | // the current analog sampling result from the ADC output register |
mjr | 82:4f6209cb5c33 | 318 | // to our pixel array. |
mjr | 82:4f6209cb5c33 | 319 | ao.initDMA(&adc_dma); |
mjr | 82:4f6209cb5c33 | 320 | |
mjr | 82:4f6209cb5c33 | 321 | // Set up our chain of linked DMA channel: |
mjr | 82:4f6209cb5c33 | 322 | // |
mjr | 82:4f6209cb5c33 | 323 | // ADC sample completion triggers Clock Up |
mjr | 82:4f6209cb5c33 | 324 | // ...which triggers the ADC transfer |
mjr | 82:4f6209cb5c33 | 325 | // ...which triggers Clock Down |
mjr | 82:4f6209cb5c33 | 326 | // |
mjr | 82:4f6209cb5c33 | 327 | // We operate the ADC in "continuous mode", meaning that it starts |
mjr | 82:4f6209cb5c33 | 328 | // a new sample immediately after the last one completes. This is |
mjr | 82:4f6209cb5c33 | 329 | // what keeps the cycle going after the Clock Down, since the Clock |
mjr | 82:4f6209cb5c33 | 330 | // Down transfer itself doesn't trigger another DMA operation. |
mjr | 82:4f6209cb5c33 | 331 | clkUp_dma.trigger(Trigger_ADC0); |
mjr | 82:4f6209cb5c33 | 332 | clkUp_dma.link(adc_dma); |
mjr | 82:4f6209cb5c33 | 333 | adc_dma.link(clkDn_dma, false); |
mjr | 82:4f6209cb5c33 | 334 | |
mjr | 82:4f6209cb5c33 | 335 | // Set the trigger on the downstream links to NONE - these are |
mjr | 82:4f6209cb5c33 | 336 | // triggered by their upstream links, so they don't need separate |
mjr | 82:4f6209cb5c33 | 337 | // peripheral or software triggers. |
mjr | 82:4f6209cb5c33 | 338 | adc_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 339 | clkDn_dma.trigger(Trigger_NONE); |
mjr | 82:4f6209cb5c33 | 340 | |
mjr | 82:4f6209cb5c33 | 341 | // Register an interrupt callback so that we're notified when |
mjr | 82:4f6209cb5c33 | 342 | // the last transfer completes. |
mjr | 82:4f6209cb5c33 | 343 | clkDn_dma.attach(this, &TSL14xx::transferDone); |
mjr | 82:4f6209cb5c33 | 344 | |
mjr | 82:4f6209cb5c33 | 345 | // clear the timing statistics |
mjr | 82:4f6209cb5c33 | 346 | totalTime = 0.0; |
mjr | 82:4f6209cb5c33 | 347 | nRuns = 0; |
mjr | 101:755f44622abc | 348 | |
mjr | 101:755f44622abc | 349 | // start the first transfer |
mjr | 101:755f44622abc | 350 | startTransfer(); |
mjr | 82:4f6209cb5c33 | 351 | } |
mjr | 82:4f6209cb5c33 | 352 | |
mjr | 82:4f6209cb5c33 | 353 | // Get the stable pixel array. This is the image array from the |
mjr | 82:4f6209cb5c33 | 354 | // previous capture. It remains valid until the next startCapture() |
mjr | 82:4f6209cb5c33 | 355 | // call, at which point this buffer will be reused for the new capture. |
mjr | 82:4f6209cb5c33 | 356 | void getPix(uint8_t * &pix, uint32_t &t) |
mjr | 82:4f6209cb5c33 | 357 | { |
mjr | 82:4f6209cb5c33 | 358 | // return the pixel array that ISN'T assigned to the DMA |
mjr | 82:4f6209cb5c33 | 359 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 360 | { |
mjr | 82:4f6209cb5c33 | 361 | // DMA owns pix2, so the stable array is pix1 |
mjr | 82:4f6209cb5c33 | 362 | pix = pix1; |
mjr | 82:4f6209cb5c33 | 363 | t = t1; |
mjr | 82:4f6209cb5c33 | 364 | } |
mjr | 82:4f6209cb5c33 | 365 | else |
mjr | 82:4f6209cb5c33 | 366 | { |
mjr | 82:4f6209cb5c33 | 367 | // DMA owns pix1, so the stable array is pix2 |
mjr | 82:4f6209cb5c33 | 368 | pix = pix2; |
mjr | 82:4f6209cb5c33 | 369 | t = t2; |
mjr | 82:4f6209cb5c33 | 370 | } |
mjr | 82:4f6209cb5c33 | 371 | } |
mjr | 82:4f6209cb5c33 | 372 | |
mjr | 86:e30a1f60f783 | 373 | // Wait for the current DMA transfer to finish, and retrieve its |
mjr | 86:e30a1f60f783 | 374 | // pixel array buffer. This provides access to the latest image |
mjr | 86:e30a1f60f783 | 375 | // without starting a new transfer. These pixels are valid throughout |
mjr | 86:e30a1f60f783 | 376 | // the next transfer (started via startCapture()) and remain valid |
mjr | 86:e30a1f60f783 | 377 | // until the next transfer after that. |
mjr | 86:e30a1f60f783 | 378 | void waitPix(uint8_t * &pix, uint32_t &t) |
mjr | 86:e30a1f60f783 | 379 | { |
mjr | 101:755f44622abc | 380 | // wait for stable buffer ownership to transfer to the client |
mjr | 86:e30a1f60f783 | 381 | wait(); |
mjr | 86:e30a1f60f783 | 382 | |
mjr | 86:e30a1f60f783 | 383 | // Return the pixel array that IS assigned to DMA, since this |
mjr | 86:e30a1f60f783 | 384 | // is the latest buffer filled. This buffer is stable, even |
mjr | 86:e30a1f60f783 | 385 | // though it's assigned to DMA, because the last transfer is |
mjr | 86:e30a1f60f783 | 386 | // already finished and thus DMA is no longer accessing the |
mjr | 86:e30a1f60f783 | 387 | // buffer. |
mjr | 86:e30a1f60f783 | 388 | if (pixDMA) |
mjr | 86:e30a1f60f783 | 389 | { |
mjr | 86:e30a1f60f783 | 390 | // DMA owns pix2 |
mjr | 86:e30a1f60f783 | 391 | pix = pix2; |
mjr | 86:e30a1f60f783 | 392 | t = t2; |
mjr | 86:e30a1f60f783 | 393 | } |
mjr | 86:e30a1f60f783 | 394 | else |
mjr | 86:e30a1f60f783 | 395 | { |
mjr | 86:e30a1f60f783 | 396 | // DMA owns pix1 |
mjr | 86:e30a1f60f783 | 397 | pix = pix1; |
mjr | 86:e30a1f60f783 | 398 | t = t1; |
mjr | 86:e30a1f60f783 | 399 | } |
mjr | 86:e30a1f60f783 | 400 | } |
mjr | 86:e30a1f60f783 | 401 | |
mjr | 101:755f44622abc | 402 | // Set the requested minimum integration time. If this is less than the |
mjr | 101:755f44622abc | 403 | // sensor's physical minimum time, the physical minimum applies. |
mjr | 101:755f44622abc | 404 | virtual void setMinIntTime(uint32_t us) |
mjr | 82:4f6209cb5c33 | 405 | { |
mjr | 101:755f44622abc | 406 | tIntMin = us; |
mjr | 101:755f44622abc | 407 | } |
mjr | 101:755f44622abc | 408 | |
mjr | 101:755f44622abc | 409 | // Wait for the stable buffer ownership to transfer to the client |
mjr | 101:755f44622abc | 410 | void wait() { while (!clientOwnsStablePix) ; } |
mjr | 101:755f44622abc | 411 | |
mjr | 101:755f44622abc | 412 | // Is a buffer available? |
mjr | 101:755f44622abc | 413 | bool ready() const { return clientOwnsStablePix; } |
mjr | 101:755f44622abc | 414 | |
mjr | 101:755f44622abc | 415 | // Release the client DMA buffer. The client must call this when it's |
mjr | 101:755f44622abc | 416 | // done with the current image frame to release the frame back to the |
mjr | 101:755f44622abc | 417 | // DMA subsystem, so that it can hand us the next frame. |
mjr | 101:755f44622abc | 418 | void releasePix() { clientOwnsStablePix = false; } |
mjr | 82:4f6209cb5c33 | 419 | |
mjr | 101:755f44622abc | 420 | // get the timing statistics - sum of scan time for all scans so far |
mjr | 101:755f44622abc | 421 | // in microseconds, and total number of scans so far |
mjr | 101:755f44622abc | 422 | void getTimingStats(uint64_t &totalTime, uint32_t &nRuns) const |
mjr | 101:755f44622abc | 423 | { |
mjr | 101:755f44622abc | 424 | totalTime = this->totalTime; |
mjr | 101:755f44622abc | 425 | nRuns = this->nRuns; |
mjr | 101:755f44622abc | 426 | } |
mjr | 101:755f44622abc | 427 | |
mjr | 101:755f44622abc | 428 | // get the average scan time in microseconds |
mjr | 101:755f44622abc | 429 | uint32_t getAvgScanTime() const |
mjr | 101:755f44622abc | 430 | { |
mjr | 101:755f44622abc | 431 | return uint32_t(totalTime / nRuns); |
mjr | 101:755f44622abc | 432 | } |
mjr | 101:755f44622abc | 433 | |
mjr | 101:755f44622abc | 434 | private: |
mjr | 101:755f44622abc | 435 | // Start a new transfer. We call this at the end of each integration |
mjr | 101:755f44622abc | 436 | // cycle, in interrupt mode. This can be called directly by the interrupt |
mjr | 101:755f44622abc | 437 | // handler invoked when the DMA transfer completes, or by a timeout. In |
mjr | 101:755f44622abc | 438 | // either case, we're in interrupt mode. |
mjr | 101:755f44622abc | 439 | void startTransfer() |
mjr | 101:755f44622abc | 440 | { |
mjr | 101:755f44622abc | 441 | // If we own the stable buffer, swap buffers: hand ownership of the |
mjr | 101:755f44622abc | 442 | // old DMA buffer to the client, and take control of the old client |
mjr | 101:755f44622abc | 443 | // buffer (which the client must be done with if we own it) as our |
mjr | 101:755f44622abc | 444 | // new DMA buffer. |
mjr | 101:755f44622abc | 445 | // |
mjr | 101:755f44622abc | 446 | // If the client owns the stable buffer, we can't swap buffers, |
mjr | 101:755f44622abc | 447 | // because the client is still working on the stable one. So we |
mjr | 101:755f44622abc | 448 | // must start the new transfer using the existing DMA buffer. |
mjr | 101:755f44622abc | 449 | if (!clientOwnsStablePix) |
mjr | 82:4f6209cb5c33 | 450 | { |
mjr | 101:755f44622abc | 451 | // swap buffers |
mjr | 101:755f44622abc | 452 | pixDMA ^= 1; |
mjr | 101:755f44622abc | 453 | |
mjr | 101:755f44622abc | 454 | // release the prior DMA buffer to the client |
mjr | 101:755f44622abc | 455 | clientOwnsStablePix = true; |
mjr | 82:4f6209cb5c33 | 456 | } |
mjr | 82:4f6209cb5c33 | 457 | |
mjr | 82:4f6209cb5c33 | 458 | // Set up the active pixel array as the destination buffer for |
mjr | 82:4f6209cb5c33 | 459 | // the ADC DMA channel. |
mjr | 82:4f6209cb5c33 | 460 | adc_dma.destination(pixDMA ? pix2 : pix1, true); |
mjr | 82:4f6209cb5c33 | 461 | |
mjr | 82:4f6209cb5c33 | 462 | // start the DMA transfers |
mjr | 82:4f6209cb5c33 | 463 | clkDn_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 464 | adc_dma.start(nPixSensor, true); |
mjr | 82:4f6209cb5c33 | 465 | clkUp_dma.start(nPixSensor*4, true); |
mjr | 82:4f6209cb5c33 | 466 | |
mjr | 82:4f6209cb5c33 | 467 | // note the start time of this transfer |
mjr | 82:4f6209cb5c33 | 468 | t0 = t.read_us(); |
mjr | 82:4f6209cb5c33 | 469 | |
mjr | 82:4f6209cb5c33 | 470 | // start the next integration cycle by pulsing SI and one clock |
mjr | 82:4f6209cb5c33 | 471 | si = 1; |
mjr | 82:4f6209cb5c33 | 472 | clock = 1; |
mjr | 82:4f6209cb5c33 | 473 | si = 0; |
mjr | 82:4f6209cb5c33 | 474 | clock = 0; |
mjr | 82:4f6209cb5c33 | 475 | |
mjr | 82:4f6209cb5c33 | 476 | // Set the timestamp for the current active buffer. The SI pulse |
mjr | 86:e30a1f60f783 | 477 | // we just did performed the HOLD operation, which takes a snapshot |
mjr | 86:e30a1f60f783 | 478 | // of the photo receptors and stores it in the sensor's shift |
mjr | 86:e30a1f60f783 | 479 | // register. We noted the start of the current integration cycle |
mjr | 86:e30a1f60f783 | 480 | // in tInt when we started it during the previous scan. The image |
mjr | 86:e30a1f60f783 | 481 | // we're about to transfer therefore represents the light collected |
mjr | 86:e30a1f60f783 | 482 | // between tInt and right now (actually, the SI pulse above, but |
mjr | 86:e30a1f60f783 | 483 | // close enough). The image covers a time range rather than a |
mjr | 86:e30a1f60f783 | 484 | // single point in time, but we still have to give it a single |
mjr | 86:e30a1f60f783 | 485 | // timestamp. Use the midpoint of the integration period. |
mjr | 82:4f6209cb5c33 | 486 | uint32_t tmid = (t0 + tInt) >> 1; |
mjr | 82:4f6209cb5c33 | 487 | if (pixDMA) |
mjr | 82:4f6209cb5c33 | 488 | t2 = tmid; |
mjr | 82:4f6209cb5c33 | 489 | else |
mjr | 82:4f6209cb5c33 | 490 | t1 = tmid; |
mjr | 82:4f6209cb5c33 | 491 | |
mjr | 82:4f6209cb5c33 | 492 | // Start the ADC sampler. The ADC will read samples continuously |
mjr | 82:4f6209cb5c33 | 493 | // until we tell it to stop. Each sample completion will trigger |
mjr | 82:4f6209cb5c33 | 494 | // our linked DMA channel, which will store the next sample in our |
mjr | 82:4f6209cb5c33 | 495 | // pixel array and pulse the CCD serial data clock to load the next |
mjr | 82:4f6209cb5c33 | 496 | // pixel onto the analog sampler pin. This will all happen without |
mjr | 82:4f6209cb5c33 | 497 | // any CPU involvement, so we can continue with other work. |
mjr | 82:4f6209cb5c33 | 498 | ao.start(); |
mjr | 82:4f6209cb5c33 | 499 | |
mjr | 82:4f6209cb5c33 | 500 | // The new integration cycle starts with the 19th clock pulse |
mjr | 82:4f6209cb5c33 | 501 | // after the SI pulse. We offload all of the transfer work (including |
mjr | 86:e30a1f60f783 | 502 | // the clock pulse generation) to the DMA controller, which doesn't |
mjr | 86:e30a1f60f783 | 503 | // notify when that 19th pulse occurs, so we have to approximate. |
mjr | 86:e30a1f60f783 | 504 | // Based on empirical measurements, each pixel transfer in our DMA |
mjr | 86:e30a1f60f783 | 505 | // setup takes about 2us, so clocking 19 pixels takes about 38us. |
mjr | 86:e30a1f60f783 | 506 | // In addition, the ADC takes about 4us extra for the first read. |
mjr | 86:e30a1f60f783 | 507 | tInt = t.read_us() + 19*2 + 4; |
mjr | 82:4f6209cb5c33 | 508 | } |
mjr | 82:4f6209cb5c33 | 509 | |
mjr | 101:755f44622abc | 510 | // End of transfer notification. This is called as an interrupt |
mjr | 101:755f44622abc | 511 | // handler when the DMA transfer completes. |
mjr | 101:755f44622abc | 512 | void transferDone() |
mjr | 82:4f6209cb5c33 | 513 | { |
mjr | 101:755f44622abc | 514 | // stop the ADC sampler |
mjr | 101:755f44622abc | 515 | ao.stop(); |
mjr | 101:755f44622abc | 516 | |
mjr | 101:755f44622abc | 517 | // clock out one extra pixel to leave the analog out pin on |
mjr | 101:755f44622abc | 518 | // the sensor in the high-Z state |
mjr | 101:755f44622abc | 519 | clock = 1; |
mjr | 101:755f44622abc | 520 | clock = 0; |
mjr | 101:755f44622abc | 521 | |
mjr | 101:755f44622abc | 522 | // add this sample to the timing statistics (for diagnostics and |
mjr | 101:755f44622abc | 523 | // performance measurement) |
mjr | 101:755f44622abc | 524 | uint32_t now = t.read_us(); |
mjr | 101:755f44622abc | 525 | totalTime += uint32_t(now - t0); |
mjr | 101:755f44622abc | 526 | nRuns += 1; |
mjr | 101:755f44622abc | 527 | |
mjr | 101:755f44622abc | 528 | // note the ending time of the transfer |
mjr | 101:755f44622abc | 529 | tDone = now; |
mjr | 101:755f44622abc | 530 | |
mjr | 101:755f44622abc | 531 | // Figure the time remaining to reach the minimum requested |
mjr | 101:755f44622abc | 532 | // integration time for the next cycle. The sensor is currently |
mjr | 101:755f44622abc | 533 | // working on an integration cycle that started at tInt, and that |
mjr | 101:755f44622abc | 534 | // cycle will end when we start the next cycle. We therefore want |
mjr | 101:755f44622abc | 535 | // to wait to start the next cycle until we've reached the desired |
mjr | 101:755f44622abc | 536 | // total integration time. |
mjr | 101:755f44622abc | 537 | uint32_t dt = now - tInt; |
mjr | 104:6e06e0f4b476 | 538 | |
mjr | 104:6e06e0f4b476 | 539 | // Figure the time to the start of the next transfer. Wait for the |
mjr | 104:6e06e0f4b476 | 540 | // remainder of the current integration period if we haven't yet |
mjr | 104:6e06e0f4b476 | 541 | // reached the requested minimum, otherwise just start almost |
mjr | 104:6e06e0f4b476 | 542 | // immediately. (Not *actually* immediately: we don't want to start |
mjr | 104:6e06e0f4b476 | 543 | // the new transfer within this interrupt handler, because the DMA |
mjr | 104:6e06e0f4b476 | 544 | // IRQ doesn't reliably clear if we start a new transfer immediately.) |
mjr | 104:6e06e0f4b476 | 545 | uint32_t dtNext = dt < tIntMin ? tIntMin - dt : 1; |
mjr | 104:6e06e0f4b476 | 546 | |
mjr | 104:6e06e0f4b476 | 547 | // Schedule the next transfer |
mjr | 104:6e06e0f4b476 | 548 | integrationTimeout.attach_us(this, &TSL14xx::startTransfer, dtNext); |
mjr | 82:4f6209cb5c33 | 549 | } |
mjr | 101:755f44622abc | 550 | |
mjr | 101:755f44622abc | 551 | // Clear the sensor shift register. Clocks in all of the pixels from |
mjr | 101:755f44622abc | 552 | // the sensor without bothering to read them on the ADC. Pulses SI |
mjr | 101:755f44622abc | 553 | // at the beginning of the operation, which starts a new integration |
mjr | 101:755f44622abc | 554 | // cycle. |
mjr | 82:4f6209cb5c33 | 555 | void clear() |
mjr | 82:4f6209cb5c33 | 556 | { |
mjr | 82:4f6209cb5c33 | 557 | // get the clock toggle register |
mjr | 82:4f6209cb5c33 | 558 | volatile uint32_t *ptor = &clockPort->PTOR; |
mjr | 82:4f6209cb5c33 | 559 | |
mjr | 82:4f6209cb5c33 | 560 | // make sure any DMA run is completed |
mjr | 82:4f6209cb5c33 | 561 | wait(); |
mjr | 82:4f6209cb5c33 | 562 | |
mjr | 82:4f6209cb5c33 | 563 | // clock in an SI pulse |
mjr | 82:4f6209cb5c33 | 564 | si = 1; |
mjr | 82:4f6209cb5c33 | 565 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 566 | clockPort->PSOR = clockMask; |
mjr | 82:4f6209cb5c33 | 567 | si = 0; |
mjr | 82:4f6209cb5c33 | 568 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 569 | |
mjr | 82:4f6209cb5c33 | 570 | // This starts a new integration period. Or more precisely, the |
mjr | 82:4f6209cb5c33 | 571 | // 19th clock pulse will start the new integration period. We're |
mjr | 82:4f6209cb5c33 | 572 | // going to blast the clock signal as fast as we can, at about |
mjr | 82:4f6209cb5c33 | 573 | // 100ns intervals (50ns up and 50ns down), so the 19th clock |
mjr | 82:4f6209cb5c33 | 574 | // will be about 2us from now. |
mjr | 82:4f6209cb5c33 | 575 | tInt = t.read_us() + 2; |
mjr | 82:4f6209cb5c33 | 576 | |
mjr | 82:4f6209cb5c33 | 577 | // clock out all pixels, plus an extra one to clock past the last |
mjr | 82:4f6209cb5c33 | 578 | // pixel and reset the last pixel's internal sampling switch in |
mjr | 82:4f6209cb5c33 | 579 | // the sensor |
mjr | 82:4f6209cb5c33 | 580 | for (int i = 0 ; i < nPixSensor + 1 ; ) |
mjr | 82:4f6209cb5c33 | 581 | { |
mjr | 82:4f6209cb5c33 | 582 | // toggle the clock to take it high |
mjr | 82:4f6209cb5c33 | 583 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 584 | |
mjr | 82:4f6209cb5c33 | 585 | // increment our loop variable here to pad the timing, to |
mjr | 82:4f6209cb5c33 | 586 | // keep our pulse width long enough for the sensor |
mjr | 82:4f6209cb5c33 | 587 | ++i; |
mjr | 82:4f6209cb5c33 | 588 | |
mjr | 82:4f6209cb5c33 | 589 | // toggle the clock to take it low |
mjr | 82:4f6209cb5c33 | 590 | *ptor = clockMask; |
mjr | 82:4f6209cb5c33 | 591 | } |
mjr | 82:4f6209cb5c33 | 592 | } |
mjr | 86:e30a1f60f783 | 593 | |
mjr | 82:4f6209cb5c33 | 594 | // DMA controller interfaces |
mjr | 82:4f6209cb5c33 | 595 | SimpleDMA adc_dma; // DMA channel for reading the analog input |
mjr | 82:4f6209cb5c33 | 596 | SimpleDMA clkUp_dma; // "Clock Up" channel |
mjr | 82:4f6209cb5c33 | 597 | SimpleDMA clkDn_dma; // "Clock Down" channel |
mjr | 82:4f6209cb5c33 | 598 | |
mjr | 82:4f6209cb5c33 | 599 | // Sensor interface pins |
mjr | 82:4f6209cb5c33 | 600 | DigitalOut si; // GPIO pin for sensor SI (serial data) |
mjr | 82:4f6209cb5c33 | 601 | DigitalOut clock; // GPIO pin for sensor SCLK (serial clock) |
mjr | 82:4f6209cb5c33 | 602 | GPIO_Type *clockPort; // IOPORT base address for clock pin - cached for DMA writes |
mjr | 82:4f6209cb5c33 | 603 | uint32_t clockMask; // IOPORT register bit mask for clock pin |
mjr | 100:1ff35c07217c | 604 | AltAnalogIn_8bit ao; // GPIO pin for sensor AO (analog output) |
mjr | 82:4f6209cb5c33 | 605 | |
mjr | 82:4f6209cb5c33 | 606 | // number of pixels in the physical sensor array |
mjr | 82:4f6209cb5c33 | 607 | int nPixSensor; // number of pixels in physical sensor array |
mjr | 82:4f6209cb5c33 | 608 | |
mjr | 82:4f6209cb5c33 | 609 | // pixel buffers - we keep two buffers so that we can transfer the |
mjr | 82:4f6209cb5c33 | 610 | // current sensor data into one buffer via DMA while we concurrently |
mjr | 82:4f6209cb5c33 | 611 | // process the last buffer |
mjr | 82:4f6209cb5c33 | 612 | uint8_t *pix1; // pixel array 1 |
mjr | 82:4f6209cb5c33 | 613 | uint8_t *pix2; // pixel array 2 |
mjr | 82:4f6209cb5c33 | 614 | |
mjr | 82:4f6209cb5c33 | 615 | // Timestamps of pix1 and pix2 arrays, in microseconds, in terms of the |
mjr | 100:1ff35c07217c | 616 | // sample timer (this->t). |
mjr | 82:4f6209cb5c33 | 617 | uint32_t t1; |
mjr | 82:4f6209cb5c33 | 618 | uint32_t t2; |
mjr | 82:4f6209cb5c33 | 619 | |
mjr | 82:4f6209cb5c33 | 620 | // DMA target buffer. This is the buffer for the next DMA transfer. |
mjr | 82:4f6209cb5c33 | 621 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
mjr | 82:4f6209cb5c33 | 622 | // data from the last transfer. |
mjr | 82:4f6209cb5c33 | 623 | uint8_t pixDMA; |
mjr | 82:4f6209cb5c33 | 624 | |
mjr | 101:755f44622abc | 625 | // Stable buffer ownership. At any given time, the DMA subsystem owns |
mjr | 101:755f44622abc | 626 | // the buffer specified by pixDMA. The other buffer - the "stable" buffer, |
mjr | 101:755f44622abc | 627 | // which contains the most recent completed frame, can be owned by EITHER |
mjr | 101:755f44622abc | 628 | // the client or by the DMA subsystem. Each time a DMA transfer completes, |
mjr | 101:755f44622abc | 629 | // the DMA subsystem looks at the stable buffer owner flag to determine |
mjr | 101:755f44622abc | 630 | // what to do: |
mjr | 101:755f44622abc | 631 | // |
mjr | 101:755f44622abc | 632 | // - If the DMA subsystem owns the stable buffer, it swaps buffers. This |
mjr | 101:755f44622abc | 633 | // makes the newly completed DMA buffer the new stable buffer, and makes |
mjr | 101:755f44622abc | 634 | // the old stable buffer the new DMA buffer. At this time, the DMA |
mjr | 101:755f44622abc | 635 | // subsystem also changes the stable buffer ownership to CLIENT. |
mjr | 101:755f44622abc | 636 | // |
mjr | 101:755f44622abc | 637 | // - If the CLIENT owns the stable buffer, the DMA subsystem can't swap |
mjr | 101:755f44622abc | 638 | // buffers, because the client is still using the stable buffer. It |
mjr | 101:755f44622abc | 639 | // simply leaves things as they are. |
mjr | 101:755f44622abc | 640 | // |
mjr | 101:755f44622abc | 641 | // In either case, the DMA system starts a new transfer at this point. |
mjr | 101:755f44622abc | 642 | // |
mjr | 101:755f44622abc | 643 | // The client, meanwhile, is free to access the stable buffer when it has |
mjr | 101:755f44622abc | 644 | // ownership. If the client *doesn't* have ownership, it must wait for |
mjr | 101:755f44622abc | 645 | // the ownership to be transferred, which can only be done by the DMA |
mjr | 101:755f44622abc | 646 | // subsystem on completing a transfer. |
mjr | 101:755f44622abc | 647 | // |
mjr | 101:755f44622abc | 648 | // When the client is done with the stable buffer, it transfers ownership |
mjr | 101:755f44622abc | 649 | // back to the DMA subsystem. |
mjr | 101:755f44622abc | 650 | // |
mjr | 101:755f44622abc | 651 | // Transfers of ownership from DMA to CLIENT are done only by DMA. |
mjr | 101:755f44622abc | 652 | // Transfers from CLIENT to DMA are done only by CLIENT. So whoever has |
mjr | 101:755f44622abc | 653 | // ownership now is responsible for transferring ownership. |
mjr | 101:755f44622abc | 654 | // |
mjr | 101:755f44622abc | 655 | volatile bool clientOwnsStablePix; |
mjr | 101:755f44622abc | 656 | |
mjr | 101:755f44622abc | 657 | // End-of-integration timeout handler. This lets us fire an interrupt |
mjr | 101:755f44622abc | 658 | // when the current integration cycle is done, so that we can start the |
mjr | 101:755f44622abc | 659 | // next cycle. |
mjr | 101:755f44622abc | 660 | Timeout integrationTimeout; |
mjr | 101:755f44622abc | 661 | |
mjr | 101:755f44622abc | 662 | // Requested minimum integration time, in micoseconds. The client can use |
mjr | 101:755f44622abc | 663 | // this to control the exposure level, by increasing it for a longer |
mjr | 101:755f44622abc | 664 | // exposure and thus more light-gathering in low-light conditions. Note |
mjr | 101:755f44622abc | 665 | // that the physical limit on the minimum integration time is roughly equal |
mjr | 101:755f44622abc | 666 | // to the pixel file transfer time, because the integration cycle is |
mjr | 101:755f44622abc | 667 | // initiated and ended by transfer starts. It's thus impossible to make |
mjr | 101:755f44622abc | 668 | // the integration time less than the time for one full pixel file |
mjr | 101:755f44622abc | 669 | // transfer. |
mjr | 101:755f44622abc | 670 | uint32_t tIntMin; |
mjr | 101:755f44622abc | 671 | |
mjr | 82:4f6209cb5c33 | 672 | // timing statistics |
mjr | 82:4f6209cb5c33 | 673 | Timer t; // sample timer |
mjr | 82:4f6209cb5c33 | 674 | uint32_t t0; // start time (us) of current sample |
mjr | 82:4f6209cb5c33 | 675 | uint32_t tInt; // start time (us) of current integration period |
mjr | 86:e30a1f60f783 | 676 | uint32_t tDone; // end time of latest finished transfer |
mjr | 82:4f6209cb5c33 | 677 | uint64_t totalTime; // total time consumed by all reads so far |
mjr | 82:4f6209cb5c33 | 678 | uint32_t nRuns; // number of runs so far |
mjr | 82:4f6209cb5c33 | 679 | }; |
mjr | 82:4f6209cb5c33 | 680 | |
mjr | 82:4f6209cb5c33 | 681 | #endif /* TSL14XX_H */ |