Examples
This page provides various examples demonstrating different features and use cases of CtrlAer.
Stepper Motor Control
This example demonstrates how to control a stepper motor using two phases. It creates alternating signals that can drive a simple bipolar stepper motor or two coils of a unipolar stepper motor.
Code
from ctrlaer import mux, HIGH, LOW, CtrlAer
def phase1(n_steps):
for i in range(n_steps):
yield HIGH, 50 # Energize for 50ms
yield LOW, 50 # De-energize for 50ms
def phase2(n_steps):
for i in range(n_steps):
yield LOW, 50 # Start de-energized
yield HIGH, 50 # Energize for 50ms
progs = [phase1(10), phase2(10)]
prog = mux(progs)
# Frequency not critical for this application
stepper = CtrlAer(sm_number=0, base_pin=0, n_pins=len(progs), freq=10000)
stepper.run(prog)
How it works
Phase generation
phase1()
andphase2()
create complementary signals- Each phase runs for a specified number of steps
- Each step consists of a HIGH and LOW period
- 50ms timing gives a step rate of 10 steps/second
Signal pattern
Hardware setup
- Components needed:
- Raspberry Pi Pico
- Bipolar stepper motor or half of a unipolar stepper
- Motor driver (e.g., L298N, A4988)
-
Power supply for the motor
-
Connections:
-
Safety notes:
- Never connect the motor directly to the Pico
- Use a proper motor driver rated for your motor's current
- Ensure your power supply can handle the motor's requirements
Ultrasonic Control
This example demonstrates how to generate audio and ultrasonic signals with precise timing control. The audio signal is at 100Hz, and the ultrasonic signal is at the PIO frequency freq
. The latter is also generated with 180 degree phase shift for use with an H-bridge driver to drive an ultrasonic transducer array.
Code
from ctrlaer import mux, HIGH, LOW, OFF, PULSE01, PULSE10, CtrlAer
def low_freq():
while True:
yield HIGH, 5 # 5ms on
yield LOW, 5 # 5ms off
def phase1():
while True:
yield PULSE01, 10 # 10ms pulse
yield OFF, 1000 # 1s off
def phase2():
while True:
yield PULSE10, 10 # 10ms pulse
yield OFF, 1000 # 1s off
progs = [low_freq(), phase1(), phase2(), phase1(), phase2()]
prog = mux(progs)
speaker = CtrlAer(sm_number=0, base_pin=0, n_pins=len(progs), freq=40000)
speaker.run(prog)
How it works
Signal generation
- Three types of signals are generated:
low_freq()
: A simple audio signal at 100Hz (5ms on, 5ms off)phase1()
: PULSE01 pattern with 1-second gapsphase2()
: PULSE10 pattern with 1-second gaps
Signal patterns
Hardware setup
- Components needed:
- Raspberry Pi Pico
- Ultrasonic transducers
- Appropriate drivers/amplifiers if needed
-
Power supply
-
Connections:
Blocking Operations
This example demonstrates advanced control flow using blocking operations and external pin control. It implements a complex timing pattern with variable durations and coordinated actions.
Code
from ctrlaer import ON, OFF, mux, CtrlAer
from machine import Pin
from time import sleep
N = 50 # Number of steps
n = 10 # Repetitions per step
analysis_delay = 5
reaction_delay = 1
off_time = 250
on_time = 100
ctrlaer = CtrlAer(sm_number=0, base_pin=7, n_pins=2, freq=111_500)
air_control = Pin(16, Pin.OUT)
air_control.value(0)
sleep(2)
def amine():
for i in range(N):
amine_time = int(on_time * i / (N - 1))
for j in range(n):
yield OFF, on_time - amine_time
yield ON, amine_time
yield OFF, off_time
ctrlaer.block()
sleep(reaction_delay)
air_control.value(1)
sleep(analysis_delay)
air_control.value(0)
def aldehyde():
for i in range(N):
aldehyde_time = int(on_time * (N - 1 - i) / (N - 1))
for j in range(n):
yield OFF, on_time - aldehyde_time
yield ON, aldehyde_time
yield OFF, off_time
prog = mux([amine(), aldehyde()])
ctrlaer.run(prog)
How it works
Setup
- Creates a controller with 2 pins starting at GP7
- Sets up a separate pin for manual control of solenoid valve on GP16
- Initializes timing parameters:
- 50 steps with variable durations
- Delays for reactions and analysis
- Fixed off time and maximum on time
Signal generation
- Two complementary generators are used to demonstrate changing stoichiometry
amine()
: Increasing ON timealdehyde()
: Decreasing ON time
- Each step includes:
- Variable ON time
- Fixed OFF time
- Optional repetitions
Control flow
- Each amine cycle:
- Generate pulses with increasing duration
- Wait for completion (
block()
) - Wait for reaction to complete
- Activate air control
- Wait for reaction products to clear out of the analyzer
- Deactivate air control
- Aldehyde runs independently with decreasing durations
Hardware setup
- Components needed:
- Raspberry Pi Pico
- 2 vibrating mesh atomizers and drivers
- Solenoid valve and MOSFET driver + freewheeling diode
-
External power supply for valve and aerosol VMAs (typically 6-12V)
-
Connections:
Serial Command Control
This example demonstrates how to control CtrlAer through serial communication from a host computer. The Pico listens for commands in a specific format and executes them in real-time.
Pico Code (listen.py)
from ctrlaer import CtrlAer
from sys import stdin
ctrlaer = CtrlAer(sm_number=0, base_pin=0, n_pins=1, freq=5000)
ctrlaer.listen(stdin)
Host Computer Code (Python with PySerial)
import serial
import time
# Open serial connection to Pico
ser = serial.Serial('/dev/ttyACM0', 115200) # Adjust port as needed
# On Windows, this might be 'COM3' or similar
# On macOS, this might be '/dev/tty.usbmodem14101' or similar
# Wait for connection to establish
time.sleep(1)
# Define commands as (state, duration_ms)
commands = [
(2, 100), # ON for 100ms (PULSE10)
(0, 200), # OFF for 200ms
(3, 150), # HIGH for 150ms
(0, 200), # OFF for 200ms
(1, 100), # PULSE01 for 100ms
]
# Send commands
for state, duration in commands:
cmd = f"{state},{duration}\r\n"
ser.write(cmd.encode())
# Wait for acknowledgment (optional)
response = ser.readline().decode().strip()
print(f"Sent: {cmd.strip()}, Response: {response}")
# End the listening session
ser.write(b"END\r\n")
ser.close()
How it works
Command format
- Commands are sent as text in the format:
<state>,<duration>
state
: Integer value corresponding to CtrlAer constants:0
: LOW/OFF (constant low)1
: PULSE01 (square wave starting low)2
: PULSE10/ON (square wave starting high)3
: HIGH (constant high)duration
: Time in milliseconds- Special command
END
terminates the listening session
Implementation details
- The
listen()
method on the CtrlAer instance creates a generator that: - Reads lines from the input stream (stdin)
- Parses each line into a state and duration
- Yields these as commands to be executed
- Echoes back the command for acknowledgment
- Terminates when "END" is received
Example applications
- Remote control of experiments
- Integration with data acquisition systems
- Automated testing sequences
- Synchronized multi-device operation
Hardware setup
- Raspberry Pi Pico connected via USB to host computer
- Output devices connected to GP0 (adjust as needed)
- No additional components required for basic operation
Timing Control Options
This example demonstrates how to control timing using either milliseconds (default) or raw PIO clock ticks for precise control over period count.
Code
from ctrlaer import ON, OFF, CtrlAer
from time import sleep
FREQ = 100_000 # 100 kHz
ctrlaer = CtrlAer(sm_number=0, base_pin=7, n_pins=1, freq=FREQ)
def step_ms():
# Use millisecond timing (default)
for _ in range(5):
yield ON, 200 # 200 ms square wave
yield OFF, 200 # 200 ms off
ctrlaer.run(step_ms()) # use_ms=True by default
def step_ticks():
# Specify in ticks (square wave counts)
# 20000 ticks = 20000 * 1 / FREQ = 200 ms
for _ in range(5):
yield ON, 20000 # 20000 ticks square wave (200 ms)
yield OFF, 20000 # 20000 ticks off (200 ms)
ctrlaer.run(step_ticks(), use_ms=False) # Explicitly specify raw ticks
How it works
Timing options
use_ms=True
(default): Timing specified in milliseconds- Intuitive for human-readable code
-
CtrlAer automatically converts to appropriate number of ticks
-
use_ms=False
: Timing specified in raw PIO clock ticks - Precise control over exact number of PIO cycles
- Allows defining signals in terms of period count rather than duration
Use cases
- Use milliseconds for:
- Most general-purpose applications
- When timing is conceptualized in terms of real-world duration
-
More readable and maintainable code
-
Use raw ticks for:
- Applications where the exact number of cycles matters
- Synchronizing with external hardware that operates on cycle counts
- Creating precise frequency relationships where ratios of ticks matter more than absolute time
- When working with very fast signals where rounding to milliseconds would cause timing errors