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