|
| 1 | +Reading analog with the ADC |
| 2 | +=========================== |
| 3 | + |
| 4 | +So far the camera has been reading digital signals -- a pin is |
| 5 | +either ``0`` or ``1``, a switch is open or closed. Most signals |
| 6 | +that come off real-world sensors are analog: a continuous |
| 7 | +voltage that varies smoothly over some range. A photoresistor |
| 8 | +sweeps through every voltage between the rails as ambient |
| 9 | +brightness changes. A temperature sensor's output drifts a few |
| 10 | +millivolts as a room heats up. A microphone's output rises and |
| 11 | +falls with the sound around it. |
| 12 | + |
| 13 | +An *analog-to-digital converter* (ADC) is the bridge. It |
| 14 | +samples the voltage on a pin and returns an integer that Python |
| 15 | +can read like any other value. |
| 16 | + |
| 17 | +Quantization |
| 18 | +------------ |
| 19 | + |
| 20 | +A digital value cannot represent a continuous voltage exactly. |
| 21 | +The ADC's job is to *quantize* -- snap each sample to the |
| 22 | +nearest of a fixed set of levels. An ``N``-bit ADC has |
| 23 | +``2^N`` levels; a 12-bit converter has 4096 of them spread |
| 24 | +across its input range. |
| 25 | + |
| 26 | +.. figure:: ../figures/analog-quantization.svg |
| 27 | + :alt: A smooth analog curve plotted against time, overlaid |
| 28 | + with a stepped digital approximation. Dashed |
| 29 | + horizontal lines mark the quantization levels; the |
| 30 | + stepped curve snaps to whichever level is nearest the |
| 31 | + analog signal at each sample point. |
| 32 | + |
| 33 | + Quantization: each sample of the analog signal (solid) is |
| 34 | + rounded to one of a finite set of digital levels (stepped |
| 35 | + dashed line). |
| 36 | + |
| 37 | +The voltage between two adjacent levels is the *step size* of |
| 38 | +the ADC; anything smaller than that vanishes into rounding. A |
| 39 | +12-bit ADC over a 3.3 V range has a step size of about |
| 40 | +``3.3 / 4096 ≈ 0.8 mV`` -- fine enough that most signals look |
| 41 | +effectively continuous in software. |
| 42 | + |
| 43 | +The machine.ADC class |
| 44 | +--------------------- |
| 45 | + |
| 46 | +:class:`machine.ADC` wraps one analog input channel. Construct |
| 47 | +it with the pin you want to read, then call |
| 48 | +:meth:`~machine.ADC.read_u16`: |
| 49 | + |
| 50 | +:: |
| 51 | + |
| 52 | + from machine import ADC |
| 53 | + |
| 54 | + adc = ADC("P6") |
| 55 | + value = adc.read_u16() |
| 56 | + print(value) |
| 57 | + |
| 58 | +:meth:`~machine.ADC.read_u16` always returns an unsigned |
| 59 | +16-bit integer between ``0`` and ``65535``. The native ADC |
| 60 | +resolution varies by board (12-bit on STM32, port-specific |
| 61 | +elsewhere); the result is left-aligned into 16 bits so the |
| 62 | +hardware detail does not leak into Python -- a value of |
| 63 | +``65535`` is full-scale regardless of the chip. |
| 64 | + |
| 65 | +The reference voltage -- the input that corresponds to |
| 66 | +full-scale -- depends on the board. Check the |
| 67 | +:doc:`/openmvcam/quickref` for the value on your cam. |
| 68 | +Anything above the reference reads as full-scale (and may |
| 69 | +damage the pin if it exceeds the absolute-maximum input |
| 70 | +voltage). |
| 71 | + |
| 72 | +Converting counts to voltage |
| 73 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 74 | + |
| 75 | +The mapping from counts to voltage is linear, with full-scale |
| 76 | +counts mapping exactly to ``Vref``: |
| 77 | + |
| 78 | +:: |
| 79 | + |
| 80 | + voltage = counts × Vref / 65535 |
| 81 | + |
| 82 | +In code: |
| 83 | + |
| 84 | +:: |
| 85 | + |
| 86 | + VREF = 3.3 # cam-dependent; see the quickref |
| 87 | + counts = adc.read_u16() |
| 88 | + voltage = counts * VREF / 65535 |
| 89 | + print(voltage, "V") |
| 90 | + |
| 91 | +Voltage dividers |
| 92 | +---------------- |
| 93 | + |
| 94 | +Two resistors in series between a voltage rail and ground form |
| 95 | +a *voltage divider*. The node between them sits at a voltage |
| 96 | +set by the ratio of the two resistors: |
| 97 | + |
| 98 | +.. figure:: ../figures/voltage-divider.svg |
| 99 | + :alt: A voltage divider. Vin at the top connects through |
| 100 | + R1 to a node tapped off as V_out, which then connects |
| 101 | + through R2 to ground. |
| 102 | + |
| 103 | + A voltage divider: ``R1`` and ``R2`` in series scale ``Vin`` |
| 104 | + down to ``V_out``. |
| 105 | + |
| 106 | +:: |
| 107 | + |
| 108 | + V_out = Vin × R2 / (R1 + R2) |
| 109 | + |
| 110 | +Equal resistors give half the rail voltage; ``R2`` much |
| 111 | +smaller than ``R1`` puts the tap close to ground; ``R2`` much |
| 112 | +larger puts it close to the rail. |
| 113 | + |
| 114 | +The formula assumes nothing else draws appreciable current |
| 115 | +from ``V_out``. An ADC pin is high-impedance (megohms, |
| 116 | +nanoamps) and easily satisfies that, so a divider feeding an |
| 117 | +ADC behaves as the formula predicts. |
| 118 | + |
| 119 | +Potentiometers |
| 120 | +-------------- |
| 121 | + |
| 122 | +A *potentiometer* is a single physical component that is |
| 123 | +exactly a voltage divider, with a sliding wiper that moves the |
| 124 | +tap between the two ends. Turning the knob changes |
| 125 | +``R1`` and ``R2`` together while keeping their sum (the total |
| 126 | +resistance of the pot) constant. |
| 127 | + |
| 128 | +.. figure:: ../figures/potentiometer-circuit.svg |
| 129 | + :alt: A potentiometer wired between 3.3 V and ground. The |
| 130 | + wiper is tapped off to an ADC pin. |
| 131 | + |
| 132 | + A potentiometer wired as a manual voltage source for the |
| 133 | + ADC: 3.3 V on one end, ground on the other, wiper to the |
| 134 | + pin. |
| 135 | + |
| 136 | +A pot is the canonical input device for trying out the ADC. |
| 137 | +Wire one end to ``3.3 V``, the other to ground, and the wiper |
| 138 | +to an ADC-capable pin; turning the knob sweeps the wiper |
| 139 | +through every voltage between the rails. |
| 140 | + |
| 141 | +:: |
| 142 | + |
| 143 | + import time |
| 144 | + from machine import ADC |
| 145 | + |
| 146 | + pot = ADC("P6") |
| 147 | + VREF = 3.3 |
| 148 | + |
| 149 | + while True: |
| 150 | + counts = pot.read_u16() |
| 151 | + voltage = counts * VREF / 65535 |
| 152 | + print(voltage, "V") |
| 153 | + time.sleep_ms(100) |
| 154 | + |
| 155 | +Reading higher voltages with a divider |
| 156 | +-------------------------------------- |
| 157 | + |
| 158 | +A voltage above ``Vref`` will pin the ADC at full-scale and may |
| 159 | +damage the input if it exceeds the absolute-maximum rating. To |
| 160 | +read a higher source -- a battery, a sensor output that ranges |
| 161 | +beyond ``Vref`` -- scale it down with a fixed voltage divider |
| 162 | +before it reaches the pin: |
| 163 | + |
| 164 | +.. figure:: ../figures/adc-divider.svg |
| 165 | + :alt: A voltage divider scaling a high V_in down to an ADC |
| 166 | + pin. R1 runs from V_in down to a junction, which is |
| 167 | + tapped off horizontally to the ADC pin; R2 continues |
| 168 | + from the junction down to ground. |
| 169 | + |
| 170 | + Scaling a high-voltage source to fit the ADC: ``R1`` and |
| 171 | + ``R2`` form a fixed voltage divider whose tap feeds the |
| 172 | + ADC pin. |
| 173 | + |
| 174 | +Pick ``R1`` and ``R2`` so the divided voltage stays inside the |
| 175 | +ADC's range at the highest input voltage you expect: |
| 176 | + |
| 177 | +:: |
| 178 | + |
| 179 | + V_adc = V_in × R2 / (R1 + R2) |
| 180 | + |
| 181 | +For a maximum ``V_in = 12 V`` and a 3.3 V reference, the ratio |
| 182 | +``R2 / (R1 + R2)`` must be at most ``3.3 / 12 ≈ 0.275``. A |
| 183 | +common pick with a little headroom is ``R1 = 33 kΩ``, |
| 184 | +``R2 = 10 kΩ``. The ratio is ``10 / 43 ≈ 0.233``, so |
| 185 | +``V_adc`` tops out at about ``12 × 0.233 ≈ 2.79 V`` -- safely |
| 186 | +below ``Vref``. |
| 187 | + |
| 188 | +To recover the original ``V_in`` from an ADC reading, invert |
| 189 | +the divider formula: |
| 190 | + |
| 191 | +:: |
| 192 | + |
| 193 | + V_in = V_adc × (R1 + R2) / R2 |
| 194 | + |
| 195 | +In code: |
| 196 | + |
| 197 | +:: |
| 198 | + |
| 199 | + from machine import ADC |
| 200 | + |
| 201 | + R1 = 33_000 |
| 202 | + R2 = 10_000 |
| 203 | + VREF = 3.3 |
| 204 | + |
| 205 | + adc = ADC("P6") |
| 206 | + |
| 207 | + counts = adc.read_u16() |
| 208 | + v_adc = counts * VREF / 65535 |
| 209 | + v_in = v_adc * (R1 + R2) / R2 |
| 210 | + print(v_in, "V") |
| 211 | + |
| 212 | +A few practical notes: |
| 213 | + |
| 214 | +* The divider draws ``V_in / (R1 + R2)`` continuously. With |
| 215 | + ``R1 + R2 = 43 kΩ`` and ``V_in = 12 V``, that is about |
| 216 | + 280 µA -- usually negligible, but if the source is |
| 217 | + battery-powered consider larger resistors (100 kΩ to 1 MΩ) |
| 218 | + to cut idle drain. |
| 219 | +* Resistor tolerance (typically ±1 % or ±5 %) feeds directly |
| 220 | + into measurement accuracy. Two ±5 % resistors can give the |
| 221 | + recovered ``V_in`` a worst-case error of roughly ±10 %. |
| 222 | +* The divider's source impedance combines with any stray |
| 223 | + capacitance to low-pass-filter the input. For fast-changing |
| 224 | + signals that matters; for a battery-voltage check it does |
| 225 | + not. |
0 commit comments