Analog Input/Output Examples - Raspberry Pi Pico¶
The following short Python programs will demonstrate essential operation of the
Raspberry Pi Pico board. These assume one or more analog input circuits are
externally attached to an ADC input. Each can be run by copying the program
into code.py
on the CIRCUITPY drive offered by the board. The text can be
pasted directly from this page, or each file can be downloaded from the
CircuitPython sample code folder on this site.
Related Pages
Analog Input¶
Demonstration of reading a single analog input channel and applying thresholding with hysteresis. For sample circuits, please see Exercise: Analog Sensing with the Pico.
Direct download: analog_input.py.
1# analog_input.py
2
3# Raspberry Pi Pico - Analog Input demo
4
5# Read an analog input with the ADC, drive the onboard LED, and print messages
6# to the serial console only when the input state changes.
7
8import board
9import time
10import analogio
11import digitalio
12
13#---------------------------------------------------------------
14# Set up the hardware: script equivalent to Arduino setup()
15
16# Set up built-in green LED for output.
17led = digitalio.DigitalInOut(board.LED) # GP25
18led.direction = digitalio.Direction.OUTPUT
19
20# Set up an analog input on ADC0 (GP26), which is physically pin 31.
21# E.g., this may be attached to photocell or photointerrupter with associated pullup resistor.
22sensor = analogio.AnalogIn(board.A0)
23
24# These may be need to be adjusted for your particular hardware. The Pico has
25# 12-bit analog-to-digital conversion so the actual conversion has 4096 possible
26# values, but the results are scaled to a 16-bit unsigned integer with range
27# from 0 to 65535.
28lower_threshold = 8000
29upper_threshold = 45000
30
31#---------------------------------------------------------------
32# Run the main loop: script equivalent to Arduino loop()
33
34# The following logic detects input transitions using a state machine with two
35# possible states. The state index reflects the current estimated state of the
36# input. The transition rules apply hysteresis in the form of assymmetric
37# thresholds to reduce switching noise: the sensor value must rise higher than
38# the upper threshold or lower than the lower threshold to trigger a state
39# change.
40state_index = False
41
42while True:
43
44 # Read the sensor once per cycle.
45 sensor_level = sensor.value
46
47 # uncomment the following to print tuples to plot with the mu editor
48 # print((sensor_level,))
49 # time.sleep(0.02) # slow sampling to avoid flooding
50
51 if state_index is False:
52 if sensor_level < lower_threshold:
53 led.value = True
54 state_index = True
55 print("On")
56
57 elif state_index is True:
58 if sensor_level > upper_threshold:
59 led.value = False
60 state_index = False
61 print("Off")
62
63
Analog Input Speed Test¶
Direct download: ain_speed_test.py.
1# ain_speed_test.py
2
3# Raspberry Pi Pico - Analog Input Speed Test
4
5# Read an analog input with the ADC and measure the number of conversions
6# possible per second from CircuitPython. The underlying hardware
7# on the RP2040 chip is rated up to 500 kS/sec.
8
9# Result: actual single-channel conversion rates run about 63 kS/sec.
10# As a control case, an empty loop runs at around 192 kHz.
11
12import board
13import time
14import analogio
15
16#---------------------------------------------------------------
17# Set up an analog input on ADC0 (GP26), which is physically pin 31.
18# E.g., this may be attached to photocell or photointerrupter with associated pullup resistor.
19sensor = analogio.AnalogIn(board.A0)
20
21#---------------------------------------------------------------
22# Run the main loop: script equivalent to Arduino loop()
23
24while True:
25
26 # Read a single ADC channel many times in a tight loop.
27 num_samples = 100000
28 start = time.monotonic_ns()
29 for i in range(num_samples):
30 sensor_level = sensor.value
31 end = time.monotonic_ns()
32 elapsed = 1e-9 * (end - start)
33 rate = num_samples / elapsed
34 print(f"Read {num_samples} samples in {elapsed} seconds, {rate} samples/sec.")
35
36 # Pause briefly.
37 time.sleep(1)
38
39 # Control case: null loop
40 start = time.monotonic_ns()
41 for i in range(num_samples):
42 pass
43 end = time.monotonic_ns()
44 elapsed = 1e-9 * (end - start)
45 rate = num_samples / elapsed
46 print(f"Performed empty loop {num_samples} times in {elapsed} seconds, {rate} loops/sec.")
47
48 # Pause briefly.
49 time.sleep(1)
Gesture Game¶
Filtering of analog signals typically assumes a constant sampling rate. However, not every activity in the system needs to run at this rate, so in practice this usually means that the signal processing activity is interleaved with other computation.
The following example highlights a strategy for interleaving multiple activities running at different rates. The main event loop of the system is kept cycling quickly and polling other objects to allow them to update their state at independent rates. The accelerometer signal processing runs at 100 Hz, the LED blink logic runs at a variable rate but typically around 1-2 Hz, and the main game logic runs at only 2Hz. The game logic implements a state machine which responds to a timed series of tilts of the accelerometer.
Direct downloads:
gestures.py (to be copied into
code.py
on CIRCUITPY)biquad.py (to be copied to CIRCUITPY without changing name)
linear.py (to be copied to CIRCUITPY without changing name)
1# gestures.py
2
3# Raspberry Pi Pico - Accelerometer Processing Demo
4
5# Read an analog accelerometer input with the ADC and detect patterns of motion.
6# Drive the onboard LED and print messages to indicate state.
7# Assumes a three-axis analog accelerometer is connected to the three ADC inputs.
8
9import board
10import time
11import analogio
12import digitalio
13
14# Import filters from the 'signals' directory provided with the course. These
15# files should be copied to the top-level directory of the CIRCUITPY filesystem
16# on the Pico.
17
18import linear
19import biquad
20
21#---------------------------------------------------------------
22
23# Coefficients for Low-Pass Butterworth IIR digital filter, generated using
24# filter_gen.py. Sampling rate: 100.0 Hz, frequency: 20.0 Hz. Filter is order
25# 4, implemented as second-order sections (biquads). Reference:
26# https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
27low_pass_100_20 = [
28 [[ 1.00000000, -0.32897568, 0.06458765], # A coeff for section 0
29 [ 0.04658291, 0.09316581, 0.04658291]], # B coeff for section 0
30 [[ 1.00000000, -0.45311952, 0.46632557], # A coeff for section 1
31 [ 1.00000000, 2.00000000, 1.00000000]], # B coeff for section 1
32]
33
34#---------------------------------------------------------------
35class Accelerometer:
36 def __init__(self):
37 """Interface to a three-axis analog accelerometer including input calibration
38 and filtering."""
39
40 self.x_in = analogio.AnalogIn(board.A0)
41 self.y_in = analogio.AnalogIn(board.A1)
42 self.z_in = analogio.AnalogIn(board.A2)
43 self.sampling_interval = 10000000 # period of 100 Hz in nanoseconds
44 self.sampling_timer = 0
45 self.raw = [0, 0, 0] # most recent raw value
46 self.grav = [0.0, 0.0, 0.0] # current calibrated and smoothed value
47
48 # create a set of filters to smooth the input signal
49 self.filters = [biquad.BiquadFilter(low_pass_100_20) for i in range(3)]
50
51 def poll(self, elapsed):
52 """Polling function to be called as frequently as possible from the event loop
53 to read and process new samples.
54
55 :param elapsed: nanoseconds elapsed since the last cycle.
56 """
57
58 self.sampling_timer -= elapsed
59 if self.sampling_timer < 0:
60 self.sampling_timer += self.sampling_interval
61
62 # read the ADC values as synchronously as possible
63 self.raw = [self.x_in.value, self.y_in.value, self.z_in.value]
64
65 # apply linear calibration to find the unit gravity vector direction
66 self.grav = [linear.map(self.raw[0], 26240, 39120, -1.0, 1.0),
67 linear.map(self.raw[1], 26288, 39360, -1.0, 1.0),
68 linear.map(self.raw[2], 26800, 40128, -1.0, 1.0)]
69
70 # pipe each calibrated value through the filter
71 self.grav = [filt.update(sample) for filt, sample in zip(self.filters, self.grav)]
72
73#---------------------------------------------------------------
74class Blinker:
75 def __init__(self):
76 """Interface to the onboard LED featuring variable rate blinking."""
77 # Set up built-in green LED for output.
78 self.led = digitalio.DigitalInOut(board.LED) # GP25
79 self.led.direction = digitalio.Direction.OUTPUT
80 self.update_timer = 0
81 self.set_rate(1.0)
82
83 def poll(self, elapsed):
84 """Polling function to be called as frequently as possible from the event loop
85 with the nanoseconds elapsed since the last cycle."""
86 self.update_timer -= elapsed
87 if self.update_timer < 0:
88 self.update_timer += self.update_interval
89 self.led.value = not self.led.value
90
91 def set_rate(self, Hz):
92 self.update_interval = int(500000000 / Hz) # blink half-period in nanoseconds
93
94#---------------------------------------------------------------
95class Logic:
96 def __init__(self, accel, blinker):
97 """Application state machine."""
98 self.accel = accel
99 self.blinker = blinker
100 self.update_interval = 500000000 # period of 2 Hz in nanoseconds
101 self.update_timer = 0
102
103 # initialize the state machine
104 self.state = 'init'
105 self.state_changed = False
106 self.state_timer = 0
107
108 def poll(self, elapsed):
109 """Polling function to be called as frequently as possible from the event loop
110 with the nanoseconds elapsed since the last cycle."""
111 self.update_timer -= elapsed
112 if self.update_timer < 0:
113 self.update_timer += self.update_interval
114 self.tick() # evaluate the state machine
115
116 def transition(self, new_state):
117 """Set the state machine to enter a new state on the next tick."""
118 self.state = new_state
119 self.state_changed = True
120 self.state_timer = 0
121 print("Entering state:", new_state)
122
123 def tick(self):
124 """Evaluate the state machine rules."""
125
126 # advance elapsed time in seconds
127 self.state_timer += 1e-9 * self.update_interval
128
129 # set up a flag to process transitions within a state clause
130 entering_state = self.state_changed
131 self.state_changed = False
132
133 # select the state clause to evaluate
134 if self.state == 'init':
135 self.transition('idle')
136
137 elif self.state == 'idle':
138 if entering_state:
139 self.blinker.set_rate(0.5)
140
141 if self.accel.grav[1] < -0.5:
142 self.transition('right')
143
144 elif self.state == 'right':
145 if entering_state:
146 self.blinker.set_rate(1.0)
147
148 if self.state_timer > 2.0:
149 self.transition('idle')
150
151 if self.accel.grav[1] > 0.5:
152 self.transition('left')
153
154 elif self.state == 'left':
155 if entering_state:
156 self.blinker.set_rate(2.0)
157
158 if self.state_timer > 2.0:
159 self.transition('idle')
160
161 if self.accel.grav[0] < -0.5:
162 self.transition('win')
163
164 elif self.state == 'win':
165 if entering_state:
166 self.blinker.set_rate(4.0)
167
168 if self.state_timer > 2.0:
169 self.transition('idle')
170
171#---------------------------------------------------------------
172class Status:
173 def __init__(self, accel, blinker, logic):
174 """Debugging information reporter."""
175 self.accel = accel
176 self.blinker = blinker
177 self.logic = logic
178 # self.update_interval = 1000000000 # period of 1 Hz in nanoseconds
179 self.update_interval = 100000000 # period of 10 Hz in nanoseconds
180 self.update_timer = 0
181
182 def poll(self, elapsed):
183 """Polling function to be called as frequently as possible from the event loop
184 with the nanoseconds elapsed since the last cycle."""
185 self.update_timer -= elapsed
186 if self.update_timer < 0:
187 self.update_timer += self.update_interval
188 print(tuple(self.accel.grav))
189 # print(tuple(self.accel.raw))
190
191#---------------------------------------------------------------
192# Initialize global objects.
193accel = Accelerometer()
194blinker = Blinker()
195logic = Logic(accel, blinker)
196status = Status(accel, blinker, logic)
197
198#---------------------------------------------------------------
199# Main event loop to run each non-preemptive thread.
200
201last_clock = time.monotonic_ns()
202
203while True:
204 # read the current nanosecond clock
205 now = time.monotonic_ns()
206 elapsed = now - last_clock
207 last_clock = now
208
209 # poll each thread
210 accel.poll(elapsed)
211 logic.poll(elapsed)
212 blinker.poll(elapsed)
213 status.poll(elapsed)