Skip to content

Commit 8e5f94d

Browse files
kwagyemanclaude
andcommitted
docs: add Hardware Control tutorial super-section
Eleven captioned subsections covering the `machine` module and the electronics needed to drive devices from an OpenMV cam: - Foundations -- microcontrollers, timing, virtual timers, pins - GPIO output / input -- electronics primers and debouncing - Analog signals -- ADC, PWM-through-RC, voltage dividers - PWM applications -- LED dimming, H-bridge motors, servos - Pulse-timed I/O -- bitstream, time_pulse_us - UART / SPI / I2C / CAN -- framing, dual-CRC packet protocol - Production patterns -- watchdog, RTC, low-power - Wrap-up Replaces the deprecated pyb-based tutorial pages (analog_io, gpio_control, io_tutorial, led_control, uart_control). Renames python/iteration/async-and-await.rst to coroutines.rst with a concept-first rewrite, and adds `yield from` to iterators-and-generators.rst. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8cd98e4 commit 8e5f94d

74 files changed

Lines changed: 6744 additions & 419 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/openmvcam/tutorial/analog_io.rst

Lines changed: 0 additions & 35 deletions
This file was deleted.

docs/openmvcam/tutorial/gpio_control.rst

Lines changed: 0 additions & 52 deletions
This file was deleted.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

Comments
 (0)