Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
TCD1103/TCD1103.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 111:42dc75fbe623
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 104:6e06e0f4b476 | 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 | 104:6e06e0f4b476 | 10 | // The big physical difference between this sensor and the old TAOS sensors |
mjr | 104:6e06e0f4b476 | 11 | // is the size. The TAOS sensors were (by some miracle) approximately the |
mjr | 104:6e06e0f4b476 | 12 | // same size as the plunger travel range, so we were able to take "contact" |
mjr | 104:6e06e0f4b476 | 13 | // images without any optics, by placing the plunger close to the sensor, |
mjr | 104:6e06e0f4b476 | 14 | // back-lighting it, and essentially taking a picture of its shadow. The |
mjr | 104:6e06e0f4b476 | 15 | // Toshiba sensor, in contrast, has a pixel window that's only 8mm long, so |
mjr | 104:6e06e0f4b476 | 16 | // the contact image approach won't work. Instead, we have to use a lens |
mjr | 104:6e06e0f4b476 | 17 | // to focus a reduced image (about 1:10 scale) on the sensor. That makes |
mjr | 104:6e06e0f4b476 | 18 | // the physical setup more complex, but it has the great advantage that we |
mjr | 104:6e06e0f4b476 | 19 | // get a focused image. The shadow was always fuzzy in the old contact |
mjr | 104:6e06e0f4b476 | 20 | // image approach, which reduced the effective resolution when determining |
mjr | 104:6e06e0f4b476 | 21 | // the plunger position. With a focused image, we can get single-pixel |
mjr | 104:6e06e0f4b476 | 22 | // resolution. With this Toshiba sensor's 1500 pixels, that's about 500 |
mjr | 104:6e06e0f4b476 | 23 | // dpi, which beats every other sensor we've come up with. |
mjr | 100:1ff35c07217c | 24 | // |
mjr | 104:6e06e0f4b476 | 25 | // The electronic interface to this sensor is similar to the TAOS, but it |
mjr | 104:6e06e0f4b476 | 26 | // has enough differences that we can't share the same code base. |
mjr | 100:1ff35c07217c | 27 | // |
mjr | 104:6e06e0f4b476 | 28 | // As with the 1410R, we have to use DMA for the ADC transfers in order |
mjr | 100:1ff35c07217c | 29 | // to keep up with the high data rate without overloading the KL25Z CPU. |
mjr | 100:1ff35c07217c | 30 | // With the 1410R, we're able to use the ADC itself as the clock source, |
mjr | 100:1ff35c07217c | 31 | // by running the ADC in continous mode and using its "sample ready" signal |
mjr | 100:1ff35c07217c | 32 | // to trigger the DMA transfer. We used this to generate the external clock |
mjr | 100:1ff35c07217c | 33 | // signal for the sensor by "linking" the ADC's DMA channel to another pair |
mjr | 100:1ff35c07217c | 34 | // of DMA channels that generated the clock up/down signal each time an ADC |
mjr | 100:1ff35c07217c | 35 | // sample completed. This strategy won't work with the Toshiba sensor, |
mjr | 100:1ff35c07217c | 36 | // though, because the Toshiba sensor's timing sequence requires *two* clock |
mjr | 100:1ff35c07217c | 37 | // pulses per pixel. I can't come up with a way to accomplish that with the |
mjr | 104:6e06e0f4b476 | 38 | // linked-DMA approach. Instead, we'll have to generate a true clock signal |
mjr | 104:6e06e0f4b476 | 39 | // for the sensor, and drive the DMA conversions off of that clock. |
mjr | 100:1ff35c07217c | 40 | // |
mjr | 104:6e06e0f4b476 | 41 | // The obvious (and, as far as I can tell, only) way to generate the clock |
mjr | 104:6e06e0f4b476 | 42 | // signal with the KL25Z at the high frequency required is to use a TPM - |
mjr | 104:6e06e0f4b476 | 43 | // the KL25Z module that drives PWM outputs. TPM channels are designed |
mjr | 100:1ff35c07217c | 44 | // precisely for this kind of work, so this is the right approach in terms of |
mjr | 100:1ff35c07217c | 45 | // suitability, but it has the downside that TPM units are an extremely scarce |
mjr | 104:6e06e0f4b476 | 46 | // resource on the KL25Z. We only have three of them to work with. Luckily, |
mjr | 100:1ff35c07217c | 47 | // the rest of the Pinscape software only requires two of them: one for the |
mjr | 100:1ff35c07217c | 48 | // IR transmitter (which uses a TPM channel to generate the 41-48 kHz carrier |
mjr | 100:1ff35c07217c | 49 | // wave used by nearly all consumer IR remotes), and one for the TLC5940 |
mjr | 100:1ff35c07217c | 50 | // driver (which uses it to generate the grayscale clock signal). Note that |
mjr | 100:1ff35c07217c | 51 | // we also use PWM channels for feedback device output ports, but those don't |
mjr | 100:1ff35c07217c | 52 | // have any dependency on the TPM period - they'll work with whatever period |
mjr | 100:1ff35c07217c | 53 | // the underlying TPM is set to use. So the feedback output ports can all |
mjr | 100:1ff35c07217c | 54 | // happily use free channels on TPM units claimed by any of the dedicated |
mjr | 100:1ff35c07217c | 55 | // users (IR, TLC5940, and us). |
mjr | 100:1ff35c07217c | 56 | // |
mjr | 100:1ff35c07217c | 57 | // But what do we do about the 2:1 ratio between master clock pulses and ADC |
mjr | 100:1ff35c07217c | 58 | // samples? The "right" way would be to allocate a second TPM unit to |
mjr | 100:1ff35c07217c | 59 | // generate a second clock signal at half the frequency of the master clock, |
mjr | 100:1ff35c07217c | 60 | // and use that as the ADC trigger. But as we just said, we only have three |
mjr | 100:1ff35c07217c | 61 | // TPM units in the whole system, and two of them are already claimed for |
mjr | 104:6e06e0f4b476 | 62 | // other uses, so we only have one unit available for our use here. |
mjr | 100:1ff35c07217c | 63 | // |
mjr | 100:1ff35c07217c | 64 | // Fortunately, we can make do with one TPM unit, by taking advantage of a |
mjr | 100:1ff35c07217c | 65 | // feature/quirk of the KL25Z ADC. The quirk lets us take ADC samples at |
mjr | 100:1ff35c07217c | 66 | // exactly half of the master clock rate, in perfect sync. The trick is to |
mjr | 100:1ff35c07217c | 67 | // pick a combination of master clock rate and ADC sample mode such that the |
mjr | 100:1ff35c07217c | 68 | // ADC conversion time is *almost but not quite* twice as long as the master |
mjr | 100:1ff35c07217c | 69 | // clock rate. With that combination of timings, we can trigger the ADC |
mjr | 100:1ff35c07217c | 70 | // from the TPM, and we'll get an ADC sample on exactly every other tick of |
mjr | 100:1ff35c07217c | 71 | // the master clock. The reason this works is that the KL25Z ADC ignores |
mjr | 100:1ff35c07217c | 72 | // hardware triggers (the TPM trigger is a hardware trigger) that occur when |
mjr | 100:1ff35c07217c | 73 | // a conversion is already in progress. So if the ADC sampling time is more |
mjr | 100:1ff35c07217c | 74 | // than one master clock period, the ADC will always be busy one clock tick |
mjr | 100:1ff35c07217c | 75 | // after a sample starts, so it'll ignore that first clock tick. But as |
mjr | 100:1ff35c07217c | 76 | // long as the sampling time is less than *two* master clock periods, the |
mjr | 100:1ff35c07217c | 77 | // ADC will always be ready again on the second tick. So we'll get one ADC |
mjr | 100:1ff35c07217c | 78 | // sample for every two master clock ticks, exactly as we need. |
mjr | 100:1ff35c07217c | 79 | // |
mjr | 104:6e06e0f4b476 | 80 | // This is all possible because the ADC timing is deterministic, and runs on |
mjr | 104:6e06e0f4b476 | 81 | // the same clock as the TPM. The KL25Z Subfamily Reference Manual explains |
mjr | 104:6e06e0f4b476 | 82 | // how to calculate the ADC conversion time for a given combination of mode |
mjr | 104:6e06e0f4b476 | 83 | // bits. So we just have to pick an ADC mode, calculate its conversion time, |
mjr | 104:6e06e0f4b476 | 84 | // and then select a TPM period that's slightly more than 1/2 of the ADC |
mjr | 104:6e06e0f4b476 | 85 | // conversion time. |
mjr | 104:6e06e0f4b476 | 86 | // |
mjr | 111:42dc75fbe623 | 87 | // I know this sounds like it should be prone to unpredictable timing bugs, |
mjr | 111:42dc75fbe623 | 88 | // but it's actually 100% deterministic! It's truly deterministic because |
mjr | 111:42dc75fbe623 | 89 | // the underlying clock for the TPM and ADC is shared. Intuitively, we |
mjr | 111:42dc75fbe623 | 90 | // think of time-based processes as inherently stochastic because clocks |
mjr | 111:42dc75fbe623 | 91 | // are never perfect, so if you have two time-based processes based on |
mjr | 111:42dc75fbe623 | 92 | // separate clocks, the two processes are never perfectly in sync because |
mjr | 111:42dc75fbe623 | 93 | // their separate clocks will drift slightly relative to one another. But |
mjr | 111:42dc75fbe623 | 94 | // that's not what's going on here. The time-based processes we're talking |
mjr | 111:42dc75fbe623 | 95 | // about are tied to the same underlying clock, so there's absolutely no |
mjr | 111:42dc75fbe623 | 96 | // possibility of clock drift or phase shift or anything else that feeds |
mjr | 111:42dc75fbe623 | 97 | // into that intuition about stochasticness in time-based processes. In |
mjr | 111:42dc75fbe623 | 98 | // addition, the key ADC feature we're exploiting for the clock doubling - |
mjr | 111:42dc75fbe623 | 99 | // that the ADC ignores hardware triggers during an active cycle - isn't |
mjr | 111:42dc75fbe623 | 100 | // some accidental behavior we observed empirically. It's by design and |
mjr | 111:42dc75fbe623 | 101 | // it's documented. We can count on it always being the case with this |
mjr | 111:42dc75fbe623 | 102 | // ADC. Between those two factors (synchronous clock, designed and |
mjr | 111:42dc75fbe623 | 103 | // documented ADC behavior), we can count on the timing being EXACTLY the |
mjr | 111:42dc75fbe623 | 104 | // same on EVERY sample. We're not counting on being "lucky". |
mjr | 111:42dc75fbe623 | 105 | // |
mjr | 110:bf332f824585 | 106 | // Note that there are several other, similar Toshiba sensors with the same |
mjr | 110:bf332f824585 | 107 | // electrical interface and almost the same signal timing, but with a 4:1 |
mjr | 110:bf332f824585 | 108 | // ratio between the master clock ticks and the pixel outputs. This code |
mjr | 110:bf332f824585 | 109 | // could be adapted to those sensors using the same "trick" we use for the |
mjr | 110:bf332f824585 | 110 | // 2:1 timing ratio, by choosing an ADC mode with a sampling rate that's |
mjr | 110:bf332f824585 | 111 | // between 3*fM and 4*fM. That will make the ADC ignore the first three |
mjr | 110:bf332f824585 | 112 | // master clocks in each cycle, triggering a new sample reading on every |
mjr | 110:bf332f824585 | 113 | // fourth master clock tick, achieving the desired 4:1 ratio. We don't |
mjr | 110:bf332f824585 | 114 | // provide an option for that because there are no such Toshiba sensors in |
mjr | 110:bf332f824585 | 115 | // production that are of interest to us as plunger sensors, and because |
mjr | 110:bf332f824585 | 116 | // the selection of a suitable fM timing and ADC mode are both dependent |
mjr | 110:bf332f824585 | 117 | // on the constraints of your application, so it's not feasible to automate |
mjr | 110:bf332f824585 | 118 | // the selection of either based on simple numeric parameters. If you want |
mjr | 110:bf332f824585 | 119 | // to adapt the code, start by figuring out the range of fM timing you can |
mjr | 110:bf332f824585 | 120 | // accept, then look at the KL25Z manual to work out the ADC cycle timing |
mjr | 110:bf332f824585 | 121 | // for various modes with the properties you want. You can then adjust |
mjr | 110:bf332f824585 | 122 | // either or both the fM timing and ADC settings until you find a suitable |
mjr | 110:bf332f824585 | 123 | // balance in the timing. The Toshiba sensors can generally accept a wide |
mjr | 110:bf332f824585 | 124 | // range of fM rates, so you can count both the clock rate and ADC modes as |
mjr | 110:bf332f824585 | 125 | // free variables, within the constraints of your application in terms of |
mjr | 110:bf332f824585 | 126 | // required frame rate and ADC sampling quality. |
mjr | 110:bf332f824585 | 127 | // |
mjr | 104:6e06e0f4b476 | 128 | // |
mjr | 104:6e06e0f4b476 | 129 | // Pixel output signal |
mjr | 104:6e06e0f4b476 | 130 | // |
mjr | 104:6e06e0f4b476 | 131 | // The pixel output signal from this sensor is an analog voltage level. It's |
mjr | 104:6e06e0f4b476 | 132 | // inverted from the brightness: higher brightness is represented by lower |
mjr | 110:bf332f824585 | 133 | // voltage. The dynamic range is only about 1V, with a 1V floor. So dark |
mjr | 110:bf332f824585 | 134 | // pixels read at about 2V, and saturated pixels read at about 1V. |
mjr | 110:bf332f824585 | 135 | // |
mjr | 110:bf332f824585 | 136 | // The output pin from the sensor connects to what is essentially a very |
mjr | 110:bf332f824585 | 137 | // small capacitor containing a tiny amount of charge. This isn't a good |
mjr | 110:bf332f824585 | 138 | // source for the ADC to sample, so some additional circuitry is required |
mjr | 110:bf332f824585 | 139 | // to convert the charge to a low-impedance voltage source suitable for |
mjr | 110:bf332f824585 | 140 | // connecting to an ADC. The driver circuit recommended in the Toshiba |
mjr | 110:bf332f824585 | 141 | // data sheet consists of a high-gain PNP transistor and a few resistors. |
mjr | 110:bf332f824585 | 142 | // See "How to connect to the KL25Z" below for the component types and |
mjr | 110:bf332f824585 | 143 | // values we've tested successfully. |
mjr | 104:6e06e0f4b476 | 144 | // |
mjr | 104:6e06e0f4b476 | 145 | // |
mjr | 104:6e06e0f4b476 | 146 | // Inverted logic signals |
mjr | 104:6e06e0f4b476 | 147 | // |
mjr | 104:6e06e0f4b476 | 148 | // The Toshiba data sheet recommends buffering the logic signal inputs from |
mjr | 104:6e06e0f4b476 | 149 | // an MCU through a 74HC04 inverter, because the sensor's logic gates have |
mjr | 104:6e06e0f4b476 | 150 | // relatively high input capacitance that an MCU might not be able to drive |
mjr | 104:6e06e0f4b476 | 151 | // fast enough directly to keep up with the sensor's timing requirements. |
mjr | 104:6e06e0f4b476 | 152 | // SH in particular might be a problem because of its 150pF capacitance, |
mjr | 104:6e06e0f4b476 | 153 | // which implies about a 2us rise/fall time if driven directly by KL25Z |
mjr | 104:6e06e0f4b476 | 154 | // GPIOs, which is too slow. |
mjr | 104:6e06e0f4b476 | 155 | // |
mjr | 110:bf332f824585 | 156 | // The software will work with or without the logic inversion, in case anyone |
mjr | 104:6e06e0f4b476 | 157 | // wants to try implementing it with direct GPIO drive (not recommended) or |
mjr | 104:6e06e0f4b476 | 158 | // with a non-inverting buffer in place of the 74HC04. Simply instantiate the |
mjr | 104:6e06e0f4b476 | 159 | // class with the 'invertedLogicGates' template parameter set to false to use |
mjr | 104:6e06e0f4b476 | 160 | // non-inverted logic. |
mjr | 104:6e06e0f4b476 | 161 | // |
mjr | 104:6e06e0f4b476 | 162 | // |
mjr | 104:6e06e0f4b476 | 163 | // How to connect to the KL25Z |
mjr | 104:6e06e0f4b476 | 164 | // |
mjr | 104:6e06e0f4b476 | 165 | // Follow the "typical drive circuit" presented in the Toshiba data sheet. |
mjr | 104:6e06e0f4b476 | 166 | // They leave some of the parts unspecified, so here are the specific values |
mjr | 104:6e06e0f4b476 | 167 | // we used for our reference implementation: |
mjr | 104:6e06e0f4b476 | 168 | // |
mjr | 104:6e06e0f4b476 | 169 | // - 3.3V power supply |
mjr | 104:6e06e0f4b476 | 170 | // - 74HC04N hex inverter for the logic gate inputs (fM, SH, ICG) |
mjr | 104:6e06e0f4b476 | 171 | // - 0.1uF ceramic + 10uF electrolytic decoupling capacitors (GND to Vcc)) |
mjr | 104:6e06e0f4b476 | 172 | // - BC212A PNP transistor for the output drive (OS), with: |
mjr | 104:6e06e0f4b476 | 173 | // - 150 ohm resistor on the base |
mjr | 104:6e06e0f4b476 | 174 | // - 150 ohm resistor between collector and GND |
mjr | 104:6e06e0f4b476 | 175 | // - 2.2K ohm resistor between emitter and Vcc |
mjr | 104:6e06e0f4b476 | 176 | // |
mjr | 100:1ff35c07217c | 177 | |
mjr | 100:1ff35c07217c | 178 | #include "config.h" |
mjr | 100:1ff35c07217c | 179 | #include "NewPwm.h" |
mjr | 100:1ff35c07217c | 180 | #include "AltAnalogIn.h" |
mjr | 100:1ff35c07217c | 181 | #include "SimpleDMA.h" |
mjr | 100:1ff35c07217c | 182 | #include "DMAChannels.h" |
mjr | 100:1ff35c07217c | 183 | |
mjr | 100:1ff35c07217c | 184 | |
mjr | 100:1ff35c07217c | 185 | template<bool invertedLogicGates> class TCD1103 |
mjr | 100:1ff35c07217c | 186 | { |
mjr | 100:1ff35c07217c | 187 | public: |
mjr | 100:1ff35c07217c | 188 | TCD1103(PinName fmPin, PinName osPin, PinName icgPin, PinName shPin) : |
mjr | 100:1ff35c07217c | 189 | fm(fmPin, invertedLogicGates), |
mjr | 100:1ff35c07217c | 190 | os(osPin, false, 6, 1), // single sample, 6-cycle long sampling mode, no averaging |
mjr | 100:1ff35c07217c | 191 | icg(icgPin), |
mjr | 100:1ff35c07217c | 192 | sh(shPin), |
mjr | 100:1ff35c07217c | 193 | os_dma(DMAch_TDC_ADC) |
mjr | 100:1ff35c07217c | 194 | { |
mjr | 103:dec22cd65b2a | 195 | // Idle conditions: SH low, ICG high. |
mjr | 103:dec22cd65b2a | 196 | sh = logicLow; |
mjr | 103:dec22cd65b2a | 197 | icg = logicHigh; |
mjr | 103:dec22cd65b2a | 198 | |
mjr | 103:dec22cd65b2a | 199 | // Set a zero minimum integration time by default. Note that tIntMin |
mjr | 103:dec22cd65b2a | 200 | // has no effect when it's less than the absolute minimum, which is |
mjr | 103:dec22cd65b2a | 201 | // the pixel transfer time for one frame (around 3ms). tIntMin only |
mjr | 103:dec22cd65b2a | 202 | // kicks in when it goes above that absolute minimum, at which point |
mjr | 103:dec22cd65b2a | 203 | // we'll wait for any additional time needed to reach tIntMin before |
mjr | 103:dec22cd65b2a | 204 | // starting the next integration cycle. |
mjr | 103:dec22cd65b2a | 205 | tIntMin = 0; |
mjr | 103:dec22cd65b2a | 206 | |
mjr | 100:1ff35c07217c | 207 | // Calibrate the ADC for best accuracy |
mjr | 100:1ff35c07217c | 208 | os.calibrate(); |
mjr | 104:6e06e0f4b476 | 209 | |
mjr | 100:1ff35c07217c | 210 | // ADC sample conversion time. This must be calculated based on the |
mjr | 100:1ff35c07217c | 211 | // combination of parameters selected for the os() initializer above. |
mjr | 109:310ac82cbbee | 212 | // See the KL25 Sub-Family Reference Manual, section 28.4.4.5, for the |
mjr | 109:310ac82cbbee | 213 | // formula. We operate in single-sample mode, so when you read the |
mjr | 109:310ac82cbbee | 214 | // Reference Manual tables, the sample time value to use is the |
mjr | 109:310ac82cbbee | 215 | // "First or Single" value. |
mjr | 109:310ac82cbbee | 216 | const float ADC_TIME = 2.1041667e-6f; // 6-cycle long sampling, no averaging |
mjr | 100:1ff35c07217c | 217 | |
mjr | 100:1ff35c07217c | 218 | // Set the TPM cycle time to satisfy our timing constraints: |
mjr | 100:1ff35c07217c | 219 | // |
mjr | 100:1ff35c07217c | 220 | // Tm + epsilon1 < A < 2*Tm - epsilon2 |
mjr | 100:1ff35c07217c | 221 | // |
mjr | 100:1ff35c07217c | 222 | // where A is the ADC conversion time and Tm is the master clock |
mjr | 109:310ac82cbbee | 223 | // period, and the epsilons provide a margin of safety for any |
mjr | 100:1ff35c07217c | 224 | // non-deterministic component to the timing of A and Tm. The |
mjr | 100:1ff35c07217c | 225 | // epsilons could be zero if the timing of the ADC is perfectly |
mjr | 100:1ff35c07217c | 226 | // deterministic; this must be determined empirically. |
mjr | 100:1ff35c07217c | 227 | // |
mjr | 100:1ff35c07217c | 228 | // The most conservative solution would be to make epsilon as large |
mjr | 100:1ff35c07217c | 229 | // as possible, which means bisecting the time window by making |
mjr | 100:1ff35c07217c | 230 | // A = 1.5*T, or, equivalently, T = A/1.5 (the latter form being more |
mjr | 100:1ff35c07217c | 231 | // useful because T is the free variable here, as we can only control |
mjr | 100:1ff35c07217c | 232 | // A to the extent that we can choose the ADC parameters). |
mjr | 100:1ff35c07217c | 233 | // |
mjr | 100:1ff35c07217c | 234 | // But we'd also like to make T as short as possible while maintaining |
mjr | 100:1ff35c07217c | 235 | // reliable operation. Shorter T yields a higher frame rate, and we |
mjr | 100:1ff35c07217c | 236 | // want the frame rate to be as high as possible so that we can track |
mjr | 100:1ff35c07217c | 237 | // fast plunger motion accurately. Empirically, we can get reliable |
mjr | 100:1ff35c07217c | 238 | // results by using half of the ADC time plus a small buffer time. |
mjr | 100:1ff35c07217c | 239 | // |
mjr | 109:310ac82cbbee | 240 | fm.getUnit()->period(masterClockPeriod = ADC_TIME/2 + 0.25e-6f); |
mjr | 100:1ff35c07217c | 241 | |
mjr | 100:1ff35c07217c | 242 | // Start the master clock running with a 50% duty cycle |
mjr | 100:1ff35c07217c | 243 | fm.write(0.5f); |
mjr | 100:1ff35c07217c | 244 | |
mjr | 104:6e06e0f4b476 | 245 | // Allocate our double pixel buffers. |
mjr | 104:6e06e0f4b476 | 246 | pix1 = new uint8_t[nPixAlo * 2]; |
mjr | 104:6e06e0f4b476 | 247 | pix2 = pix1 + nPixAlo; |
mjr | 100:1ff35c07217c | 248 | |
mjr | 100:1ff35c07217c | 249 | // put the first DMA transfer into the first buffer (pix1) |
mjr | 100:1ff35c07217c | 250 | pixDMA = 0; |
mjr | 101:755f44622abc | 251 | clientOwnsStablePix = false; |
mjr | 100:1ff35c07217c | 252 | |
mjr | 100:1ff35c07217c | 253 | // start the sample timer with an arbitrary epoch of "now" |
mjr | 100:1ff35c07217c | 254 | t.start(); |
mjr | 100:1ff35c07217c | 255 | |
mjr | 100:1ff35c07217c | 256 | // Set up the ADC transfer DMA channel. This channel transfers |
mjr | 100:1ff35c07217c | 257 | // the current analog sampling result from the ADC output register |
mjr | 100:1ff35c07217c | 258 | // to our pixel array. |
mjr | 100:1ff35c07217c | 259 | os.initDMA(&os_dma); |
mjr | 100:1ff35c07217c | 260 | |
mjr | 100:1ff35c07217c | 261 | // Register an interrupt callback so that we're notified when |
mjr | 100:1ff35c07217c | 262 | // the last ADC transfer completes. |
mjr | 100:1ff35c07217c | 263 | os_dma.attach(this, &TCD1103::transferDone); |
mjr | 100:1ff35c07217c | 264 | |
mjr | 100:1ff35c07217c | 265 | // Set up the ADC to trigger on the master clock's TPM channel |
mjr | 100:1ff35c07217c | 266 | os.setTriggerTPM(fm.getUnitNum()); |
mjr | 100:1ff35c07217c | 267 | |
mjr | 100:1ff35c07217c | 268 | // clear the timing statistics |
mjr | 100:1ff35c07217c | 269 | totalXferTime = 0.0; |
mjr | 100:1ff35c07217c | 270 | maxXferTime = 0; |
mjr | 100:1ff35c07217c | 271 | minXferTime = 0xffffffff; |
mjr | 100:1ff35c07217c | 272 | nRuns = 0; |
mjr | 100:1ff35c07217c | 273 | |
mjr | 101:755f44622abc | 274 | // start the first transfer |
mjr | 101:755f44622abc | 275 | startTransfer(); |
mjr | 100:1ff35c07217c | 276 | } |
mjr | 100:1ff35c07217c | 277 | |
mjr | 100:1ff35c07217c | 278 | // logic gate levels, based on whether or not the logic gate connections |
mjr | 100:1ff35c07217c | 279 | // in the hardware are buffered through inverters |
mjr | 100:1ff35c07217c | 280 | static const int logicLow = invertedLogicGates ? 1 : 0; |
mjr | 100:1ff35c07217c | 281 | static const bool logicHigh = invertedLogicGates ? 0 : 1; |
mjr | 100:1ff35c07217c | 282 | |
mjr | 100:1ff35c07217c | 283 | // ready to read |
mjr | 101:755f44622abc | 284 | bool ready() { return clientOwnsStablePix; } |
mjr | 103:dec22cd65b2a | 285 | |
mjr | 100:1ff35c07217c | 286 | // Get the stable pixel array. This is the image array from the |
mjr | 100:1ff35c07217c | 287 | // previous capture. It remains valid until the next startCapture() |
mjr | 100:1ff35c07217c | 288 | // call, at which point this buffer will be reused for the new capture. |
mjr | 100:1ff35c07217c | 289 | void getPix(uint8_t * &pix, uint32_t &t) |
mjr | 100:1ff35c07217c | 290 | { |
mjr | 104:6e06e0f4b476 | 291 | // Return the pixel array that ISN'T assigned to the DMA. |
mjr | 100:1ff35c07217c | 292 | if (pixDMA) |
mjr | 100:1ff35c07217c | 293 | { |
mjr | 100:1ff35c07217c | 294 | // DMA owns pix2, so the stable array is pix1 |
mjr | 100:1ff35c07217c | 295 | pix = pix1; |
mjr | 100:1ff35c07217c | 296 | t = t1; |
mjr | 100:1ff35c07217c | 297 | } |
mjr | 100:1ff35c07217c | 298 | else |
mjr | 100:1ff35c07217c | 299 | { |
mjr | 100:1ff35c07217c | 300 | // DMA owns pix1, so the stable array is pix2 |
mjr | 100:1ff35c07217c | 301 | pix = pix2; |
mjr | 100:1ff35c07217c | 302 | t = t2; |
mjr | 100:1ff35c07217c | 303 | } |
mjr | 100:1ff35c07217c | 304 | } |
mjr | 100:1ff35c07217c | 305 | |
mjr | 101:755f44622abc | 306 | // release the client's pixel buffer |
mjr | 101:755f44622abc | 307 | void releasePix() { clientOwnsStablePix = false; } |
mjr | 101:755f44622abc | 308 | |
mjr | 101:755f44622abc | 309 | // figure the average scan time from the running totals |
mjr | 101:755f44622abc | 310 | uint32_t getAvgScanTime() { return static_cast<uint32_t>(totalXferTime / nRuns);} |
mjr | 101:755f44622abc | 311 | |
mjr | 101:755f44622abc | 312 | // Set the requested minimum integration time. If this is less than the |
mjr | 101:755f44622abc | 313 | // sensor's physical minimum time, the physical minimum applies. |
mjr | 101:755f44622abc | 314 | virtual void setMinIntTime(uint32_t us) |
mjr | 100:1ff35c07217c | 315 | { |
mjr | 101:755f44622abc | 316 | tIntMin = us; |
mjr | 101:755f44622abc | 317 | } |
mjr | 101:755f44622abc | 318 | |
mjr | 101:755f44622abc | 319 | protected: |
mjr | 100:1ff35c07217c | 320 | // Start an image capture from the sensor. Waits the previous |
mjr | 100:1ff35c07217c | 321 | // capture to finish if it's still running, then starts a new one |
mjr | 104:6e06e0f4b476 | 322 | // and returns immediately. The new capture proceeds asynchronously |
mjr | 104:6e06e0f4b476 | 323 | // via DMA hardware transfer, so the client can continue with other |
mjr | 100:1ff35c07217c | 324 | // processing during the capture. |
mjr | 101:755f44622abc | 325 | void startTransfer() |
mjr | 100:1ff35c07217c | 326 | { |
mjr | 101:755f44622abc | 327 | // if we own the stable buffer, swap buffers |
mjr | 101:755f44622abc | 328 | if (!clientOwnsStablePix) |
mjr | 100:1ff35c07217c | 329 | { |
mjr | 101:755f44622abc | 330 | // swap buffers |
mjr | 101:755f44622abc | 331 | pixDMA ^= 1; |
mjr | 101:755f44622abc | 332 | |
mjr | 101:755f44622abc | 333 | // release the prior DMA buffer to the client |
mjr | 101:755f44622abc | 334 | clientOwnsStablePix = true; |
mjr | 100:1ff35c07217c | 335 | } |
mjr | 100:1ff35c07217c | 336 | |
mjr | 104:6e06e0f4b476 | 337 | // figure our destination buffer |
mjr | 104:6e06e0f4b476 | 338 | uint8_t *dst = pixDMA ? pix2 : pix1; |
mjr | 104:6e06e0f4b476 | 339 | |
mjr | 100:1ff35c07217c | 340 | // Set up the active pixel array as the destination buffer for |
mjr | 100:1ff35c07217c | 341 | // the ADC DMA channel. |
mjr | 104:6e06e0f4b476 | 342 | os_dma.destination(dst, true); |
mjr | 100:1ff35c07217c | 343 | |
mjr | 100:1ff35c07217c | 344 | // Start the read cycle by sending the ICG/SH pulse sequence |
mjr | 100:1ff35c07217c | 345 | uint32_t tNewInt = gen_SH_ICG_pulse(true); |
mjr | 100:1ff35c07217c | 346 | |
mjr | 100:1ff35c07217c | 347 | // Set the timestamp for the current active buffer. The ICG/SH |
mjr | 100:1ff35c07217c | 348 | // gymnastics we just did transferred the CCD pixels into the sensor's |
mjr | 100:1ff35c07217c | 349 | // internal shift register and reset the pixels, starting a new |
mjr | 100:1ff35c07217c | 350 | // integration cycle. So the pixels we just shifted started |
mjr | 100:1ff35c07217c | 351 | // integrating the *last* time we did that, which we recorded as |
mjr | 100:1ff35c07217c | 352 | // tInt at the time. The image we're about to transfer therefore |
mjr | 100:1ff35c07217c | 353 | // represents the light collected between tInt and the SH pulse we |
mjr | 100:1ff35c07217c | 354 | // just did. The image covers a time range rather than a single |
mjr | 100:1ff35c07217c | 355 | // point in time, but we still have to give it a single timestamp. |
mjr | 100:1ff35c07217c | 356 | // Use the midpoint of the integration period. |
mjr | 100:1ff35c07217c | 357 | uint32_t tmid = (tNewInt + tInt) >> 1; |
mjr | 100:1ff35c07217c | 358 | if (pixDMA) |
mjr | 100:1ff35c07217c | 359 | t2 = tmid; |
mjr | 100:1ff35c07217c | 360 | else |
mjr | 100:1ff35c07217c | 361 | t1 = tmid; |
mjr | 100:1ff35c07217c | 362 | |
mjr | 100:1ff35c07217c | 363 | // Record the start time of the currently active integration period |
mjr | 100:1ff35c07217c | 364 | tInt = tNewInt; |
mjr | 100:1ff35c07217c | 365 | } |
mjr | 100:1ff35c07217c | 366 | |
mjr | 101:755f44622abc | 367 | // End of transfer notification. This runs as an interrupt handler when |
mjr | 101:755f44622abc | 368 | // the DMA transfer completes. |
mjr | 101:755f44622abc | 369 | void transferDone() |
mjr | 100:1ff35c07217c | 370 | { |
mjr | 104:6e06e0f4b476 | 371 | // stop the ADC triggering |
mjr | 104:6e06e0f4b476 | 372 | os.stop(); |
mjr | 104:6e06e0f4b476 | 373 | |
mjr | 101:755f44622abc | 374 | // add this sample to the timing statistics (for diagnostics and |
mjr | 101:755f44622abc | 375 | // performance measurement) |
mjr | 101:755f44622abc | 376 | uint32_t now = t.read_us(); |
mjr | 101:755f44622abc | 377 | uint32_t dt = dtPixXfer = static_cast<uint32_t>(now - tXfer); |
mjr | 101:755f44622abc | 378 | totalXferTime += dt; |
mjr | 101:755f44622abc | 379 | nRuns += 1; |
mjr | 101:755f44622abc | 380 | |
mjr | 101:755f44622abc | 381 | // collect debug statistics |
mjr | 101:755f44622abc | 382 | if (dt < minXferTime) minXferTime = dt; |
mjr | 101:755f44622abc | 383 | if (dt > maxXferTime) maxXferTime = dt; |
mjr | 104:6e06e0f4b476 | 384 | |
mjr | 104:6e06e0f4b476 | 385 | // figure how long we've been integrating so far on this cycle |
mjr | 101:755f44622abc | 386 | uint32_t dtInt = now - tInt; |
mjr | 104:6e06e0f4b476 | 387 | |
mjr | 104:6e06e0f4b476 | 388 | // Figure the time to the start of the next transfer. Wait for the |
mjr | 104:6e06e0f4b476 | 389 | // remainder of the current integration period if we haven't yet |
mjr | 104:6e06e0f4b476 | 390 | // reached the requested minimum, otherwise just start almost |
mjr | 104:6e06e0f4b476 | 391 | // immediately. (Not *actually* immediately: we don't want to start |
mjr | 104:6e06e0f4b476 | 392 | // the new transfer within this interrupt handler, because the DMA |
mjr | 104:6e06e0f4b476 | 393 | // IRQ doesn't reliably clear if we start a new transfer immediately.) |
mjr | 104:6e06e0f4b476 | 394 | uint32_t dtNext = dtInt < tIntMin ? tIntMin - dtInt : 1; |
mjr | 104:6e06e0f4b476 | 395 | |
mjr | 104:6e06e0f4b476 | 396 | // Schedule the next transfer |
mjr | 104:6e06e0f4b476 | 397 | integrationTimeout.attach_us(this, &TCD1103::startTransfer, dtNext); |
mjr | 100:1ff35c07217c | 398 | } |
mjr | 100:1ff35c07217c | 399 | |
mjr | 100:1ff35c07217c | 400 | // Generate an SH/ICG pulse. This transfers the pixel data from the live |
mjr | 100:1ff35c07217c | 401 | // sensor photoreceptors into the sensor's internal shift register, clears |
mjr | 100:1ff35c07217c | 402 | // the live pixels, and starts a new integration cycle. |
mjr | 100:1ff35c07217c | 403 | // |
mjr | 100:1ff35c07217c | 404 | // If start_dma_xfer is true, we'll start the DMA transfer for the ADC |
mjr | 100:1ff35c07217c | 405 | // pixel data. We handle this here because the sensor starts clocking |
mjr | 100:1ff35c07217c | 406 | // out pixels precisely at the end of the ICG pulse, so we have to be |
mjr | 100:1ff35c07217c | 407 | // be very careful about the timing. |
mjr | 100:1ff35c07217c | 408 | // |
mjr | 100:1ff35c07217c | 409 | // Returns the timestamp (relative to our image timer 't') of the end |
mjr | 100:1ff35c07217c | 410 | // of the SH pulse, which is the moment the new integration cycle starts. |
mjr | 100:1ff35c07217c | 411 | // |
mjr | 100:1ff35c07217c | 412 | // Note that we send these pulses synchronously - that is, this routine |
mjr | 100:1ff35c07217c | 413 | // blocks until the pulses have been sent. The overall sequence takes |
mjr | 100:1ff35c07217c | 414 | // about 2.5us to 3us, so it's not a significant interruption of the |
mjr | 100:1ff35c07217c | 415 | // main loop. |
mjr | 100:1ff35c07217c | 416 | // |
mjr | 100:1ff35c07217c | 417 | uint32_t gen_SH_ICG_pulse(bool start_dma_xfer) |
mjr | 100:1ff35c07217c | 418 | { |
mjr | 109:310ac82cbbee | 419 | // Make sure the ADC is stopped |
mjr | 109:310ac82cbbee | 420 | os.stop(); |
mjr | 109:310ac82cbbee | 421 | |
mjr | 100:1ff35c07217c | 422 | // If desired, prepare to start the DMA transfer for the ADC data. |
mjr | 100:1ff35c07217c | 423 | // (Set up a dummy location to write in lieu of the DMA register if |
mjr | 100:1ff35c07217c | 424 | // DMA initiation isn't required, so that we don't have to take the |
mjr | 100:1ff35c07217c | 425 | // time for a conditional when we're ready to start the DMA transfer. |
mjr | 100:1ff35c07217c | 426 | // The timing there will be extremely tight, and we can't afford the |
mjr | 100:1ff35c07217c | 427 | // extra instructions to test a condition.) |
mjr | 100:1ff35c07217c | 428 | uint8_t dma_chcfg_dummy = 0; |
mjr | 100:1ff35c07217c | 429 | volatile uint8_t *dma_chcfg = start_dma_xfer ? os_dma.prepare(nPixSensor, true) : &dma_chcfg_dummy; |
mjr | 100:1ff35c07217c | 430 | |
mjr | 100:1ff35c07217c | 431 | // The basic idea is to take ICG low, and while holding ICG low, |
mjr | 100:1ff35c07217c | 432 | // pulse SH. The coincidence of the two pulses transfers the charge |
mjr | 100:1ff35c07217c | 433 | // from the live pixels into the shift register, which effectively |
mjr | 100:1ff35c07217c | 434 | // discharges the live pixels and thereby starts a new integration |
mjr | 100:1ff35c07217c | 435 | // cycle. |
mjr | 100:1ff35c07217c | 436 | // |
mjr | 100:1ff35c07217c | 437 | // The timing of the pulse sequence is rather tightly constrained |
mjr | 100:1ff35c07217c | 438 | // per the data sheet, so we have to take some care in executing it: |
mjr | 100:1ff35c07217c | 439 | // |
mjr | 100:1ff35c07217c | 440 | // ICG -> LOW |
mjr | 100:1ff35c07217c | 441 | // 100-1000 ns delay (*) |
mjr | 100:1ff35c07217c | 442 | // SH -> HIGH |
mjr | 100:1ff35c07217c | 443 | // >1000ns delay |
mjr | 100:1ff35c07217c | 444 | // SH -> LOW |
mjr | 100:1ff35c07217c | 445 | // >1000ns delay |
mjr | 100:1ff35c07217c | 446 | // ICG -> high (**) |
mjr | 100:1ff35c07217c | 447 | // |
mjr | 100:1ff35c07217c | 448 | // There are two steps here that are tricky: |
mjr | 100:1ff35c07217c | 449 | // |
mjr | 100:1ff35c07217c | 450 | // (*) is a narrow window that we can't achieve with an mbed |
mjr | 100:1ff35c07217c | 451 | // microsecond timer. Instead, we'll do a couple of extra writes |
mjr | 100:1ff35c07217c | 452 | // to the ICG register, which take about 60ns each. |
mjr | 100:1ff35c07217c | 453 | // |
mjr | 100:1ff35c07217c | 454 | // (**) has the rather severe constraint that the transition must |
mjr | 100:1ff35c07217c | 455 | // occur AND complete while the master clock is high. Other people |
mjr | 100:1ff35c07217c | 456 | // working with similar Toshiba chips in MCU projects have suggested |
mjr | 100:1ff35c07217c | 457 | // that this constraint can safely be ignored, so maybe the data |
mjr | 100:1ff35c07217c | 458 | // sheet's insistence about it is obsolete advice from past Toshiba |
mjr | 100:1ff35c07217c | 459 | // sensors that the doc writers carried forward by copy-and-paste. |
mjr | 100:1ff35c07217c | 460 | // Toshiba has been making these sorts of chips for a very long time, |
mjr | 100:1ff35c07217c | 461 | // and the data sheets for many of them are obvious copy-and-paste |
mjr | 100:1ff35c07217c | 462 | // jobs. But let's take the data sheet at its word and assume that |
mjr | 100:1ff35c07217c | 463 | // this is important for proper operation. Our best hope of |
mjr | 100:1ff35c07217c | 464 | // satisfying this constraint is to synchronize the start of the |
mjr | 100:1ff35c07217c | 465 | // ICG->high transition with the start of a TPM cycle on the master |
mjr | 100:1ff35c07217c | 466 | // clock. That guarantees that the ICG transition starts when the |
mjr | 100:1ff35c07217c | 467 | // clock signal is high (as each TPM cycle starts out high), and |
mjr | 100:1ff35c07217c | 468 | // gives us the longest possible runway for the transition to |
mjr | 100:1ff35c07217c | 469 | // complete while the clock is still high, as we get the full |
mjr | 100:1ff35c07217c | 470 | // length of the high part of the cycle to work with. To quantify, |
mjr | 100:1ff35c07217c | 471 | // it gives us about 600ns. The register write takes about 60ns, |
mjr | 100:1ff35c07217c | 472 | // and waitEndCycle() adds several instructions of overhead, perhaps |
mjr | 100:1ff35c07217c | 473 | // 200ns, so we get around 300ns for the transition to finish. That |
mjr | 100:1ff35c07217c | 474 | // should be a gracious plenty assuming that the hardware is set up |
mjr | 100:1ff35c07217c | 475 | // with an inverter to buffer the clock signals. The inverter should |
mjr | 100:1ff35c07217c | 476 | // be able to pull up the 35pF on ICG in a "typical" 30ns (rise time |
mjr | 100:1ff35c07217c | 477 | // plus propagation delay, per the 74HC04 data sheet) and max 150ns. |
mjr | 100:1ff35c07217c | 478 | // This seems to be one place where the inverter might really be |
mjr | 100:1ff35c07217c | 479 | // necessary to meet the timing requirements, as the KL25Z GPIO |
mjr | 100:1ff35c07217c | 480 | // might need more like 2us to pull that load up. |
mjr | 100:1ff35c07217c | 481 | // |
mjr | 100:1ff35c07217c | 482 | // There's an additional constraint on the timing at the end of the |
mjr | 100:1ff35c07217c | 483 | // ICG pulse. The sensor starts clocking out pixels on the rising |
mjr | 100:1ff35c07217c | 484 | // edge of the ICG pulse. So we need the ICG pulse end to align |
mjr | 100:1ff35c07217c | 485 | // with the start of an ADC cycle. If we get that wrong, all of our |
mjr | 100:1ff35c07217c | 486 | // ADC samples will be off by half a clock, so every sample will be |
mjr | 100:1ff35c07217c | 487 | // the average of two adjacent pixels instead of one pixel. That |
mjr | 109:310ac82cbbee | 488 | // would have the effect of shifting the image by half a pixel, |
mjr | 109:310ac82cbbee | 489 | // which could make our edge detection jitter by one pixel from one |
mjr | 109:310ac82cbbee | 490 | // frame to the next. So we definitely want to avoid this. |
mjr | 100:1ff35c07217c | 491 | // |
mjr | 100:1ff35c07217c | 492 | // The end of the SH pulse triggers the start of a new integration |
mjr | 100:1ff35c07217c | 493 | // cycle, so note the time of that pulse for image timestamping |
mjr | 100:1ff35c07217c | 494 | // purposes. That will be the start time of the NEXT image we |
mjr | 100:1ff35c07217c | 495 | // transfer after we shift out the current sensor pixels, which |
mjr | 100:1ff35c07217c | 496 | // represent the pixels from the last time we pulsed SH. |
mjr | 100:1ff35c07217c | 497 | // |
mjr | 100:1ff35c07217c | 498 | icg = logicLow; |
mjr | 109:310ac82cbbee | 499 | icg = logicLow; // for timing, adds about 150ns > min 100ns |
mjr | 103:dec22cd65b2a | 500 | |
mjr | 103:dec22cd65b2a | 501 | sh = logicHigh; // take SH high |
mjr | 103:dec22cd65b2a | 502 | |
mjr | 100:1ff35c07217c | 503 | wait_us(1); // >1000ns delay |
mjr | 103:dec22cd65b2a | 504 | sh = logicHigh; // a little more padding to be sure we're over the minimum |
mjr | 103:dec22cd65b2a | 505 | |
mjr | 103:dec22cd65b2a | 506 | sh = logicLow; // take SH low |
mjr | 103:dec22cd65b2a | 507 | |
mjr | 103:dec22cd65b2a | 508 | uint32_t t_sh = t.read_us(); // this is the start time of the NEXT integration |
mjr | 103:dec22cd65b2a | 509 | |
mjr | 103:dec22cd65b2a | 510 | wait_us(3); // >1000ns delay, 5000ns typical; 3us should get us most |
mjr | 103:dec22cd65b2a | 511 | // of the way there, considering that we have some more |
mjr | 103:dec22cd65b2a | 512 | // work to do before we end the ICG pulse |
mjr | 100:1ff35c07217c | 513 | |
mjr | 100:1ff35c07217c | 514 | // Now the tricky part! We have to end the ICG pulse (take ICG high) |
mjr | 100:1ff35c07217c | 515 | // at the start of a master clock cycle, AND at the start of an ADC |
mjr | 100:1ff35c07217c | 516 | // sampling cycle. The sensor will start clocking out pixels the |
mjr | 100:1ff35c07217c | 517 | // instance ICG goes high, so we have to align our ADC cycle so that |
mjr | 100:1ff35c07217c | 518 | // we start a sample at almost exactly the same time we take ICG |
mjr | 100:1ff35c07217c | 519 | // high. |
mjr | 100:1ff35c07217c | 520 | // |
mjr | 100:1ff35c07217c | 521 | // Now, every ADC sampling cycle always starts at a rising edge of |
mjr | 100:1ff35c07217c | 522 | // the master clock, since the master clock is the ADC trigger. BUT, |
mjr | 100:1ff35c07217c | 523 | // the converse is NOT true: every rising edge of the master clock |
mjr | 100:1ff35c07217c | 524 | // is NOT an ADC sample start. Recall that we've contrived the timing |
mjr | 100:1ff35c07217c | 525 | // so that every OTHER master clock rising edge starts an ADC sample. |
mjr | 100:1ff35c07217c | 526 | // |
mjr | 100:1ff35c07217c | 527 | // So how do we detect which part of the clock cycle we're in? We |
mjr | 100:1ff35c07217c | 528 | // could conceivably use the COCO bit in the ADC status register to |
mjr | 100:1ff35c07217c | 529 | // detect the little window between the end of one sample and the |
mjr | 100:1ff35c07217c | 530 | // start of the next. Unfortunately, this doesn't work: the COCO |
mjr | 100:1ff35c07217c | 531 | // bit is never actually set for the duration of even a single CPU |
mjr | 100:1ff35c07217c | 532 | // instruction in our setup, no matter how loose we make the timing |
mjr | 100:1ff35c07217c | 533 | // between the ADC and the fM cycle. I think the reason is the DMA |
mjr | 100:1ff35c07217c | 534 | // setup: the COCO bit triggers the DMA, and the DMA controller |
mjr | 100:1ff35c07217c | 535 | // reads the ADC result register (the DMA source in our setup), |
mjr | 100:1ff35c07217c | 536 | // which has the side effect of clearing COCO. I've experimented |
mjr | 100:1ff35c07217c | 537 | // with this using different timing parameters, and the result is |
mjr | 100:1ff35c07217c | 538 | // always the same: the CPU *never* sees the COCO bit set. The DMA |
mjr | 100:1ff35c07217c | 539 | // trigger timing is evidently deterministic such that the DMA unit |
mjr | 100:1ff35c07217c | 540 | // invariably gets its shot at reading ADC0->R before the CPU does. |
mjr | 100:1ff35c07217c | 541 | // |
mjr | 100:1ff35c07217c | 542 | // The COCO approach would be a little iffy anyway, since we want the |
mjr | 100:1ff35c07217c | 543 | // ADC idle time to be as short as possible, which wouldn't give us |
mjr | 100:1ff35c07217c | 544 | // much time to do all we have to do in the COCO period, even if |
mjr | 100:1ff35c07217c | 545 | // there were one. What we can do instead is seize control of the |
mjr | 100:1ff35c07217c | 546 | // ADC cycle timing: rather than trying to detect when the cycle |
mjr | 100:1ff35c07217c | 547 | // ends, we can specify when it begins. We can do this by canceling |
mjr | 100:1ff35c07217c | 548 | // the TPM->ADC trigger and aborting any conversion in progress, then |
mjr | 100:1ff35c07217c | 549 | // reprogramming the TPM->ADC trigger at our leisure. What we *can* |
mjr | 100:1ff35c07217c | 550 | // detect reliably is the start of a TPM cycle. So here's our |
mjr | 100:1ff35c07217c | 551 | // strategy: |
mjr | 100:1ff35c07217c | 552 | // |
mjr | 100:1ff35c07217c | 553 | // - Turn off the TPM->ADC trigger and abort the current conversion |
mjr | 100:1ff35c07217c | 554 | // - Wait until a new TPM cycle starts |
mjr | 100:1ff35c07217c | 555 | // - Reset the TPM->ADC trigger. The first new conversion will |
mjr | 100:1ff35c07217c | 556 | // start on the next TPM cycle, so we have the remainder of |
mjr | 100:1ff35c07217c | 557 | // the current TPM cycle to make this happen (about 1us, enough |
mjr | 100:1ff35c07217c | 558 | // for 16 CPU instructions - plenty for this step) |
mjr | 100:1ff35c07217c | 559 | // - Wait for the new TPM cycle |
mjr | 100:1ff35c07217c | 560 | // - End the ICG pulse |
mjr | 100:1ff35c07217c | 561 | // |
mjr | 100:1ff35c07217c | 562 | |
mjr | 100:1ff35c07217c | 563 | // Enable the DMA controller for the new transfer from the ADC. |
mjr | 100:1ff35c07217c | 564 | // The sensor will start clocking out new samples at the ICG rising |
mjr | 100:1ff35c07217c | 565 | // edge, so the next ADC sample to complete will represent the first |
mjr | 100:1ff35c07217c | 566 | // pixel in the new frame. So we need the DMA ready to go at the |
mjr | 100:1ff35c07217c | 567 | // very next sample. Recall that the DMA is triggered by ADC |
mjr | 100:1ff35c07217c | 568 | // completion, and ADC is stopped right now, so enabling the DMA |
mjr | 100:1ff35c07217c | 569 | // won't have any immediate effect - it just spools it up so that |
mjr | 100:1ff35c07217c | 570 | // it's ready to move samples as soon as we resume the ADC. |
mjr | 100:1ff35c07217c | 571 | *dma_chcfg |= DMAMUX_CHCFG_ENBL_MASK; |
mjr | 100:1ff35c07217c | 572 | |
mjr | 100:1ff35c07217c | 573 | // wait for the start of a new master clock cycle |
mjr | 100:1ff35c07217c | 574 | fm.waitEndCycle(); |
mjr | 100:1ff35c07217c | 575 | |
mjr | 109:310ac82cbbee | 576 | // Wait one more cycle to be sure the DMA is ready. Empirically, |
mjr | 109:310ac82cbbee | 577 | // this extra wait is actually required; evidently DMA startup has |
mjr | 109:310ac82cbbee | 578 | // some non-deterministic timing element or perhaps an asynchronous |
mjr | 109:310ac82cbbee | 579 | // external dependency. In any case, *without* this extra wait, |
mjr | 109:310ac82cbbee | 580 | // the DMA transfer sporadically (about 20% probability) misses the |
mjr | 109:310ac82cbbee | 581 | // very first pixel that the sensor clocks out, so the entire image |
mjr | 109:310ac82cbbee | 582 | // is shifted "left" by one pixel. That makes the position sensing |
mjr | 109:310ac82cbbee | 583 | // jitter by a pixel from one frame to the next according to whether |
mjr | 109:310ac82cbbee | 584 | // or not we had that one-pixel delay in the DMA startup. Happily, |
mjr | 109:310ac82cbbee | 585 | // padding the timing by an fM cycle seems to make the DMA startup |
mjr | 109:310ac82cbbee | 586 | // perfectly reliable. |
mjr | 109:310ac82cbbee | 587 | fm.waitEndCycle(); |
mjr | 109:310ac82cbbee | 588 | |
mjr | 100:1ff35c07217c | 589 | // Okay, a master clock cycle just started, so we have about 1us |
mjr | 100:1ff35c07217c | 590 | // (about 16 CPU instructions) before the next one begins. Resume |
mjr | 100:1ff35c07217c | 591 | // ADC sampling. The first new sample will start with the next |
mjr | 109:310ac82cbbee | 592 | // TPM cycle 1us from now. This step itself takes about 3 machine |
mjr | 109:310ac82cbbee | 593 | // instructions for 180ns, so we have about 820ns left to go. |
mjr | 100:1ff35c07217c | 594 | os.resume(); |
mjr | 100:1ff35c07217c | 595 | |
mjr | 104:6e06e0f4b476 | 596 | // Eerything is queued up! We just have to fire the starting gun |
mjr | 104:6e06e0f4b476 | 597 | // on the sensor at the right moment. And that right moment is the |
mjr | 104:6e06e0f4b476 | 598 | // start of the next TPM cycle. Wait for it... |
mjr | 100:1ff35c07217c | 599 | fm.waitEndCycle(); |
mjr | 100:1ff35c07217c | 600 | |
mjr | 100:1ff35c07217c | 601 | // And go! |
mjr | 100:1ff35c07217c | 602 | icg = logicHigh; |
mjr | 100:1ff35c07217c | 603 | |
mjr | 100:1ff35c07217c | 604 | // note the start time of the transfer |
mjr | 100:1ff35c07217c | 605 | tXfer = t.read_us(); |
mjr | 100:1ff35c07217c | 606 | |
mjr | 100:1ff35c07217c | 607 | // return the timestamp of the end of the SH pulse - this is the start |
mjr | 100:1ff35c07217c | 608 | // of the new integration period that we just initiated |
mjr | 100:1ff35c07217c | 609 | return t_sh; |
mjr | 100:1ff35c07217c | 610 | } |
mjr | 100:1ff35c07217c | 611 | |
mjr | 100:1ff35c07217c | 612 | // master clock |
mjr | 100:1ff35c07217c | 613 | NewPwmOut fm; |
mjr | 100:1ff35c07217c | 614 | |
mjr | 100:1ff35c07217c | 615 | // analog input for reading the pixel voltage level |
mjr | 100:1ff35c07217c | 616 | AltAnalogIn_8bit os; |
mjr | 100:1ff35c07217c | 617 | |
mjr | 100:1ff35c07217c | 618 | // Integration Clear Gate output |
mjr | 100:1ff35c07217c | 619 | DigitalOut icg; |
mjr | 100:1ff35c07217c | 620 | |
mjr | 100:1ff35c07217c | 621 | // Shift Gate output |
mjr | 100:1ff35c07217c | 622 | DigitalOut sh; |
mjr | 100:1ff35c07217c | 623 | |
mjr | 100:1ff35c07217c | 624 | // DMA channel for the analog input |
mjr | 100:1ff35c07217c | 625 | SimpleDMA os_dma; |
mjr | 100:1ff35c07217c | 626 | |
mjr | 100:1ff35c07217c | 627 | // Master clock period, in seconds, calculated based on the ADC timing |
mjr | 100:1ff35c07217c | 628 | float masterClockPeriod; |
mjr | 100:1ff35c07217c | 629 | |
mjr | 100:1ff35c07217c | 630 | // Number of pixels. The TCD1103 has 1500 image pixels, plus 32 dummy |
mjr | 100:1ff35c07217c | 631 | // pixels at the front end (before the first image pixel) and another 14 |
mjr | 100:1ff35c07217c | 632 | // dummy pixels at the back end. The sensor always transfers the full |
mjr | 100:1ff35c07217c | 633 | // file on each read cycle, including the dummies, so we have to make |
mjr | 100:1ff35c07217c | 634 | // room for the dummy pixels during each read. |
mjr | 100:1ff35c07217c | 635 | static const int nPixSensor = 1546; |
mjr | 100:1ff35c07217c | 636 | |
mjr | 104:6e06e0f4b476 | 637 | // Figure the number of pixels to allocate per pixel buffer. Round |
mjr | 104:6e06e0f4b476 | 638 | // up to the next 4-byte boundary, so that the buffers are both DWORD- |
mjr | 104:6e06e0f4b476 | 639 | // aligned. (This allows using DWORD pointers into the buffer to |
mjr | 104:6e06e0f4b476 | 640 | // operate on buffer pixels four at a time, such as in the negative |
mjr | 104:6e06e0f4b476 | 641 | // image inversion code in the generic PlungerSensorImage base class.) |
mjr | 104:6e06e0f4b476 | 642 | static const int nPixAlo = (nPixSensor + 3) & ~3; |
mjr | 104:6e06e0f4b476 | 643 | |
mjr | 100:1ff35c07217c | 644 | // pixel buffers - we keep two buffers so that we can transfer the |
mjr | 100:1ff35c07217c | 645 | // current sensor data into one buffer via DMA while we concurrently |
mjr | 100:1ff35c07217c | 646 | // process the last buffer |
mjr | 100:1ff35c07217c | 647 | uint8_t *pix1; // pixel array 1 |
mjr | 100:1ff35c07217c | 648 | uint8_t *pix2; // pixel array 2 |
mjr | 100:1ff35c07217c | 649 | |
mjr | 100:1ff35c07217c | 650 | // Timestamps of pix1 and pix2 arrays, in microseconds, in terms of the |
mjr | 100:1ff35c07217c | 651 | // sample timer (this->t). |
mjr | 100:1ff35c07217c | 652 | uint32_t t1; |
mjr | 100:1ff35c07217c | 653 | uint32_t t2; |
mjr | 100:1ff35c07217c | 654 | |
mjr | 100:1ff35c07217c | 655 | // DMA target buffer. This is the buffer for the next DMA transfer. |
mjr | 100:1ff35c07217c | 656 | // 0 means pix1, 1 means pix2. The other buffer contains the stable |
mjr | 100:1ff35c07217c | 657 | // data from the last transfer. |
mjr | 100:1ff35c07217c | 658 | uint8_t pixDMA; |
mjr | 100:1ff35c07217c | 659 | |
mjr | 101:755f44622abc | 660 | // Stable buffer ownership. At any given time, the DMA subsystem owns |
mjr | 101:755f44622abc | 661 | // the buffer specified by pixDMA. The other buffer - the "stable" buffer, |
mjr | 101:755f44622abc | 662 | // which contains the most recent completed frame, can be owned by EITHER |
mjr | 101:755f44622abc | 663 | // the client or by the DMA subsystem. Each time a DMA transfer completes, |
mjr | 101:755f44622abc | 664 | // the DMA subsystem looks at the stable buffer owner flag to determine |
mjr | 101:755f44622abc | 665 | // what to do: |
mjr | 101:755f44622abc | 666 | // |
mjr | 101:755f44622abc | 667 | // - If the DMA subsystem owns the stable buffer, it swaps buffers. This |
mjr | 101:755f44622abc | 668 | // makes the newly completed DMA buffer the new stable buffer, and makes |
mjr | 101:755f44622abc | 669 | // the old stable buffer the new DMA buffer. At this time, the DMA |
mjr | 101:755f44622abc | 670 | // subsystem also changes the stable buffer ownership to CLIENT. |
mjr | 101:755f44622abc | 671 | // |
mjr | 101:755f44622abc | 672 | // - If the CLIENT owns the stable buffer, the DMA subsystem can't swap |
mjr | 101:755f44622abc | 673 | // buffers, because the client is still using the stable buffer. It |
mjr | 101:755f44622abc | 674 | // simply leaves things as they are. |
mjr | 101:755f44622abc | 675 | // |
mjr | 101:755f44622abc | 676 | // In either case, the DMA system starts a new transfer at this point. |
mjr | 101:755f44622abc | 677 | // |
mjr | 101:755f44622abc | 678 | // The client, meanwhile, is free to access the stable buffer when it has |
mjr | 101:755f44622abc | 679 | // ownership. If the client *doesn't* have ownership, it must wait for |
mjr | 101:755f44622abc | 680 | // the ownership to be transferred, which can only be done by the DMA |
mjr | 101:755f44622abc | 681 | // subsystem on completing a transfer. |
mjr | 101:755f44622abc | 682 | // |
mjr | 101:755f44622abc | 683 | // When the client is done with the stable buffer, it transfers ownership |
mjr | 101:755f44622abc | 684 | // back to the DMA subsystem. |
mjr | 101:755f44622abc | 685 | // |
mjr | 101:755f44622abc | 686 | // Transfers of ownership from DMA to CLIENT are done only by DMA. |
mjr | 101:755f44622abc | 687 | // Transfers from CLIENT to DMA are done only by CLIENT. So whoever has |
mjr | 101:755f44622abc | 688 | // ownership now is responsible for transferring ownership. |
mjr | 101:755f44622abc | 689 | // |
mjr | 101:755f44622abc | 690 | volatile bool clientOwnsStablePix; |
mjr | 101:755f44622abc | 691 | |
mjr | 101:755f44622abc | 692 | // Minimum requested integration time, in microseconds |
mjr | 101:755f44622abc | 693 | uint32_t tIntMin; |
mjr | 101:755f44622abc | 694 | |
mjr | 101:755f44622abc | 695 | // Timeout for generating an interrupt at the end of the integration period |
mjr | 101:755f44622abc | 696 | Timeout integrationTimeout; |
mjr | 101:755f44622abc | 697 | |
mjr | 100:1ff35c07217c | 698 | // timing statistics |
mjr | 100:1ff35c07217c | 699 | Timer t; // sample timer |
mjr | 100:1ff35c07217c | 700 | uint32_t tInt; // start time (us) of current integration period |
mjr | 100:1ff35c07217c | 701 | uint32_t tXfer; // start time (us) of current pixel transfer |
mjr | 100:1ff35c07217c | 702 | uint32_t dtPixXfer; // pixel transfer time of last frame |
mjr | 100:1ff35c07217c | 703 | uint64_t totalXferTime; // total time consumed by all reads so far |
mjr | 100:1ff35c07217c | 704 | uint32_t nRuns; // number of runs so far |
mjr | 100:1ff35c07217c | 705 | |
mjr | 100:1ff35c07217c | 706 | // debugging - min/max transfer time statistics |
mjr | 100:1ff35c07217c | 707 | uint32_t minXferTime; |
mjr | 100:1ff35c07217c | 708 | uint32_t maxXferTime; |
mjr | 100:1ff35c07217c | 709 | }; |