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@47:df7a88cd249c, 2016-02-18 (annotated)
- Committer:
- mjr
- Date:
- Thu Feb 18 07:32:20 2016 +0000
- Revision:
- 47:df7a88cd249c
- Parent:
- 45:c42166b2878c
- Child:
- 48:058ace2aed1d
3-channel linked DMA scheme for CCD image capture working
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 | 47:df7a88cd249c | 9 | * For fast reads of the pixel file from the sensor, we use the KL25Z's |
| mjr | 47:df7a88cd249c | 10 | * DMA capability. The method we use is very specific to the KL25Z |
| mjr | 47:df7a88cd249c | 11 | * hardware and is pretty tricky. These are attributes I don't normally |
| mjr | 47:df7a88cd249c | 12 | * like, but the speedup is amazing, enough to justify the complex |
| mjr | 47:df7a88cd249c | 13 | * design. I don't think there's any other way to even get close to |
| mjr | 47:df7a88cd249c | 14 | * this kind of read speed for this sensor with this MCU. (And I've |
| mjr | 47:df7a88cd249c | 15 | * tried!) Reading the sensor quickly is more than just academic, |
| mjr | 47:df7a88cd249c | 16 | * too: the plunger moves very fast during a release motion, so we |
| mjr | 47:df7a88cd249c | 17 | * have to be able to take correspondingly fast pictures to get |
| mjr | 47:df7a88cd249c | 18 | * clean images without motion blur. Before the speedup of this DMA |
| mjr | 47:df7a88cd249c | 19 | * approach, images captured during release motions had a lot of |
| mjr | 47:df7a88cd249c | 20 | * motion blur, and even aliasing from the sinusoidal motion when |
| mjr | 47:df7a88cd249c | 21 | * the plunger bounces back and forth off the springs. The speedup |
| mjr | 47:df7a88cd249c | 22 | * gets us over the threshold where we can capture images with very |
| mjr | 47:df7a88cd249c | 23 | * little blur, so we can track the motion much more precisely even |
| mjr | 47:df7a88cd249c | 24 | * at release speeds. This lets us determine the plunger position |
| mjr | 47:df7a88cd249c | 25 | * more precisely and more quickly, which improves responsiveness in |
| mjr | 47:df7a88cd249c | 26 | * the pinball simulator on the PC. |
| mjr | 47:df7a88cd249c | 27 | * |
| mjr | 47:df7a88cd249c | 28 | * Here's our approach. |
| mjr | 47:df7a88cd249c | 29 | * |
| mjr | 47:df7a88cd249c | 30 | * First, we put the analog input port (the ADC == Analog-to-Digital |
| mjr | 47:df7a88cd249c | 31 | * Converter) in "continuous" mode, at the highest clock speed we can |
| mjr | 47:df7a88cd249c | 32 | * program with the available clocks and the fastest read cycle |
| mjr | 47:df7a88cd249c | 33 | * available in the ADC hardware. (The analog input port is the |
| mjr | 47:df7a88cd249c | 34 | * GPIO pin attached to the sensor's AO == Analog Output pin, where |
| mjr | 47:df7a88cd249c | 35 | * it outputs each pixel's value, one at a time, as an analog voltage |
| mjr | 47:df7a88cd249c | 36 | * level.) In continuous mode, every time the ADC finishes taking a |
| mjr | 47:df7a88cd249c | 37 | * sample, it stores the result value in its output register and then |
| mjr | 47:df7a88cd249c | 38 | * immediately starts taking a new sample. This means that no MCU |
| mjr | 47:df7a88cd249c | 39 | * (or even DMA) action is required to start each new sample. This |
| mjr | 47:df7a88cd249c | 40 | * is where most of the speedup comes from, since it takes significant |
| mjr | 47:df7a88cd249c | 41 | * time (multiple microseconds) to move data through the peripheral |
| mjr | 47:df7a88cd249c | 42 | * registers, and it takes more time (also multiple microseconds) for |
| mjr | 47:df7a88cd249c | 43 | * the ADC to spin up for each new sample when in single-sample mode. |
| mjr | 47:df7a88cd249c | 44 | * We cut out about 7us this way and get the time per sample down to |
| mjr | 47:df7a88cd249c | 45 | * about 2us. This is close to the documented maximum speed for the |
| mjr | 47:df7a88cd249c | 46 | * ADC hardware. |
| mjr | 47:df7a88cd249c | 47 | * |
| mjr | 47:df7a88cd249c | 48 | * Second, we use the DMA controller to read the ADC result register |
| mjr | 47:df7a88cd249c | 49 | * and store each sample in a memory array for processing. The ADC |
| mjr | 47:df7a88cd249c | 50 | * hardware is designed to work with the DMA controller by signaling |
| mjr | 47:df7a88cd249c | 51 | * the DMA controller when a new sample is ready; this allows DMA to |
| mjr | 47:df7a88cd249c | 52 | * move each sample immediately when it's available without any CPU |
| mjr | 47:df7a88cd249c | 53 | * involvement. |
| mjr | 2:c174f9ee414a | 54 | * |
| mjr | 47:df7a88cd249c | 55 | * Third - and this is where it really gets tricky - we use two |
| mjr | 47:df7a88cd249c | 56 | * additional "linked" DMA channels to generate the clock signal |
| mjr | 47:df7a88cd249c | 57 | * to the CCD sensor. The clock signal is how we tell the CCD when |
| mjr | 47:df7a88cd249c | 58 | * to place the next pixel voltage on its AO pin, so the clock has |
| mjr | 47:df7a88cd249c | 59 | * to be generated in lock step with the ADC sampling cycle. The |
| mjr | 47:df7a88cd249c | 60 | * ADC timing isn't perfectly uniform or predictable, so we can't |
| mjr | 47:df7a88cd249c | 61 | * just generate the pixel clock with a *real* clock. We have to |
| mjr | 47:df7a88cd249c | 62 | * time the signal exactly with the ADC, which means that we have |
| mjr | 47:df7a88cd249c | 63 | * to generate it from the ADC "sample is ready" signal. Fortunately, |
| mjr | 47:df7a88cd249c | 64 | * there is just such a signal, and in fact we're already using it, |
| mjr | 47:df7a88cd249c | 65 | * as described above, to tell the DMA when to move each result from |
| mjr | 47:df7a88cd249c | 66 | * the ADC output register to our memory array. So how do we use this |
| mjr | 47:df7a88cd249c | 67 | * to generate the CCD clock? The answer lies in the DMA controller's |
| mjr | 47:df7a88cd249c | 68 | * channel linking feature. This allows one DMA channel to trigger a |
| mjr | 47:df7a88cd249c | 69 | * second DMA channel each time the first channel completes one |
| mjr | 47:df7a88cd249c | 70 | * transfer. And we can use DMA to control our clock GPIO pin by |
| mjr | 47:df7a88cd249c | 71 | * using the pin's GPIO IPORT register as the DMA destination address. |
| mjr | 47:df7a88cd249c | 72 | * Specifically, we can take the clock high by writing our pin's bit |
| mjr | 47:df7a88cd249c | 73 | * pattern to the PSOR ("set output") register, and we can take the |
| mjr | 47:df7a88cd249c | 74 | * clock low by writing to the PCOR ("clear output") register. We |
| mjr | 47:df7a88cd249c | 75 | * use one DMA channel for each of these operations. |
| mjr | 47:df7a88cd249c | 76 | * |
| mjr | 47:df7a88cd249c | 77 | * Putting it all together, the cascade of linked DMA channels |
| mjr | 47:df7a88cd249c | 78 | * works like this: |
| mjr | 47:df7a88cd249c | 79 | * |
| mjr | 47:df7a88cd249c | 80 | * - The ADC sample completes, which triggers channel 1, the |
| mjr | 47:df7a88cd249c | 81 | * "Clock Up" channel. This performs one transfer of the |
| mjr | 47:df7a88cd249c | 82 | * clock GPIO bit to the clock PSOR register, taking the clock |
| mjr | 47:df7a88cd249c | 83 | * high, which causes the CCD to move the next pixel onto AO. |
| mjr | 47:df7a88cd249c | 84 | * |
| mjr | 47:df7a88cd249c | 85 | * - After the Clock Up channel does its transfer, it triggers |
| mjr | 47:df7a88cd249c | 86 | * its link to channel 2, the ADC transfer channel. This |
| mjr | 47:df7a88cd249c | 87 | * channel moves the ADC output register value to our memory |
| mjr | 47:df7a88cd249c | 88 | * array. |
| mjr | 47:df7a88cd249c | 89 | * |
| mjr | 47:df7a88cd249c | 90 | * - After the ADC channel does its transfer, it triggers channel |
| mjr | 47:df7a88cd249c | 91 | * 3, the "Clock Down" channel. This performs one transfer of |
| mjr | 47:df7a88cd249c | 92 | * the clock GPIO bit to the clock PCOR register, taking the |
| mjr | 47:df7a88cd249c | 93 | * clock low. |
| mjr | 47:df7a88cd249c | 94 | * |
| mjr | 47:df7a88cd249c | 95 | * Note that the order of the channels - Clock Up, ADC, Clock Down - |
| mjr | 47:df7a88cd249c | 96 | * is important, because it ensures that we don't toggle the clock |
| mjr | 47:df7a88cd249c | 97 | * bit too fast. The CCD has a minimum pulse duration of 50ns for |
| mjr | 47:df7a88cd249c | 98 | * the clock signal. The DMA controller is so fast that we could |
| mjr | 47:df7a88cd249c | 99 | * toggle the clock faster than this limit if we did the Up and |
| mjr | 47:df7a88cd249c | 100 | * Down transfers adjacently. |
| mjr | 47:df7a88cd249c | 101 | * |
| mjr | 47:df7a88cd249c | 102 | * Note also that it's important that Clock Up be the first operation, |
| mjr | 47:df7a88cd249c | 103 | * because the ADC is in continuous mode, meaning that it starts |
| mjr | 47:df7a88cd249c | 104 | * taking a new sample immediately upon finishing the previous one. |
| mjr | 47:df7a88cd249c | 105 | * So when the ADC DMA signal fires, the new sample is just starting. |
| mjr | 47:df7a88cd249c | 106 | * We therefore have to get the next pixel onto the sampling pin as |
| mjr | 47:df7a88cd249c | 107 | * quickly as possible. The CCD sensor's "analog output settling |
| mjr | 47:df7a88cd249c | 108 | * time" is 120ns - this is the time for a new pixel voltage to |
| mjr | 47:df7a88cd249c | 109 | * stabilize on AO after a clock rising edge. So assuming that the |
| mjr | 47:df7a88cd249c | 110 | * ADC raises the DMA signal immediately, and the DMA controller |
| mjr | 47:df7a88cd249c | 111 | * responds within a couple of MCU clock cycles, we should have the |
| mjr | 47:df7a88cd249c | 112 | * new pixel voltage stable on the sampling pin by about 200ns after |
| mjr | 47:df7a88cd249c | 113 | * the new ADC sample cycle starts. The sampling cycle with our |
| mjr | 47:df7a88cd249c | 114 | * current parameters is about 2us, so the voltage level is stable |
| mjr | 47:df7a88cd249c | 115 | * for 90% of the cycle. |
| mjr | 47:df7a88cd249c | 116 | * |
| mjr | 47:df7a88cd249c | 117 | * Also, it's okay that the ADC sample transfer doesn't happen until |
| mjr | 47:df7a88cd249c | 118 | * after the Clock Up DMA transfer. The ADC output register holds the |
| mjr | 47:df7a88cd249c | 119 | * last result until the next sample completes, so we have about 2us |
| mjr | 47:df7a88cd249c | 120 | * to grab it. The first Clock Up DMA transfer only takes a couple |
| mjr | 47:df7a88cd249c | 121 | * of clocks - order of 100ns - so we get to it with time to spare. |
| mjr | 47:df7a88cd249c | 122 | * |
| mjr | 47:df7a88cd249c | 123 | * (Note that it's tempting to try to handle the clock with a single |
| mjr | 47:df7a88cd249c | 124 | * DMA channel, by using the PTOR "toggle output" to do TWO writes: |
| mjr | 47:df7a88cd249c | 125 | * one to toggle the clock up and another to toggle it down. But |
| mjr | 47:df7a88cd249c | 126 | * I haven't found a good way to do this. The problem is that the |
| mjr | 47:df7a88cd249c | 127 | * DMA controller can only do one transfer per trigger in the fully |
| mjr | 47:df7a88cd249c | 128 | * autonomous mode we're using, and we need to do two writes. In |
| mjr | 47:df7a88cd249c | 129 | * fact, we'd really need to do three or four writes: we'd have to |
| mjr | 47:df7a88cd249c | 130 | * throw in one or two no-op writes (of all zeroes) between the two |
| mjr | 47:df7a88cd249c | 131 | * toggles, for time padding to ensure that we meet the minimum 50ns |
| mjr | 47:df7a88cd249c | 132 | * pulse width for the TSL1410R clock signal. But it's the same |
| mjr | 47:df7a88cd249c | 133 | * issue whether it's two writes or four. The DMA controller does |
| mjr | 47:df7a88cd249c | 134 | * have a "continuous" mode that does an entire transfer on a single |
| mjr | 47:df7a88cd249c | 135 | * trigger, but it can't reset itself after such a transfer, so CPU |
| mjr | 47:df7a88cd249c | 136 | * intervention would be required on every ADC cycle to set up the |
| mjr | 47:df7a88cd249c | 137 | * next clock write. We could do that with an interrupt, but given |
| mjr | 47:df7a88cd249c | 138 | * the 2us cycle time, an interrupt would create a ton of CPU load, |
| mjr | 47:df7a88cd249c | 139 | * and probably isn't even enough time to reliably complete each |
| mjr | 47:df7a88cd249c | 140 | * interrupt service call before the next cycle. Fortunately, at |
| mjr | 47:df7a88cd249c | 141 | * the moment we only have one other module in the whole system |
| mjr | 47:df7a88cd249c | 142 | * using DMA at all - the TLC5940 PWM controller interface, which |
| mjr | 47:df7a88cd249c | 143 | * only needs one channel. So with the four available channels in |
| mjr | 47:df7a88cd249c | 144 | * the hardware, we can afford to use three of them here.) |
| mjr | 2:c174f9ee414a | 145 | */ |
| mjr | 2:c174f9ee414a | 146 | |
| mjr | 35:e959ffba78fd | 147 | #include "mbed.h" |
| mjr | 35:e959ffba78fd | 148 | #include "config.h" |
| mjr | 43:7a6364d82a41 | 149 | #include "AltAnalogIn.h" |
| mjr | 45:c42166b2878c | 150 | #include "SimpleDMA.h" |
| mjr | 47:df7a88cd249c | 151 | #include "DMAChannels.h" |
| mjr | 2:c174f9ee414a | 152 | |
| mjr | 35:e959ffba78fd | 153 | #ifndef TSL1410R_H |
| mjr | 35:e959ffba78fd | 154 | #define TSL1410R_H |
| mjr | 47:df7a88cd249c | 155 | |
| mjr | 35:e959ffba78fd | 156 | |
| mjr | 47:df7a88cd249c | 157 | // To allow DMA access to the clock pin, we need to point the DMA |
| mjr | 47:df7a88cd249c | 158 | // controller to the IOPORT registers that control the pin. PORT_BASE() |
| mjr | 47:df7a88cd249c | 159 | // gives us the address of the register group for the 32 GPIO pins with |
| mjr | 47:df7a88cd249c | 160 | // the same letter name as our target pin (e.g., PTA0 through PTA31), |
| mjr | 47:df7a88cd249c | 161 | // and PINMASK gives us the bit pattern to write to those registers to |
| mjr | 47:df7a88cd249c | 162 | // access our single GPIO pin. Each register group has three special |
| mjr | 47:df7a88cd249c | 163 | // registers that update the pin in particular ways: PSOR ("set output |
| mjr | 47:df7a88cd249c | 164 | // register") turns pins on, PCOR ("clear output register") turns pins |
| mjr | 47:df7a88cd249c | 165 | // off, and PTOR ("toggle output register") toggle pins to the opposite |
| mjr | 47:df7a88cd249c | 166 | // of their current values. These registers have special semantics: |
| mjr | 47:df7a88cd249c | 167 | // writing a bit as 0 has no effect on the corresponding pin, while |
| mjr | 47:df7a88cd249c | 168 | // writing a bit as 1 performs the register's action on the pin. This |
| mjr | 47:df7a88cd249c | 169 | // allows a single GPIO pin to be set, cleared, or toggled with a |
| mjr | 47:df7a88cd249c | 170 | // 32-bit write to one of these registers, without affecting any of the |
| mjr | 47:df7a88cd249c | 171 | // other pins addressed by the register. (It also allows changing any |
| mjr | 47:df7a88cd249c | 172 | // group of pins with a single write, although we don't use that |
| mjr | 47:df7a88cd249c | 173 | // feature here.) |
| mjr | 35:e959ffba78fd | 174 | // |
| mjr | 47:df7a88cd249c | 175 | // - To turn a pin ON: PORT_BASE(pin)->PSOR = PINMASK(pin) |
| mjr | 47:df7a88cd249c | 176 | // - To turn a pin OFF: PORT_BASE(pin)->PCOR = PINMASK(pin) |
| mjr | 47:df7a88cd249c | 177 | // - To toggle a pin: PORT_BASE(pin)->PTOR = PINMASK(pin) |
| mjr | 47:df7a88cd249c | 178 | // |
| mjr | 43:7a6364d82a41 | 179 | #define GPIO_PORT(pin) (((unsigned int)(pin)) >> PORT_SHIFT) |
| mjr | 47:df7a88cd249c | 180 | #define GPIO_PORT_BASE(pin) ((GPIO_Type *)(PTA_BASE + GPIO_PORT(pin) * 0x40)) |
| mjr | 43:7a6364d82a41 | 181 | #define GPIO_PINMASK(pin) gpio_set(pin) |
| mjr | 2:c174f9ee414a | 182 | |
| mjr | 35:e959ffba78fd | 183 | class TSL1410R |
| mjr | 2:c174f9ee414a | 184 | { |
| mjr | 2:c174f9ee414a | 185 | public: |
| mjr | 47:df7a88cd249c | 186 | TSL1410R(int nPixSensor, PinName siPin, PinName clockPin, PinName ao1Pin, PinName /*ao2Pin*/) |
| mjr | 47:df7a88cd249c | 187 | : adc_dma(DMAch_ADC), |
| mjr | 47:df7a88cd249c | 188 | clkUp_dma(DMAch_CLKUP), |
| mjr | 47:df7a88cd249c | 189 | clkDn_dma(DMAch_CLKDN), |
| mjr | 47:df7a88cd249c | 190 | si(siPin), |
| mjr | 47:df7a88cd249c | 191 | clock(clockPin), |
| mjr | 47:df7a88cd249c | 192 | ao1(ao1Pin, true), |
| mjr | 47:df7a88cd249c | 193 | nPixSensor(nPixSensor) |
| mjr | 17:ab3cec0c8bf4 | 194 | { |
| mjr | 47:df7a88cd249c | 195 | // allocate our double pixel buffers |
| mjr | 47:df7a88cd249c | 196 | pix1 = new uint8_t[nPixSensor*2]; |
| mjr | 47:df7a88cd249c | 197 | pix2 = pix1 + nPixSensor; |
| mjr | 35:e959ffba78fd | 198 | |
| mjr | 47:df7a88cd249c | 199 | // put the first DMA transfer into the first buffer (pix1) |
| mjr | 47:df7a88cd249c | 200 | pixDMA = 0; |
| mjr | 47:df7a88cd249c | 201 | |
| mjr | 35:e959ffba78fd | 202 | // remember the clock pin port base and pin mask for fast access |
| mjr | 35:e959ffba78fd | 203 | clockPort = GPIO_PORT_BASE(clockPin); |
| mjr | 35:e959ffba78fd | 204 | clockMask = GPIO_PINMASK(clockPin); |
| mjr | 35:e959ffba78fd | 205 | |
| mjr | 43:7a6364d82a41 | 206 | // clear out power-on random data by clocking through all pixels twice |
| mjr | 17:ab3cec0c8bf4 | 207 | clear(); |
| mjr | 17:ab3cec0c8bf4 | 208 | clear(); |
| mjr | 43:7a6364d82a41 | 209 | |
| mjr | 47:df7a88cd249c | 210 | // Set up the Clock Up DMA channel. This channel takes the |
| mjr | 47:df7a88cd249c | 211 | // clock high by writing the clock bit to the PSOR (set output) |
| mjr | 47:df7a88cd249c | 212 | // register for the clock pin. |
| mjr | 47:df7a88cd249c | 213 | clkUp_dma.source(&clockMask, false, 32); |
| mjr | 47:df7a88cd249c | 214 | clkUp_dma.destination(&clockPort->PSOR, false, 32); |
| mjr | 47:df7a88cd249c | 215 | |
| mjr | 47:df7a88cd249c | 216 | // Set up the Clock Down DMA channel. This channel takes the |
| mjr | 47:df7a88cd249c | 217 | // clock low by writing the clock bit to the PCOR (clear output) |
| mjr | 47:df7a88cd249c | 218 | // register for the clock pin. |
| mjr | 47:df7a88cd249c | 219 | clkDn_dma.source(&clockMask, false, 32); |
| mjr | 47:df7a88cd249c | 220 | clkDn_dma.destination(&clockPort->PCOR, false, 32); |
| mjr | 45:c42166b2878c | 221 | |
| mjr | 47:df7a88cd249c | 222 | // Set up the ADC transfer DMA channel. This channel transfers |
| mjr | 47:df7a88cd249c | 223 | // the current analog sampling result from the ADC output register |
| mjr | 47:df7a88cd249c | 224 | // to our pixel array. |
| mjr | 47:df7a88cd249c | 225 | ao1.initDMA(&adc_dma); |
| mjr | 47:df7a88cd249c | 226 | |
| mjr | 47:df7a88cd249c | 227 | // Set up our chain of linked DMA channel: |
| mjr | 47:df7a88cd249c | 228 | // ADC sample completion triggers Clock Up |
| mjr | 47:df7a88cd249c | 229 | // ...which triggers the ADC transfer |
| mjr | 47:df7a88cd249c | 230 | // ... which triggers Clock Down |
| mjr | 47:df7a88cd249c | 231 | clkUp_dma.trigger(Trigger_ADC0); |
| mjr | 47:df7a88cd249c | 232 | clkUp_dma.link(adc_dma); |
| mjr | 47:df7a88cd249c | 233 | adc_dma.link(clkDn_dma, false); |
| mjr | 45:c42166b2878c | 234 | |
| mjr | 47:df7a88cd249c | 235 | // Set the trigger on the downstream links to NONE - these are |
| mjr | 47:df7a88cd249c | 236 | // triggered by their upstream links, so they don't need separate |
| mjr | 47:df7a88cd249c | 237 | // peripheral or software triggers. |
| mjr | 47:df7a88cd249c | 238 | adc_dma.trigger(Trigger_NONE); |
| mjr | 47:df7a88cd249c | 239 | clkDn_dma.trigger(Trigger_NONE); |
| mjr | 47:df7a88cd249c | 240 | |
| mjr | 47:df7a88cd249c | 241 | // Register an interrupt callback so that we're notified when |
| mjr | 47:df7a88cd249c | 242 | // the last transfer completes. |
| mjr | 47:df7a88cd249c | 243 | clkDn_dma.attach(this, &TSL1410R::transferDone); |
| mjr | 47:df7a88cd249c | 244 | |
| mjr | 47:df7a88cd249c | 245 | // clear the timing statistics |
| mjr | 47:df7a88cd249c | 246 | totalTime = 0.0; |
| mjr | 47:df7a88cd249c | 247 | nRuns = 0; |
| mjr | 17:ab3cec0c8bf4 | 248 | } |
| mjr | 43:7a6364d82a41 | 249 | |
| mjr | 47:df7a88cd249c | 250 | // end of transfer notification |
| mjr | 47:df7a88cd249c | 251 | void transferDone() |
| mjr | 47:df7a88cd249c | 252 | { |
| mjr | 47:df7a88cd249c | 253 | // stop the ADC sampler |
| mjr | 47:df7a88cd249c | 254 | ao1.stop(); |
| mjr | 47:df7a88cd249c | 255 | |
| mjr | 47:df7a88cd249c | 256 | // clock out one extra pixel to leave A1 in the high-Z state |
| mjr | 47:df7a88cd249c | 257 | clock = 1; |
| mjr | 47:df7a88cd249c | 258 | clock = 0; |
| mjr | 45:c42166b2878c | 259 | |
| mjr | 47:df7a88cd249c | 260 | // stop the clock |
| mjr | 47:df7a88cd249c | 261 | t.stop(); |
| mjr | 47:df7a88cd249c | 262 | |
| mjr | 47:df7a88cd249c | 263 | // count the statistics |
| mjr | 47:df7a88cd249c | 264 | totalTime += t.read(); |
| mjr | 47:df7a88cd249c | 265 | nRuns += 1; |
| mjr | 47:df7a88cd249c | 266 | } |
| mjr | 47:df7a88cd249c | 267 | |
| mjr | 47:df7a88cd249c | 268 | // Get the stable pixel array. This is the image array from the |
| mjr | 47:df7a88cd249c | 269 | // previous capture. It remains valid until the next startCapture() |
| mjr | 47:df7a88cd249c | 270 | // call, at which point this buffer will be reused for the new capture. |
| mjr | 47:df7a88cd249c | 271 | void getPix(uint8_t * &pix, int &n) |
| mjr | 17:ab3cec0c8bf4 | 272 | { |
| mjr | 47:df7a88cd249c | 273 | // return the pixel array that ISN'T assigned to the DMA |
| mjr | 47:df7a88cd249c | 274 | pix = pixDMA ? pix1 : pix2; |
| mjr | 47:df7a88cd249c | 275 | n = nPixSensor; |
| mjr | 47:df7a88cd249c | 276 | } |
| mjr | 47:df7a88cd249c | 277 | |
| mjr | 47:df7a88cd249c | 278 | // Start an image capture from the sensor. This waits for any previous |
| mjr | 47:df7a88cd249c | 279 | // capture to finish, then starts a new one and returns immediately. The |
| mjr | 47:df7a88cd249c | 280 | // new capture proceeds autonomously via the DMA hardware, so the caller |
| mjr | 47:df7a88cd249c | 281 | // can continue with other processing during the capture. |
| mjr | 47:df7a88cd249c | 282 | void startCapture() |
| mjr | 47:df7a88cd249c | 283 | { |
| mjr | 47:df7a88cd249c | 284 | // wait for the previous transfer to finish |
| mjr | 47:df7a88cd249c | 285 | while (adc_dma.isBusy()) { } |
| mjr | 43:7a6364d82a41 | 286 | |
| mjr | 47:df7a88cd249c | 287 | // swap to the other DMA buffer |
| mjr | 47:df7a88cd249c | 288 | pixDMA ^= 1; |
| mjr | 47:df7a88cd249c | 289 | |
| mjr | 47:df7a88cd249c | 290 | // start timing this transfer |
| mjr | 47:df7a88cd249c | 291 | t.reset(); |
| mjr | 47:df7a88cd249c | 292 | t.start(); |
| mjr | 35:e959ffba78fd | 293 | |
| mjr | 47:df7a88cd249c | 294 | // set up the active pixel array as the destination buffer for |
| mjr | 47:df7a88cd249c | 295 | // the ADC DMA channel |
| mjr | 47:df7a88cd249c | 296 | adc_dma.destination(pixDMA ? pix2 : pix1, true); |
| mjr | 47:df7a88cd249c | 297 | |
| mjr | 47:df7a88cd249c | 298 | // start the DMA transfers |
| mjr | 47:df7a88cd249c | 299 | clkDn_dma.start(nPixSensor*4); |
| mjr | 47:df7a88cd249c | 300 | adc_dma.start(nPixSensor); |
| mjr | 47:df7a88cd249c | 301 | clkUp_dma.start(nPixSensor*4); |
| mjr | 47:df7a88cd249c | 302 | |
| mjr | 17:ab3cec0c8bf4 | 303 | // start the next integration cycle by pulsing SI and one clock |
| mjr | 17:ab3cec0c8bf4 | 304 | si = 1; |
| mjr | 43:7a6364d82a41 | 305 | clock = 1; |
| mjr | 17:ab3cec0c8bf4 | 306 | si = 0; |
| mjr | 43:7a6364d82a41 | 307 | clock = 0; |
| mjr | 17:ab3cec0c8bf4 | 308 | |
| mjr | 47:df7a88cd249c | 309 | // clock in the first pixel |
| mjr | 47:df7a88cd249c | 310 | clock = 1; |
| mjr | 47:df7a88cd249c | 311 | clock = 0; |
| mjr | 43:7a6364d82a41 | 312 | |
| mjr | 47:df7a88cd249c | 313 | // Start the ADC sampler. The ADC will read samples continuously |
| mjr | 47:df7a88cd249c | 314 | // until we tell it to stop. Each sample completion will trigger |
| mjr | 47:df7a88cd249c | 315 | // our linked DMA channel, which will store the next sample in our |
| mjr | 47:df7a88cd249c | 316 | // pixel array and pulse the CCD serial data clock to load the next |
| mjr | 47:df7a88cd249c | 317 | // pixel onto the analog sampler pin. This will all happen without |
| mjr | 47:df7a88cd249c | 318 | // any CPU involvement, so we can continue with other work. |
| mjr | 47:df7a88cd249c | 319 | ao1.start(); |
| mjr | 17:ab3cec0c8bf4 | 320 | } |
| mjr | 47:df7a88cd249c | 321 | |
| mjr | 2:c174f9ee414a | 322 | // Clock through all pixels to clear the array. Pulses SI at the |
| mjr | 2:c174f9ee414a | 323 | // beginning of the operation, which starts a new integration cycle. |
| mjr | 2:c174f9ee414a | 324 | // The caller can thus immediately call read() to read the pixels |
| mjr | 2:c174f9ee414a | 325 | // integrated while the clear() was taking place. |
| mjr | 17:ab3cec0c8bf4 | 326 | void clear() |
| mjr | 17:ab3cec0c8bf4 | 327 | { |
| mjr | 17:ab3cec0c8bf4 | 328 | // clock in an SI pulse |
| mjr | 17:ab3cec0c8bf4 | 329 | si = 1; |
| mjr | 43:7a6364d82a41 | 330 | clockPort->PSOR = clockMask; |
| mjr | 17:ab3cec0c8bf4 | 331 | si = 0; |
| mjr | 43:7a6364d82a41 | 332 | clockPort->PCOR = clockMask; |
| mjr | 17:ab3cec0c8bf4 | 333 | |
| mjr | 17:ab3cec0c8bf4 | 334 | // clock out all pixels |
| mjr | 47:df7a88cd249c | 335 | for (int i = 0 ; i < nPixSensor + 1 ; ++i) |
| mjr | 47:df7a88cd249c | 336 | { |
| mjr | 47:df7a88cd249c | 337 | clock = 1; |
| mjr | 47:df7a88cd249c | 338 | clock = 0; |
| mjr | 17:ab3cec0c8bf4 | 339 | } |
| mjr | 17:ab3cec0c8bf4 | 340 | } |
| mjr | 47:df7a88cd249c | 341 | |
| mjr | 47:df7a88cd249c | 342 | // get the timing statistics |
| mjr | 47:df7a88cd249c | 343 | void getTimingStats(float &totalTime, uint32_t &nRuns) |
| mjr | 47:df7a88cd249c | 344 | { |
| mjr | 47:df7a88cd249c | 345 | totalTime = this->totalTime; |
| mjr | 47:df7a88cd249c | 346 | nRuns = this->nRuns; |
| mjr | 47:df7a88cd249c | 347 | } |
| mjr | 2:c174f9ee414a | 348 | |
| mjr | 2:c174f9ee414a | 349 | private: |
| mjr | 47:df7a88cd249c | 350 | // DMA controller interfaces |
| mjr | 47:df7a88cd249c | 351 | SimpleDMA adc_dma; // DMA channel for reading the analog input |
| mjr | 47:df7a88cd249c | 352 | SimpleDMA clkUp_dma; // "Clock Up" channel |
| mjr | 47:df7a88cd249c | 353 | SimpleDMA clkDn_dma; // "Clock Down" channel |
| mjr | 47:df7a88cd249c | 354 | |
| mjr | 47:df7a88cd249c | 355 | // Sensor interface pins |
| mjr | 40:cc0d9814522b | 356 | DigitalOut si; // GPIO pin for sensor SI (serial data) |
| mjr | 40:cc0d9814522b | 357 | DigitalOut clock; // GPIO pin for sensor SCLK (serial clock) |
| mjr | 47:df7a88cd249c | 358 | GPIO_Type *clockPort; // IOPORT base address for clock pin - cached for DMA writes |
| mjr | 35:e959ffba78fd | 359 | uint32_t clockMask; // IOPORT register bit mask for clock pin |
| mjr | 47:df7a88cd249c | 360 | AltAnalogIn ao1; // GPIO pin for sensor AO (analog output) |
| mjr | 47:df7a88cd249c | 361 | |
| mjr | 47:df7a88cd249c | 362 | // number of pixels in the physical sensor array |
| mjr | 47:df7a88cd249c | 363 | int nPixSensor; // number of pixels in physical sensor array |
| mjr | 47:df7a88cd249c | 364 | |
| mjr | 47:df7a88cd249c | 365 | // pixel buffers - we keep two buffers so that we can transfer the |
| mjr | 47:df7a88cd249c | 366 | // current sensor data into one buffer via DMA while we concurrently |
| mjr | 47:df7a88cd249c | 367 | // process the last buffer |
| mjr | 47:df7a88cd249c | 368 | uint8_t *pix1; // pixel array 1 |
| mjr | 47:df7a88cd249c | 369 | uint8_t *pix2; // pixel array 2 |
| mjr | 47:df7a88cd249c | 370 | |
| mjr | 47:df7a88cd249c | 371 | // DMA target buffer. This is the buffer for the next DMA transfer. |
| mjr | 47:df7a88cd249c | 372 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
| mjr | 47:df7a88cd249c | 373 | // data from the last transfer. |
| mjr | 47:df7a88cd249c | 374 | uint8_t pixDMA; |
| mjr | 47:df7a88cd249c | 375 | |
| mjr | 47:df7a88cd249c | 376 | // timing statistics |
| mjr | 47:df7a88cd249c | 377 | Timer t; // timer - started when we start a DMA transfer |
| mjr | 47:df7a88cd249c | 378 | float totalTime; // total time consumed by all reads so far |
| mjr | 47:df7a88cd249c | 379 | uint32_t nRuns; // number of runs so far |
| mjr | 2:c174f9ee414a | 380 | }; |
| mjr | 2:c174f9ee414a | 381 | |
| mjr | 2:c174f9ee414a | 382 | #endif /* TSL1410R_H */ |
