Raspberry pi pico analog Input/Output Example

Here are the following short Python programs that will illustrate how the Raspberry Pi Pico board works. These presume that one or more analog input circuits are connected from the outside to an ADC input. Every one of them can be run by copying the program into code. py on the CIRCUITPY drive provided on the board When you complete these steps, CircuitPython will boot up and be ready to go. The text can be copied and pasted directly from this page, or each file can be click on ‘Download: .py’ at the bottom of each sample code page in the CircuitPython sample code folder on this site.

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.

# 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.

# 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 analog signals typically assumes a consistent sampling frequency. However, not all system processes require this same high rate, so in practice, signal processing is often interleaved with other computations.

This example demonstrates one strategy for intermixing activities running at different speeds. The core event loop cycles rapidly, polling objects to update independently. Accelerometer signal processing occurs at 100 Hz, LED blinking typically 1-2 Hz, and game logic runs at 2 Hz.

The game logic employs a state machine responding to timed accelerometer tilts. On each main loop iteration:

  • Accelerometer sampling takes place at 100 Hz
  • LED blinking logic updates its state (1-2 Hz typical rate)
  • The game logic state machine checks for transitions every 2 Hz
    This allows maintaining a uniform fast loop rate while spreading computations over various processing elements according to their individual timing needs, rather than tying all activity to a single constrained rate.
A sample circuit for connecting a three-channel analog accelerometer, useful for measuring tilt and motion.

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)
# 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)

Source: Raspberry pi pico analog Input/Output Example


About The Author

Muhammad Bilal

I am highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.

Scroll to Top