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