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
TCD1103/TCD1103.h@101:755f44622abc, 2019-11-29 (annotated)
- Committer:
- mjr
- Date:
- Fri Nov 29 05:38:07 2019 +0000
- Revision:
- 101:755f44622abc
- Parent:
- 100:1ff35c07217c
- Child:
- 103:dec22cd65b2a
Use continuous asynchronous frame transfers in image sensors
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 100:1ff35c07217c | 1 | // Toshiba TCD1103 linear CCD image sensor, 1x1500 pixels |
mjr | 100:1ff35c07217c | 2 | // |
mjr | 100:1ff35c07217c | 3 | // This sensor is conceptually similar to the TAOS TSL1410R (the original |
mjr | 100:1ff35c07217c | 4 | // Pinscape sensor!). Like the TSL1410R, it has a linear array of optical |
mjr | 100:1ff35c07217c | 5 | // sensor pixels that convert incident photons into electrical charge, an |
mjr | 100:1ff35c07217c | 6 | // internal shift register connected to the pixel file that acts as an |
mjr | 100:1ff35c07217c | 7 | // electronic shutter, and a serial interface that clocks the pixels out |
mjr | 100:1ff35c07217c | 8 | // to the host in analog voltage level format. |
mjr | 100:1ff35c07217c | 9 | // |
mjr | 100:1ff35c07217c | 10 | // Mechanically, this sensor has an entirely different size scale vs the |
mjr | 100:1ff35c07217c | 11 | // TSL1410R. The 1410R's sensor window is about the same size as a standard |
mjr | 100:1ff35c07217c | 12 | // plunger's travel range (about 80mm), so the mechanical setup we use with |
mjr | 100:1ff35c07217c | 13 | // sensor is to situate the sensor adjacent to the plunger, with the pixel |
mjr | 100:1ff35c07217c | 14 | // window aligned with the plunger's axis of motion, so that the plunger |
mjr | 100:1ff35c07217c | 15 | // casts a shadow on the sensor at 1:1 scale. The TCD1103, in contrast, is |
mjr | 100:1ff35c07217c | 16 | // a tiny little thing, with about an 8mm window. That means that we have |
mjr | 100:1ff35c07217c | 17 | // to reduce the plunger shadow image by about 10X to fit the sensor, so an |
mjr | 100:1ff35c07217c | 18 | // optical lens is required. This makes it more complicated to set up, but |
mjr | 100:1ff35c07217c | 19 | // it also adds the advantage of allowing us to focus the image, for a more |
mjr | 100:1ff35c07217c | 20 | // precise reading. The shadow in the lens-less 1410R setup is usually about |
mjr | 100:1ff35c07217c | 21 | // four of five pixels wide, so we lose a lot of the sensor's native |
mjr | 100:1ff35c07217c | 22 | // precision to the poor optics - we only get about 1/50" resolution as a |
mjr | 100:1ff35c07217c | 23 | // result. With a focusing lens, we could potentially get single-pixel |
mjr | 100:1ff35c07217c | 24 | // resolution, which would be about 1/500" resolution. The reality will |
mjr | 100:1ff35c07217c | 25 | // be somewhat lower, depending on how hard we want to work at the optics, |
mjr | 100:1ff35c07217c | 26 | // but it should be possible to do much better than the unfocused 1410R. |
mjr | 100:1ff35c07217c | 27 | // |
mjr | 100:1ff35c07217c | 28 | // The electronic interface to this sensor has some fairly tight timing |
mjr | 100:1ff35c07217c | 29 | // requirements, per the data sheet. The sensor requires the host to |
mjr | 100:1ff35c07217c | 30 | // provide a master clock that runs at 0.4 MHz to 4 MHz. The data sheet's |
mjr | 100:1ff35c07217c | 31 | // timing diagrams imply that the master clock runs continuously, although |
mjr | 100:1ff35c07217c | 32 | // it's probably like the 1410R, where the clock is only needed when you |
mjr | 100:1ff35c07217c | 33 | // want to run the shift register and can be stopped at other times. |
mjr | 100:1ff35c07217c | 34 | // |
mjr | 100:1ff35c07217c | 35 | // As with the 1410R, we'll have to use DMA for the ADC transfers in order |
mjr | 100:1ff35c07217c | 36 | // to keep up with the high data rate without overloading the KL25Z CPU. |
mjr | 100:1ff35c07217c | 37 | // With the 1410R, we're able to use the ADC itself as the clock source, |
mjr | 100:1ff35c07217c | 38 | // by running the ADC in continous mode and using its "sample ready" signal |
mjr | 100:1ff35c07217c | 39 | // to trigger the DMA transfer. We used this to generate the external clock |
mjr | 100:1ff35c07217c | 40 | // signal for the sensor by "linking" the ADC's DMA channel to another pair |
mjr | 100:1ff35c07217c | 41 | // of DMA channels that generated the clock up/down signal each time an ADC |
mjr | 100:1ff35c07217c | 42 | // sample completed. This strategy won't work with the Toshiba sensor, |
mjr | 100:1ff35c07217c | 43 | // though, because the Toshiba sensor's timing sequence requires *two* clock |
mjr | 100:1ff35c07217c | 44 | // pulses per pixel. I can't come up with a way to accomplish that with the |
mjr | 100:1ff35c07217c | 45 | // linked-DMA approach. (I've tried!) |
mjr | 100:1ff35c07217c | 46 | // |
mjr | 100:1ff35c07217c | 47 | // So instead, we'll have to generate a true clock signal for the sensor. |
mjr | 100:1ff35c07217c | 48 | // The obvious way to do this (and the only way, as far as I can come up with) |
mjr | 100:1ff35c07217c | 49 | // is to use a TPM channel - that is, a PWM output. TPM channels are designed |
mjr | 100:1ff35c07217c | 50 | // precisely for this kind of work, so this is the right approach in terms of |
mjr | 100:1ff35c07217c | 51 | // suitability, but it has the downside that TPM units are an extremely scarce |
mjr | 100:1ff35c07217c | 52 | // resource on the KL25Z. We only have three of them to work with. Luckily |
mjr | 100:1ff35c07217c | 53 | // the rest of the Pinscape software only requires two of them: one for the |
mjr | 100:1ff35c07217c | 54 | // IR transmitter (which uses a TPM channel to generate the 41-48 kHz carrier |
mjr | 100:1ff35c07217c | 55 | // wave used by nearly all consumer IR remotes), and one for the TLC5940 |
mjr | 100:1ff35c07217c | 56 | // driver (which uses it to generate the grayscale clock signal). Note that |
mjr | 100:1ff35c07217c | 57 | // we also use PWM channels for feedback device output ports, but those don't |
mjr | 100:1ff35c07217c | 58 | // have any dependency on the TPM period - they'll work with whatever period |
mjr | 100:1ff35c07217c | 59 | // the underlying TPM is set to use. So the feedback output ports can all |
mjr | 100:1ff35c07217c | 60 | // happily use free channels on TPM units claimed by any of the dedicated |
mjr | 100:1ff35c07217c | 61 | // users (IR, TLC5940, and us). |
mjr | 100:1ff35c07217c | 62 | // |
mjr | 100:1ff35c07217c | 63 | // But what do we do about the 2:1 ratio between master clock pulses and ADC |
mjr | 100:1ff35c07217c | 64 | // samples? The "right" way would be to allocate a second TPM unit to |
mjr | 100:1ff35c07217c | 65 | // generate a second clock signal at half the frequency of the master clock, |
mjr | 100:1ff35c07217c | 66 | // and use that as the ADC trigger. But as we just said, we only have three |
mjr | 100:1ff35c07217c | 67 | // TPM units in the whole system, and two of them are already claimed for |
mjr | 100:1ff35c07217c | 68 | // other uses, so we only have one unit to use here. |
mjr | 100:1ff35c07217c | 69 | // |
mjr | 100:1ff35c07217c | 70 | // Fortunately, we can make do with one TPM unit, by taking advantage of a |
mjr | 100:1ff35c07217c | 71 | // feature/quirk of the KL25Z ADC. The quirk lets us take ADC samples at |
mjr | 100:1ff35c07217c | 72 | // exactly half of the master clock rate, in perfect sync. The trick is to |
mjr | 100:1ff35c07217c | 73 | // pick a combination of master clock rate and ADC sample mode such that the |
mjr | 100:1ff35c07217c | 74 | // ADC conversion time is *almost but not quite* twice as long as the master |
mjr | 100:1ff35c07217c | 75 | // clock rate. With that combination of timings, we can trigger the ADC |
mjr | 100:1ff35c07217c | 76 | // from the TPM, and we'll get an ADC sample on exactly every other tick of |
mjr | 100:1ff35c07217c | 77 | // the master clock. The reason this works is that the KL25Z ADC ignores |
mjr | 100:1ff35c07217c | 78 | // hardware triggers (the TPM trigger is a hardware trigger) that occur when |
mjr | 100:1ff35c07217c | 79 | // a conversion is already in progress. So if the ADC sampling time is more |
mjr | 100:1ff35c07217c | 80 | // than one master clock period, the ADC will always be busy one clock tick |
mjr | 100:1ff35c07217c | 81 | // after a sample starts, so it'll ignore that first clock tick. But as |
mjr | 100:1ff35c07217c | 82 | // long as the sampling time is less than *two* master clock periods, the |
mjr | 100:1ff35c07217c | 83 | // ADC will always be ready again on the second tick. So we'll get one ADC |
mjr | 100:1ff35c07217c | 84 | // sample for every two master clock ticks, exactly as we need. |
mjr | 100:1ff35c07217c | 85 | // |
mjr | 100:1ff35c07217c | 86 | |
mjr | 100:1ff35c07217c | 87 | #include "config.h" |
mjr | 100:1ff35c07217c | 88 | #include "NewPwm.h" |
mjr | 100:1ff35c07217c | 89 | #include "AltAnalogIn.h" |
mjr | 100:1ff35c07217c | 90 | #include "SimpleDMA.h" |
mjr | 100:1ff35c07217c | 91 | #include "DMAChannels.h" |
mjr | 100:1ff35c07217c | 92 | |
mjr | 100:1ff35c07217c | 93 | |
mjr | 100:1ff35c07217c | 94 | // Logic Gate Inverters: if invertedLogicGates is true, it means that the |
mjr | 100:1ff35c07217c | 95 | // hardware is buffering all of the logic signals (fM, ICG, SH) through an |
mjr | 100:1ff35c07217c | 96 | // inverter. The data sheet recommends using a 74HC04 between the host MCU |
mjr | 100:1ff35c07217c | 97 | // and the chip logic gates because of the high capacitive load on some of |
mjr | 100:1ff35c07217c | 98 | // the gates (particularly SH, 150pF). |
mjr | 100:1ff35c07217c | 99 | |
mjr | 100:1ff35c07217c | 100 | template<bool invertedLogicGates> class TCD1103 |
mjr | 100:1ff35c07217c | 101 | { |
mjr | 100:1ff35c07217c | 102 | public: |
mjr | 100:1ff35c07217c | 103 | TCD1103(PinName fmPin, PinName osPin, PinName icgPin, PinName shPin) : |
mjr | 100:1ff35c07217c | 104 | fm(fmPin, invertedLogicGates), |
mjr | 100:1ff35c07217c | 105 | os(osPin, false, 6, 1), // single sample, 6-cycle long sampling mode, no averaging |
mjr | 100:1ff35c07217c | 106 | icg(icgPin), |
mjr | 100:1ff35c07217c | 107 | sh(shPin), |
mjr | 100:1ff35c07217c | 108 | os_dma(DMAch_TDC_ADC) |
mjr | 100:1ff35c07217c | 109 | { |
mjr | 100:1ff35c07217c | 110 | // Calibrate the ADC for best accuracy |
mjr | 100:1ff35c07217c | 111 | os.calibrate(); |
mjr | 100:1ff35c07217c | 112 | |
mjr | 100:1ff35c07217c | 113 | // Idle conditions: SH low, ICG high. |
mjr | 100:1ff35c07217c | 114 | sh = logicLow; |
mjr | 100:1ff35c07217c | 115 | icg = logicHigh; |
mjr | 100:1ff35c07217c | 116 | |
mjr | 100:1ff35c07217c | 117 | // ADC sample conversion time. This must be calculated based on the |
mjr | 100:1ff35c07217c | 118 | // combination of parameters selected for the os() initializer above. |
mjr | 100:1ff35c07217c | 119 | // See the KL25 Sub-Family Reference Manual, section 28.4.45, for the |
mjr | 100:1ff35c07217c | 120 | // formula. |
mjr | 100:1ff35c07217c | 121 | const float ADC_TIME = 2.2083333e-6f; // 6-cycle long sampling, no averaging |
mjr | 100:1ff35c07217c | 122 | |
mjr | 100:1ff35c07217c | 123 | // Set the TPM cycle time to satisfy our timing constraints: |
mjr | 100:1ff35c07217c | 124 | // |
mjr | 100:1ff35c07217c | 125 | // Tm + epsilon1 < A < 2*Tm - epsilon2 |
mjr | 100:1ff35c07217c | 126 | // |
mjr | 100:1ff35c07217c | 127 | // where A is the ADC conversion time and Tm is the master clock |
mjr | 100:1ff35c07217c | 128 | // period, and the epsilons are a margin of safety for any |
mjr | 100:1ff35c07217c | 129 | // non-deterministic component to the timing of A and Tm. The |
mjr | 100:1ff35c07217c | 130 | // epsilons could be zero if the timing of the ADC is perfectly |
mjr | 100:1ff35c07217c | 131 | // deterministic; this must be determined empirically. |
mjr | 100:1ff35c07217c | 132 | // |
mjr | 100:1ff35c07217c | 133 | // The most conservative solution would be to make epsilon as large |
mjr | 100:1ff35c07217c | 134 | // as possible, which means bisecting the time window by making |
mjr | 100:1ff35c07217c | 135 | // A = 1.5*T, or, equivalently, T = A/1.5 (the latter form being more |
mjr | 100:1ff35c07217c | 136 | // useful because T is the free variable here, as we can only control |
mjr | 100:1ff35c07217c | 137 | // A to the extent that we can choose the ADC parameters). |
mjr | 100:1ff35c07217c | 138 | // |
mjr | 100:1ff35c07217c | 139 | // But we'd also like to make T as short as possible while maintaining |
mjr | 100:1ff35c07217c | 140 | // reliable operation. Shorter T yields a higher frame rate, and we |
mjr | 100:1ff35c07217c | 141 | // want the frame rate to be as high as possible so that we can track |
mjr | 100:1ff35c07217c | 142 | // fast plunger motion accurately. Empirically, we can get reliable |
mjr | 100:1ff35c07217c | 143 | // results by using half of the ADC time plus a small buffer time. |
mjr | 100:1ff35c07217c | 144 | // |
mjr | 100:1ff35c07217c | 145 | fm.getUnit()->period(masterClockPeriod = ADC_TIME/2 + 0.1e-6f); |
mjr | 100:1ff35c07217c | 146 | printf("TCD1103 master clock period = %g\r\n", masterClockPeriod); |
mjr | 100:1ff35c07217c | 147 | |
mjr | 100:1ff35c07217c | 148 | // Start the master clock running with a 50% duty cycle |
mjr | 100:1ff35c07217c | 149 | fm.write(0.5f); |
mjr | 100:1ff35c07217c | 150 | |
mjr | 100:1ff35c07217c | 151 | // allocate our double pixel buffers |
mjr | 100:1ff35c07217c | 152 | pix1 = new uint8_t[nPixSensor*2]; |
mjr | 100:1ff35c07217c | 153 | pix2 = pix1 + nPixSensor; |
mjr | 100:1ff35c07217c | 154 | |
mjr | 100:1ff35c07217c | 155 | // put the first DMA transfer into the first buffer (pix1) |
mjr | 101:755f44622abc | 156 | tIntMin = 0; |
mjr | 100:1ff35c07217c | 157 | pixDMA = 0; |
mjr | 101:755f44622abc | 158 | clientOwnsStablePix = false; |
mjr | 100:1ff35c07217c | 159 | |
mjr | 100:1ff35c07217c | 160 | // start the sample timer with an arbitrary epoch of "now" |
mjr | 100:1ff35c07217c | 161 | t.start(); |
mjr | 100:1ff35c07217c | 162 | |
mjr | 100:1ff35c07217c | 163 | // Set up the ADC transfer DMA channel. This channel transfers |
mjr | 100:1ff35c07217c | 164 | // the current analog sampling result from the ADC output register |
mjr | 100:1ff35c07217c | 165 | // to our pixel array. |
mjr | 100:1ff35c07217c | 166 | os.initDMA(&os_dma); |
mjr | 100:1ff35c07217c | 167 | |
mjr | 100:1ff35c07217c | 168 | // Register an interrupt callback so that we're notified when |
mjr | 100:1ff35c07217c | 169 | // the last ADC transfer completes. |
mjr | 100:1ff35c07217c | 170 | os_dma.attach(this, &TCD1103::transferDone); |
mjr | 100:1ff35c07217c | 171 | |
mjr | 100:1ff35c07217c | 172 | // Set up the ADC to trigger on the master clock's TPM channel |
mjr | 100:1ff35c07217c | 173 | os.setTriggerTPM(fm.getUnitNum()); |
mjr | 100:1ff35c07217c | 174 | |
mjr | 100:1ff35c07217c | 175 | // clear the timing statistics |
mjr | 100:1ff35c07217c | 176 | totalXferTime = 0.0; |
mjr | 100:1ff35c07217c | 177 | maxXferTime = 0; |
mjr | 100:1ff35c07217c | 178 | minXferTime = 0xffffffff; |
mjr | 100:1ff35c07217c | 179 | nRuns = 0; |
mjr | 100:1ff35c07217c | 180 | |
mjr | 100:1ff35c07217c | 181 | // clear random power-up data by clocking through all pixels twice |
mjr | 100:1ff35c07217c | 182 | clear(); |
mjr | 100:1ff35c07217c | 183 | clear(); |
mjr | 101:755f44622abc | 184 | |
mjr | 101:755f44622abc | 185 | // start the first transfer |
mjr | 101:755f44622abc | 186 | startTransfer(); |
mjr | 100:1ff35c07217c | 187 | } |
mjr | 100:1ff35c07217c | 188 | |
mjr | 100:1ff35c07217c | 189 | // logic gate levels, based on whether or not the logic gate connections |
mjr | 100:1ff35c07217c | 190 | // in the hardware are buffered through inverters |
mjr | 100:1ff35c07217c | 191 | static const int logicLow = invertedLogicGates ? 1 : 0; |
mjr | 100:1ff35c07217c | 192 | static const bool logicHigh = invertedLogicGates ? 0 : 1; |
mjr | 100:1ff35c07217c | 193 | |
mjr | 100:1ff35c07217c | 194 | // ready to read |
mjr | 101:755f44622abc | 195 | bool ready() { return clientOwnsStablePix; } |
mjr | 100:1ff35c07217c | 196 | |
mjr | 101:755f44622abc | 197 | // wait for the DMA subsystem to release a buffer to the client |
mjr | 101:755f44622abc | 198 | void wait() { while (!clientOwnsStablePix) ; } |
mjr | 100:1ff35c07217c | 199 | |
mjr | 100:1ff35c07217c | 200 | // Get the stable pixel array. This is the image array from the |
mjr | 100:1ff35c07217c | 201 | // previous capture. It remains valid until the next startCapture() |
mjr | 100:1ff35c07217c | 202 | // call, at which point this buffer will be reused for the new capture. |
mjr | 100:1ff35c07217c | 203 | void getPix(uint8_t * &pix, uint32_t &t) |
mjr | 100:1ff35c07217c | 204 | { |
mjr | 100:1ff35c07217c | 205 | // return the pixel array that ISN'T assigned to the DMA |
mjr | 100:1ff35c07217c | 206 | if (pixDMA) |
mjr | 100:1ff35c07217c | 207 | { |
mjr | 100:1ff35c07217c | 208 | // DMA owns pix2, so the stable array is pix1 |
mjr | 100:1ff35c07217c | 209 | pix = pix1; |
mjr | 100:1ff35c07217c | 210 | t = t1; |
mjr | 100:1ff35c07217c | 211 | } |
mjr | 100:1ff35c07217c | 212 | else |
mjr | 100:1ff35c07217c | 213 | { |
mjr | 100:1ff35c07217c | 214 | // DMA owns pix1, so the stable array is pix2 |
mjr | 100:1ff35c07217c | 215 | pix = pix2; |
mjr | 100:1ff35c07217c | 216 | t = t2; |
mjr | 100:1ff35c07217c | 217 | } |
mjr | 100:1ff35c07217c | 218 | |
mjr | 100:1ff35c07217c | 219 | // debugging - print out the pixel transfer time stats periodically |
mjr | 100:1ff35c07217c | 220 | static int n; |
mjr | 100:1ff35c07217c | 221 | ++n; |
mjr | 100:1ff35c07217c | 222 | if (n > 1000) |
mjr | 100:1ff35c07217c | 223 | { |
mjr | 100:1ff35c07217c | 224 | printf("TCD1103 scan last=%d, min=%d, max=%d (us)\r\n", dtPixXfer, minXferTime, maxXferTime); |
mjr | 100:1ff35c07217c | 225 | n = 0; |
mjr | 100:1ff35c07217c | 226 | } |
mjr | 100:1ff35c07217c | 227 | } |
mjr | 100:1ff35c07217c | 228 | |
mjr | 101:755f44622abc | 229 | // release the client's pixel buffer |
mjr | 101:755f44622abc | 230 | void releasePix() { clientOwnsStablePix = false; } |
mjr | 101:755f44622abc | 231 | |
mjr | 101:755f44622abc | 232 | // figure the average scan time from the running totals |
mjr | 101:755f44622abc | 233 | uint32_t getAvgScanTime() { return static_cast<uint32_t>(totalXferTime / nRuns);} |
mjr | 101:755f44622abc | 234 | |
mjr | 101:755f44622abc | 235 | // Set the requested minimum integration time. If this is less than the |
mjr | 101:755f44622abc | 236 | // sensor's physical minimum time, the physical minimum applies. |
mjr | 101:755f44622abc | 237 | virtual void setMinIntTime(uint32_t us) |
mjr | 100:1ff35c07217c | 238 | { |
mjr | 101:755f44622abc | 239 | tIntMin = us; |
mjr | 101:755f44622abc | 240 | } |
mjr | 101:755f44622abc | 241 | |
mjr | 101:755f44622abc | 242 | protected: |
mjr | 101:755f44622abc | 243 | // clear the sensor pixels |
mjr | 101:755f44622abc | 244 | void clear() |
mjr | 101:755f44622abc | 245 | { |
mjr | 101:755f44622abc | 246 | // send an SH/ICG pulse sequence to start an integration cycle |
mjr | 101:755f44622abc | 247 | // (without initiating a DMA transfer, as we just want to discard |
mjr | 101:755f44622abc | 248 | // the incoming samples for a "clear") |
mjr | 101:755f44622abc | 249 | tInt = gen_SH_ICG_pulse(false); |
mjr | 100:1ff35c07217c | 250 | |
mjr | 101:755f44622abc | 251 | // wait for one full readout cycle, plus a little extra for padding |
mjr | 101:755f44622abc | 252 | ::wait(nPixSensor*masterClockPeriod*2 + 4.0e-6f); |
mjr | 101:755f44622abc | 253 | } |
mjr | 101:755f44622abc | 254 | |
mjr | 100:1ff35c07217c | 255 | // Start an image capture from the sensor. Waits the previous |
mjr | 100:1ff35c07217c | 256 | // capture to finish if it's still running, then starts a new one |
mjr | 100:1ff35c07217c | 257 | // and returns immediately. The new capture proceeds autonomously |
mjr | 100:1ff35c07217c | 258 | // via the DMA hardware, so the caller can continue with other |
mjr | 100:1ff35c07217c | 259 | // processing during the capture. |
mjr | 101:755f44622abc | 260 | void startTransfer() |
mjr | 100:1ff35c07217c | 261 | { |
mjr | 101:755f44622abc | 262 | // if we own the stable buffer, swap buffers |
mjr | 101:755f44622abc | 263 | if (!clientOwnsStablePix) |
mjr | 100:1ff35c07217c | 264 | { |
mjr | 101:755f44622abc | 265 | // swap buffers |
mjr | 101:755f44622abc | 266 | pixDMA ^= 1; |
mjr | 101:755f44622abc | 267 | |
mjr | 101:755f44622abc | 268 | // release the prior DMA buffer to the client |
mjr | 101:755f44622abc | 269 | clientOwnsStablePix = true; |
mjr | 100:1ff35c07217c | 270 | } |
mjr | 100:1ff35c07217c | 271 | |
mjr | 100:1ff35c07217c | 272 | // Set up the active pixel array as the destination buffer for |
mjr | 100:1ff35c07217c | 273 | // the ADC DMA channel. |
mjr | 100:1ff35c07217c | 274 | os_dma.destination(pixDMA ? pix2 : pix1, true); |
mjr | 100:1ff35c07217c | 275 | |
mjr | 100:1ff35c07217c | 276 | // Start the read cycle by sending the ICG/SH pulse sequence |
mjr | 100:1ff35c07217c | 277 | uint32_t tNewInt = gen_SH_ICG_pulse(true); |
mjr | 100:1ff35c07217c | 278 | |
mjr | 100:1ff35c07217c | 279 | // Set the timestamp for the current active buffer. The ICG/SH |
mjr | 100:1ff35c07217c | 280 | // gymnastics we just did transferred the CCD pixels into the sensor's |
mjr | 100:1ff35c07217c | 281 | // internal shift register and reset the pixels, starting a new |
mjr | 100:1ff35c07217c | 282 | // integration cycle. So the pixels we just shifted started |
mjr | 100:1ff35c07217c | 283 | // integrating the *last* time we did that, which we recorded as |
mjr | 100:1ff35c07217c | 284 | // tInt at the time. The image we're about to transfer therefore |
mjr | 100:1ff35c07217c | 285 | // represents the light collected between tInt and the SH pulse we |
mjr | 100:1ff35c07217c | 286 | // just did. The image covers a time range rather than a single |
mjr | 100:1ff35c07217c | 287 | // point in time, but we still have to give it a single timestamp. |
mjr | 100:1ff35c07217c | 288 | // Use the midpoint of the integration period. |
mjr | 100:1ff35c07217c | 289 | uint32_t tmid = (tNewInt + tInt) >> 1; |
mjr | 100:1ff35c07217c | 290 | if (pixDMA) |
mjr | 100:1ff35c07217c | 291 | t2 = tmid; |
mjr | 100:1ff35c07217c | 292 | else |
mjr | 100:1ff35c07217c | 293 | t1 = tmid; |
mjr | 100:1ff35c07217c | 294 | |
mjr | 100:1ff35c07217c | 295 | // Record the start time of the currently active integration period |
mjr | 100:1ff35c07217c | 296 | tInt = tNewInt; |
mjr | 100:1ff35c07217c | 297 | } |
mjr | 100:1ff35c07217c | 298 | |
mjr | 101:755f44622abc | 299 | // End of transfer notification. This runs as an interrupt handler when |
mjr | 101:755f44622abc | 300 | // the DMA transfer completes. |
mjr | 101:755f44622abc | 301 | void transferDone() |
mjr | 100:1ff35c07217c | 302 | { |
mjr | 101:755f44622abc | 303 | // add this sample to the timing statistics (for diagnostics and |
mjr | 101:755f44622abc | 304 | // performance measurement) |
mjr | 101:755f44622abc | 305 | uint32_t now = t.read_us(); |
mjr | 101:755f44622abc | 306 | uint32_t dt = dtPixXfer = static_cast<uint32_t>(now - tXfer); |
mjr | 101:755f44622abc | 307 | totalXferTime += dt; |
mjr | 101:755f44622abc | 308 | nRuns += 1; |
mjr | 101:755f44622abc | 309 | |
mjr | 101:755f44622abc | 310 | // collect debug statistics |
mjr | 101:755f44622abc | 311 | if (dt < minXferTime) minXferTime = dt; |
mjr | 101:755f44622abc | 312 | if (dt > maxXferTime) maxXferTime = dt; |
mjr | 100:1ff35c07217c | 313 | |
mjr | 101:755f44622abc | 314 | // check if there's still time left before we reach the minimum |
mjr | 101:755f44622abc | 315 | // requested integration period |
mjr | 101:755f44622abc | 316 | uint32_t dtInt = now - tInt; |
mjr | 101:755f44622abc | 317 | if (tIntMin > dtInt) |
mjr | 101:755f44622abc | 318 | { |
mjr | 101:755f44622abc | 319 | // wait for the remaining interval before starting the next |
mjr | 101:755f44622abc | 320 | // integration |
mjr | 101:755f44622abc | 321 | integrationTimeout.attach(this, &TCD1103::startTransfer, tInt - dtInt); |
mjr | 101:755f44622abc | 322 | } |
mjr | 101:755f44622abc | 323 | else |
mjr | 101:755f44622abc | 324 | { |
mjr | 101:755f44622abc | 325 | // we've already reached the minimum integration time - start |
mjr | 101:755f44622abc | 326 | // the next transfer immediately |
mjr | 101:755f44622abc | 327 | startTransfer(); |
mjr | 101:755f44622abc | 328 | } |
mjr | 100:1ff35c07217c | 329 | } |
mjr | 100:1ff35c07217c | 330 | |
mjr | 100:1ff35c07217c | 331 | // Generate an SH/ICG pulse. This transfers the pixel data from the live |
mjr | 100:1ff35c07217c | 332 | // sensor photoreceptors into the sensor's internal shift register, clears |
mjr | 100:1ff35c07217c | 333 | // the live pixels, and starts a new integration cycle. |
mjr | 100:1ff35c07217c | 334 | // |
mjr | 100:1ff35c07217c | 335 | // If start_dma_xfer is true, we'll start the DMA transfer for the ADC |
mjr | 100:1ff35c07217c | 336 | // pixel data. We handle this here because the sensor starts clocking |
mjr | 100:1ff35c07217c | 337 | // out pixels precisely at the end of the ICG pulse, so we have to be |
mjr | 100:1ff35c07217c | 338 | // be very careful about the timing. |
mjr | 100:1ff35c07217c | 339 | // |
mjr | 100:1ff35c07217c | 340 | // Returns the timestamp (relative to our image timer 't') of the end |
mjr | 100:1ff35c07217c | 341 | // of the SH pulse, which is the moment the new integration cycle starts. |
mjr | 100:1ff35c07217c | 342 | // |
mjr | 100:1ff35c07217c | 343 | // Note that we send these pulses synchronously - that is, this routine |
mjr | 100:1ff35c07217c | 344 | // blocks until the pulses have been sent. The overall sequence takes |
mjr | 100:1ff35c07217c | 345 | // about 2.5us to 3us, so it's not a significant interruption of the |
mjr | 100:1ff35c07217c | 346 | // main loop. |
mjr | 100:1ff35c07217c | 347 | // |
mjr | 100:1ff35c07217c | 348 | uint32_t gen_SH_ICG_pulse(bool start_dma_xfer) |
mjr | 100:1ff35c07217c | 349 | { |
mjr | 100:1ff35c07217c | 350 | // If desired, prepare to start the DMA transfer for the ADC data. |
mjr | 100:1ff35c07217c | 351 | // (Set up a dummy location to write in lieu of the DMA register if |
mjr | 100:1ff35c07217c | 352 | // DMA initiation isn't required, so that we don't have to take the |
mjr | 100:1ff35c07217c | 353 | // time for a conditional when we're ready to start the DMA transfer. |
mjr | 100:1ff35c07217c | 354 | // The timing there will be extremely tight, and we can't afford the |
mjr | 100:1ff35c07217c | 355 | // extra instructions to test a condition.) |
mjr | 100:1ff35c07217c | 356 | uint8_t dma_chcfg_dummy = 0; |
mjr | 100:1ff35c07217c | 357 | volatile uint8_t *dma_chcfg = start_dma_xfer ? os_dma.prepare(nPixSensor, true) : &dma_chcfg_dummy; |
mjr | 100:1ff35c07217c | 358 | |
mjr | 100:1ff35c07217c | 359 | // The basic idea is to take ICG low, and while holding ICG low, |
mjr | 100:1ff35c07217c | 360 | // pulse SH. The coincidence of the two pulses transfers the charge |
mjr | 100:1ff35c07217c | 361 | // from the live pixels into the shift register, which effectively |
mjr | 100:1ff35c07217c | 362 | // discharges the live pixels and thereby starts a new integration |
mjr | 100:1ff35c07217c | 363 | // cycle. |
mjr | 100:1ff35c07217c | 364 | // |
mjr | 100:1ff35c07217c | 365 | // The timing of the pulse sequence is rather tightly constrained |
mjr | 100:1ff35c07217c | 366 | // per the data sheet, so we have to take some care in executing it: |
mjr | 100:1ff35c07217c | 367 | // |
mjr | 100:1ff35c07217c | 368 | // ICG -> LOW |
mjr | 100:1ff35c07217c | 369 | // 100-1000 ns delay (*) |
mjr | 100:1ff35c07217c | 370 | // SH -> HIGH |
mjr | 100:1ff35c07217c | 371 | // >1000ns delay |
mjr | 100:1ff35c07217c | 372 | // SH -> LOW |
mjr | 100:1ff35c07217c | 373 | // >1000ns delay |
mjr | 100:1ff35c07217c | 374 | // ICG -> high (**) |
mjr | 100:1ff35c07217c | 375 | // |
mjr | 100:1ff35c07217c | 376 | // There are two steps here that are tricky: |
mjr | 100:1ff35c07217c | 377 | // |
mjr | 100:1ff35c07217c | 378 | // (*) is a narrow window that we can't achieve with an mbed |
mjr | 100:1ff35c07217c | 379 | // microsecond timer. Instead, we'll do a couple of extra writes |
mjr | 100:1ff35c07217c | 380 | // to the ICG register, which take about 60ns each. |
mjr | 100:1ff35c07217c | 381 | // |
mjr | 100:1ff35c07217c | 382 | // (**) has the rather severe constraint that the transition must |
mjr | 100:1ff35c07217c | 383 | // occur AND complete while the master clock is high. Other people |
mjr | 100:1ff35c07217c | 384 | // working with similar Toshiba chips in MCU projects have suggested |
mjr | 100:1ff35c07217c | 385 | // that this constraint can safely be ignored, so maybe the data |
mjr | 100:1ff35c07217c | 386 | // sheet's insistence about it is obsolete advice from past Toshiba |
mjr | 100:1ff35c07217c | 387 | // sensors that the doc writers carried forward by copy-and-paste. |
mjr | 100:1ff35c07217c | 388 | // Toshiba has been making these sorts of chips for a very long time, |
mjr | 100:1ff35c07217c | 389 | // and the data sheets for many of them are obvious copy-and-paste |
mjr | 100:1ff35c07217c | 390 | // jobs. But let's take the data sheet at its word and assume that |
mjr | 100:1ff35c07217c | 391 | // this is important for proper operation. Our best hope of |
mjr | 100:1ff35c07217c | 392 | // satisfying this constraint is to synchronize the start of the |
mjr | 100:1ff35c07217c | 393 | // ICG->high transition with the start of a TPM cycle on the master |
mjr | 100:1ff35c07217c | 394 | // clock. That guarantees that the ICG transition starts when the |
mjr | 100:1ff35c07217c | 395 | // clock signal is high (as each TPM cycle starts out high), and |
mjr | 100:1ff35c07217c | 396 | // gives us the longest possible runway for the transition to |
mjr | 100:1ff35c07217c | 397 | // complete while the clock is still high, as we get the full |
mjr | 100:1ff35c07217c | 398 | // length of the high part of the cycle to work with. To quantify, |
mjr | 100:1ff35c07217c | 399 | // it gives us about 600ns. The register write takes about 60ns, |
mjr | 100:1ff35c07217c | 400 | // and waitEndCycle() adds several instructions of overhead, perhaps |
mjr | 100:1ff35c07217c | 401 | // 200ns, so we get around 300ns for the transition to finish. That |
mjr | 100:1ff35c07217c | 402 | // should be a gracious plenty assuming that the hardware is set up |
mjr | 100:1ff35c07217c | 403 | // with an inverter to buffer the clock signals. The inverter should |
mjr | 100:1ff35c07217c | 404 | // be able to pull up the 35pF on ICG in a "typical" 30ns (rise time |
mjr | 100:1ff35c07217c | 405 | // plus propagation delay, per the 74HC04 data sheet) and max 150ns. |
mjr | 100:1ff35c07217c | 406 | // This seems to be one place where the inverter might really be |
mjr | 100:1ff35c07217c | 407 | // necessary to meet the timing requirements, as the KL25Z GPIO |
mjr | 100:1ff35c07217c | 408 | // might need more like 2us to pull that load up. |
mjr | 100:1ff35c07217c | 409 | // |
mjr | 100:1ff35c07217c | 410 | // There's an additional constraint on the timing at the end of the |
mjr | 100:1ff35c07217c | 411 | // ICG pulse. The sensor starts clocking out pixels on the rising |
mjr | 100:1ff35c07217c | 412 | // edge of the ICG pulse. So we need the ICG pulse end to align |
mjr | 100:1ff35c07217c | 413 | // with the start of an ADC cycle. If we get that wrong, all of our |
mjr | 100:1ff35c07217c | 414 | // ADC samples will be off by half a clock, so every sample will be |
mjr | 100:1ff35c07217c | 415 | // the average of two adjacent pixels instead of one pixel. That |
mjr | 100:1ff35c07217c | 416 | // would lose half of the image resolution, which would obviously |
mjr | 100:1ff35c07217c | 417 | // be bad. So make certain we're at the tail end of an ADC cycle |
mjr | 100:1ff35c07217c | 418 | // by waiting for the ADC "ready" bit to be set. |
mjr | 100:1ff35c07217c | 419 | // |
mjr | 100:1ff35c07217c | 420 | // The end of the SH pulse triggers the start of a new integration |
mjr | 100:1ff35c07217c | 421 | // cycle, so note the time of that pulse for image timestamping |
mjr | 100:1ff35c07217c | 422 | // purposes. That will be the start time of the NEXT image we |
mjr | 100:1ff35c07217c | 423 | // transfer after we shift out the current sensor pixels, which |
mjr | 100:1ff35c07217c | 424 | // represent the pixels from the last time we pulsed SH. |
mjr | 100:1ff35c07217c | 425 | // |
mjr | 100:1ff35c07217c | 426 | icg = logicLow; |
mjr | 100:1ff35c07217c | 427 | icg = logicLow; // for timing, adds about 60ns |
mjr | 100:1ff35c07217c | 428 | icg = logicLow; // ditto, another 60ns, total is now 120ns > min 100ns |
mjr | 100:1ff35c07217c | 429 | sh = logicHigh; |
mjr | 100:1ff35c07217c | 430 | wait_us(1); // >1000ns delay |
mjr | 100:1ff35c07217c | 431 | sh = logicLow; |
mjr | 100:1ff35c07217c | 432 | uint32_t t_sh = t.read_us(); // this is the start time of the NEXT image |
mjr | 100:1ff35c07217c | 433 | wait_us(1); // >1000ns delay |
mjr | 100:1ff35c07217c | 434 | |
mjr | 100:1ff35c07217c | 435 | // Now the tricky part! We have to end the ICG pulse (take ICG high) |
mjr | 100:1ff35c07217c | 436 | // at the start of a master clock cycle, AND at the start of an ADC |
mjr | 100:1ff35c07217c | 437 | // sampling cycle. The sensor will start clocking out pixels the |
mjr | 100:1ff35c07217c | 438 | // instance ICG goes high, so we have to align our ADC cycle so that |
mjr | 100:1ff35c07217c | 439 | // we start a sample at almost exactly the same time we take ICG |
mjr | 100:1ff35c07217c | 440 | // high. |
mjr | 100:1ff35c07217c | 441 | // |
mjr | 100:1ff35c07217c | 442 | // Now, every ADC sampling cycle always starts at a rising edge of |
mjr | 100:1ff35c07217c | 443 | // the master clock, since the master clock is the ADC trigger. BUT, |
mjr | 100:1ff35c07217c | 444 | // the converse is NOT true: every rising edge of the master clock |
mjr | 100:1ff35c07217c | 445 | // is NOT an ADC sample start. Recall that we've contrived the timing |
mjr | 100:1ff35c07217c | 446 | // so that every OTHER master clock rising edge starts an ADC sample. |
mjr | 100:1ff35c07217c | 447 | // |
mjr | 100:1ff35c07217c | 448 | // So how do we detect which part of the clock cycle we're in? We |
mjr | 100:1ff35c07217c | 449 | // could conceivably use the COCO bit in the ADC status register to |
mjr | 100:1ff35c07217c | 450 | // detect the little window between the end of one sample and the |
mjr | 100:1ff35c07217c | 451 | // start of the next. Unfortunately, this doesn't work: the COCO |
mjr | 100:1ff35c07217c | 452 | // bit is never actually set for the duration of even a single CPU |
mjr | 100:1ff35c07217c | 453 | // instruction in our setup, no matter how loose we make the timing |
mjr | 100:1ff35c07217c | 454 | // between the ADC and the fM cycle. I think the reason is the DMA |
mjr | 100:1ff35c07217c | 455 | // setup: the COCO bit triggers the DMA, and the DMA controller |
mjr | 100:1ff35c07217c | 456 | // reads the ADC result register (the DMA source in our setup), |
mjr | 100:1ff35c07217c | 457 | // which has the side effect of clearing COCO. I've experimented |
mjr | 100:1ff35c07217c | 458 | // with this using different timing parameters, and the result is |
mjr | 100:1ff35c07217c | 459 | // always the same: the CPU *never* sees the COCO bit set. The DMA |
mjr | 100:1ff35c07217c | 460 | // trigger timing is evidently deterministic such that the DMA unit |
mjr | 100:1ff35c07217c | 461 | // invariably gets its shot at reading ADC0->R before the CPU does. |
mjr | 100:1ff35c07217c | 462 | // |
mjr | 100:1ff35c07217c | 463 | // The COCO approach would be a little iffy anyway, since we want the |
mjr | 100:1ff35c07217c | 464 | // ADC idle time to be as short as possible, which wouldn't give us |
mjr | 100:1ff35c07217c | 465 | // much time to do all we have to do in the COCO period, even if |
mjr | 100:1ff35c07217c | 466 | // there were one. What we can do instead is seize control of the |
mjr | 100:1ff35c07217c | 467 | // ADC cycle timing: rather than trying to detect when the cycle |
mjr | 100:1ff35c07217c | 468 | // ends, we can specify when it begins. We can do this by canceling |
mjr | 100:1ff35c07217c | 469 | // the TPM->ADC trigger and aborting any conversion in progress, then |
mjr | 100:1ff35c07217c | 470 | // reprogramming the TPM->ADC trigger at our leisure. What we *can* |
mjr | 100:1ff35c07217c | 471 | // detect reliably is the start of a TPM cycle. So here's our |
mjr | 100:1ff35c07217c | 472 | // strategy: |
mjr | 100:1ff35c07217c | 473 | // |
mjr | 100:1ff35c07217c | 474 | // - Turn off the TPM->ADC trigger and abort the current conversion |
mjr | 100:1ff35c07217c | 475 | // - Wait until a new TPM cycle starts |
mjr | 100:1ff35c07217c | 476 | // - Reset the TPM->ADC trigger. The first new conversion will |
mjr | 100:1ff35c07217c | 477 | // start on the next TPM cycle, so we have the remainder of |
mjr | 100:1ff35c07217c | 478 | // the current TPM cycle to make this happen (about 1us, enough |
mjr | 100:1ff35c07217c | 479 | // for 16 CPU instructions - plenty for this step) |
mjr | 100:1ff35c07217c | 480 | // - Wait for the new TPM cycle |
mjr | 100:1ff35c07217c | 481 | // - End the ICG pulse |
mjr | 100:1ff35c07217c | 482 | // |
mjr | 100:1ff35c07217c | 483 | |
mjr | 100:1ff35c07217c | 484 | // The timing is so tight here that we want to be sure we're not |
mjr | 100:1ff35c07217c | 485 | // interrupted by other tasks - disable interrupts. |
mjr | 100:1ff35c07217c | 486 | __disable_irq(); |
mjr | 100:1ff35c07217c | 487 | |
mjr | 100:1ff35c07217c | 488 | // disable the TPM->ADC trigger and abort the current conversion |
mjr | 100:1ff35c07217c | 489 | os.stop(); |
mjr | 100:1ff35c07217c | 490 | |
mjr | 100:1ff35c07217c | 491 | // Enable the DMA controller for the new transfer from the ADC. |
mjr | 100:1ff35c07217c | 492 | // The sensor will start clocking out new samples at the ICG rising |
mjr | 100:1ff35c07217c | 493 | // edge, so the next ADC sample to complete will represent the first |
mjr | 100:1ff35c07217c | 494 | // pixel in the new frame. So we need the DMA ready to go at the |
mjr | 100:1ff35c07217c | 495 | // very next sample. Recall that the DMA is triggered by ADC |
mjr | 100:1ff35c07217c | 496 | // completion, and ADC is stopped right now, so enabling the DMA |
mjr | 100:1ff35c07217c | 497 | // won't have any immediate effect - it just spools it up so that |
mjr | 100:1ff35c07217c | 498 | // it's ready to move samples as soon as we resume the ADC. |
mjr | 100:1ff35c07217c | 499 | *dma_chcfg |= DMAMUX_CHCFG_ENBL_MASK; |
mjr | 100:1ff35c07217c | 500 | |
mjr | 100:1ff35c07217c | 501 | // wait for the start of a new master clock cycle |
mjr | 100:1ff35c07217c | 502 | fm.waitEndCycle(); |
mjr | 100:1ff35c07217c | 503 | |
mjr | 100:1ff35c07217c | 504 | // Okay, a master clock cycle just started, so we have about 1us |
mjr | 100:1ff35c07217c | 505 | // (about 16 CPU instructions) before the next one begins. Resume |
mjr | 100:1ff35c07217c | 506 | // ADC sampling. The first new sample will start with the next |
mjr | 100:1ff35c07217c | 507 | // TPM cycle 1us from now. (This step takes about 3 instructions.) |
mjr | 100:1ff35c07217c | 508 | os.resume(); |
mjr | 100:1ff35c07217c | 509 | |
mjr | 100:1ff35c07217c | 510 | // Okay, everything is queued up! We just have to fire the starting |
mjr | 100:1ff35c07217c | 511 | // pistol on the sensor at the right moment. And that right moment |
mjr | 100:1ff35c07217c | 512 | // is the start of the next TPM cycle. Wait for it... |
mjr | 100:1ff35c07217c | 513 | fm.waitEndCycle(); |
mjr | 100:1ff35c07217c | 514 | |
mjr | 100:1ff35c07217c | 515 | // And go! |
mjr | 100:1ff35c07217c | 516 | icg = logicHigh; |
mjr | 100:1ff35c07217c | 517 | |
mjr | 100:1ff35c07217c | 518 | // note the start time of the transfer |
mjr | 100:1ff35c07217c | 519 | tXfer = t.read_us(); |
mjr | 100:1ff35c07217c | 520 | |
mjr | 100:1ff35c07217c | 521 | // done with the critical timing section |
mjr | 100:1ff35c07217c | 522 | __enable_irq(); |
mjr | 100:1ff35c07217c | 523 | |
mjr | 100:1ff35c07217c | 524 | // return the timestamp of the end of the SH pulse - this is the start |
mjr | 100:1ff35c07217c | 525 | // of the new integration period that we just initiated |
mjr | 100:1ff35c07217c | 526 | return t_sh; |
mjr | 100:1ff35c07217c | 527 | } |
mjr | 100:1ff35c07217c | 528 | |
mjr | 100:1ff35c07217c | 529 | // master clock |
mjr | 100:1ff35c07217c | 530 | NewPwmOut fm; |
mjr | 100:1ff35c07217c | 531 | |
mjr | 100:1ff35c07217c | 532 | // analog input for reading the pixel voltage level |
mjr | 100:1ff35c07217c | 533 | AltAnalogIn_8bit os; |
mjr | 100:1ff35c07217c | 534 | |
mjr | 100:1ff35c07217c | 535 | // Integration Clear Gate output |
mjr | 100:1ff35c07217c | 536 | DigitalOut icg; |
mjr | 100:1ff35c07217c | 537 | |
mjr | 100:1ff35c07217c | 538 | // Shift Gate output |
mjr | 100:1ff35c07217c | 539 | DigitalOut sh; |
mjr | 100:1ff35c07217c | 540 | |
mjr | 100:1ff35c07217c | 541 | // DMA channel for the analog input |
mjr | 100:1ff35c07217c | 542 | SimpleDMA os_dma; |
mjr | 100:1ff35c07217c | 543 | |
mjr | 100:1ff35c07217c | 544 | // Master clock period, in seconds, calculated based on the ADC timing |
mjr | 100:1ff35c07217c | 545 | float masterClockPeriod; |
mjr | 100:1ff35c07217c | 546 | |
mjr | 100:1ff35c07217c | 547 | // Number of pixels. The TCD1103 has 1500 image pixels, plus 32 dummy |
mjr | 100:1ff35c07217c | 548 | // pixels at the front end (before the first image pixel) and another 14 |
mjr | 100:1ff35c07217c | 549 | // dummy pixels at the back end. The sensor always transfers the full |
mjr | 100:1ff35c07217c | 550 | // file on each read cycle, including the dummies, so we have to make |
mjr | 100:1ff35c07217c | 551 | // room for the dummy pixels during each read. |
mjr | 100:1ff35c07217c | 552 | static const int nPixSensor = 1546; |
mjr | 100:1ff35c07217c | 553 | |
mjr | 100:1ff35c07217c | 554 | // pixel buffers - we keep two buffers so that we can transfer the |
mjr | 100:1ff35c07217c | 555 | // current sensor data into one buffer via DMA while we concurrently |
mjr | 100:1ff35c07217c | 556 | // process the last buffer |
mjr | 100:1ff35c07217c | 557 | uint8_t *pix1; // pixel array 1 |
mjr | 100:1ff35c07217c | 558 | uint8_t *pix2; // pixel array 2 |
mjr | 100:1ff35c07217c | 559 | |
mjr | 100:1ff35c07217c | 560 | // Timestamps of pix1 and pix2 arrays, in microseconds, in terms of the |
mjr | 100:1ff35c07217c | 561 | // sample timer (this->t). |
mjr | 100:1ff35c07217c | 562 | uint32_t t1; |
mjr | 100:1ff35c07217c | 563 | uint32_t t2; |
mjr | 100:1ff35c07217c | 564 | |
mjr | 100:1ff35c07217c | 565 | // DMA target buffer. This is the buffer for the next DMA transfer. |
mjr | 100:1ff35c07217c | 566 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
mjr | 100:1ff35c07217c | 567 | // data from the last transfer. |
mjr | 100:1ff35c07217c | 568 | uint8_t pixDMA; |
mjr | 100:1ff35c07217c | 569 | |
mjr | 101:755f44622abc | 570 | // Stable buffer ownership. At any given time, the DMA subsystem owns |
mjr | 101:755f44622abc | 571 | // the buffer specified by pixDMA. The other buffer - the "stable" buffer, |
mjr | 101:755f44622abc | 572 | // which contains the most recent completed frame, can be owned by EITHER |
mjr | 101:755f44622abc | 573 | // the client or by the DMA subsystem. Each time a DMA transfer completes, |
mjr | 101:755f44622abc | 574 | // the DMA subsystem looks at the stable buffer owner flag to determine |
mjr | 101:755f44622abc | 575 | // what to do: |
mjr | 101:755f44622abc | 576 | // |
mjr | 101:755f44622abc | 577 | // - If the DMA subsystem owns the stable buffer, it swaps buffers. This |
mjr | 101:755f44622abc | 578 | // makes the newly completed DMA buffer the new stable buffer, and makes |
mjr | 101:755f44622abc | 579 | // the old stable buffer the new DMA buffer. At this time, the DMA |
mjr | 101:755f44622abc | 580 | // subsystem also changes the stable buffer ownership to CLIENT. |
mjr | 101:755f44622abc | 581 | // |
mjr | 101:755f44622abc | 582 | // - If the CLIENT owns the stable buffer, the DMA subsystem can't swap |
mjr | 101:755f44622abc | 583 | // buffers, because the client is still using the stable buffer. It |
mjr | 101:755f44622abc | 584 | // simply leaves things as they are. |
mjr | 101:755f44622abc | 585 | // |
mjr | 101:755f44622abc | 586 | // In either case, the DMA system starts a new transfer at this point. |
mjr | 101:755f44622abc | 587 | // |
mjr | 101:755f44622abc | 588 | // The client, meanwhile, is free to access the stable buffer when it has |
mjr | 101:755f44622abc | 589 | // ownership. If the client *doesn't* have ownership, it must wait for |
mjr | 101:755f44622abc | 590 | // the ownership to be transferred, which can only be done by the DMA |
mjr | 101:755f44622abc | 591 | // subsystem on completing a transfer. |
mjr | 101:755f44622abc | 592 | // |
mjr | 101:755f44622abc | 593 | // When the client is done with the stable buffer, it transfers ownership |
mjr | 101:755f44622abc | 594 | // back to the DMA subsystem. |
mjr | 101:755f44622abc | 595 | // |
mjr | 101:755f44622abc | 596 | // Transfers of ownership from DMA to CLIENT are done only by DMA. |
mjr | 101:755f44622abc | 597 | // Transfers from CLIENT to DMA are done only by CLIENT. So whoever has |
mjr | 101:755f44622abc | 598 | // ownership now is responsible for transferring ownership. |
mjr | 101:755f44622abc | 599 | // |
mjr | 101:755f44622abc | 600 | volatile bool clientOwnsStablePix; |
mjr | 101:755f44622abc | 601 | |
mjr | 101:755f44622abc | 602 | // Minimum requested integration time, in microseconds |
mjr | 101:755f44622abc | 603 | uint32_t tIntMin; |
mjr | 101:755f44622abc | 604 | |
mjr | 101:755f44622abc | 605 | // Timeout for generating an interrupt at the end of the integration period |
mjr | 101:755f44622abc | 606 | Timeout integrationTimeout; |
mjr | 101:755f44622abc | 607 | |
mjr | 100:1ff35c07217c | 608 | // timing statistics |
mjr | 100:1ff35c07217c | 609 | Timer t; // sample timer |
mjr | 100:1ff35c07217c | 610 | uint32_t tInt; // start time (us) of current integration period |
mjr | 100:1ff35c07217c | 611 | uint32_t tXfer; // start time (us) of current pixel transfer |
mjr | 100:1ff35c07217c | 612 | uint32_t dtPixXfer; // pixel transfer time of last frame |
mjr | 100:1ff35c07217c | 613 | uint64_t totalXferTime; // total time consumed by all reads so far |
mjr | 100:1ff35c07217c | 614 | uint32_t nRuns; // number of runs so far |
mjr | 100:1ff35c07217c | 615 | |
mjr | 100:1ff35c07217c | 616 | // debugging - min/max transfer time statistics |
mjr | 100:1ff35c07217c | 617 | uint32_t minXferTime; |
mjr | 100:1ff35c07217c | 618 | uint32_t maxXferTime; |
mjr | 100:1ff35c07217c | 619 | }; |