11 KiB
Enhanced MicroPython for Jumperless V5
This document describes the enhanced MicroPython implementation for Jumperless V5 that adds:
- Runtime Module Import - Import .py and .mpy modules from the filesystem
- Machine Module Support - Standard MicroPython machine classes integrated with Jumperless hardware
- Filesystem Integration - Full filesystem access for scripts and modules
- Backward Compatibility - All existing Jumperless functions continue to work
New Features
🔧 Machine Module Support
The standard MicroPython machine
module is now available with Jumperless-specific integration:
Pin Class
Control GPIO pins using node numbers or names:
from machine import Pin
# Using node numbers (breadboard nodes)
pin1 = Pin(1, Pin.OUT) # Breadboard node 1
pin13 = Pin(13, Pin.OUT) # Breadboard node 13
# Using GPIO numbers directly
pin_gpio = Pin(26, Pin.IN) # RP2350 GPIO 26
# Pin operations
pin1.on() # Set HIGH
pin1.off() # Set LOW
state = pin1.value() # Read state (0 or 1)
pin1.value(1) # Set state
# Pin modes
pin_in = Pin(2, Pin.IN) # Input
pin_out = Pin(3, Pin.OUT, value=1) # Output, initially HIGH
pin_pullup = Pin(4, Pin.IN, Pin.PULL_UP) # Input with pullup
ADC Class
Read analog values from ADC channels:
from machine import ADC
# Create ADC on GPIO 26 (NANO_A0 equivalent)
adc0 = ADC(26) # GPIO 26 = ADC channel 0
adc1 = ADC(27) # GPIO 27 = ADC channel 1
adc2 = ADC(28) # GPIO 28 = ADC channel 2
adc3 = ADC(29) # GPIO 29 = ADC channel 3
# Read values
reading = adc0.read_u16() # Returns 0-65535
voltage = (reading / 65535) * 3.3 # Convert to voltage
PWM Class (for DAC)
Generate PWM signals and control DAC outputs:
from machine import Pin, PWM
# Regular PWM on any pin
pwm = PWM(Pin(2))
pwm.duty_u16(32768) # 50% duty cycle
# DAC control via PWM (TOP_RAIL and BOTTOM_RAIL)
dac_top = PWM(Pin(16)) # TOP_RAIL (DAC channel 2)
dac_bottom = PWM(Pin(17)) # BOTTOM_RAIL (DAC channel 3)
# Set voltage via duty cycle (-8V to +8V range)
# 0V = duty 32768, 3.3V = duty 46244, -3.3V = duty 19289
duty_3v3 = int(((3.3 + 8.0) / 16.0) * 65535)
dac_top.duty_u16(duty_3v3) # Set TOP_RAIL to 3.3V
📁 Filesystem and Module Import
Module Search Paths
MicroPython now searches these directories for modules:
import sys
print(sys.path)
# Output:
# ['', '/python_scripts', '/python_scripts/lib', '/python_scripts/modules', '/lib', '/modules']
Creating Custom Modules
Create .py
files in python_scripts/lib/
for automatic import:
File: python_scripts/lib/sensors.py
# Custom sensor library
from machine import ADC
def read_all_adcs():
"""Read all 4 ADC channels and return voltages"""
voltages = []
for channel in range(4):
adc = ADC(26 + channel)
reading = adc.read_u16()
voltage = (reading / 65535) * 3.3
voltages.append(voltage)
return voltages
def temperature_from_adc(adc_channel, r_ref=10000):
"""Convert ADC reading to temperature (assuming thermistor)"""
adc = ADC(26 + adc_channel)
reading = adc.read_u16()
voltage = (reading / 65535) * 3.3
# Simple thermistor calculation (customize for your sensor)
if voltage > 0:
resistance = r_ref * (3.3 - voltage) / voltage
# Steinhart-Hart equation (simplified)
temp_k = 1.0 / (0.001129148 + 0.000234125 * log(resistance) + 0.0000000876741 * log(resistance)**3)
temp_c = temp_k - 273.15
return temp_c
return 0.0
Usage in REPL:
import sensors
# Read all ADC channels
voltages = sensors.read_all_adcs()
print(f"ADC voltages: {voltages}")
# Get temperature from ADC 0
temp = sensors.temperature_from_adc(0)
print(f"Temperature: {temp:.1f}°C")
Importing .mpy Files
Compiled MicroPython bytecode files (.mpy) are also supported for faster loading:
- Compile Python to bytecode:
mpy-cross your_module.py
- Copy
your_module.mpy
topython_scripts/lib/
- Import normally:
import your_module
🔗 Backward Compatibility
All existing Jumperless functions continue to work exactly as before:
# Traditional Jumperless functions (still work)
connect(1, 30)
disconnect(1, 30)
dac_set(TOP_RAIL, 3.3)
voltage = adc_get(0)
gpio_set(13, 1)
is_connected(1, 30)
# Node constants still available
TOP_RAIL, BOTTOM_RAIL, DAC0, DAC1, etc.
# All function aliases work
set_dac(TOP_RAIL, 3.3) # Same as dac_set()
get_adc(0) # Same as adc_get()
🎯 Combining Both Approaches
Mix traditional Jumperless functions with machine module:
from machine import Pin, ADC, PWM
# Set up connections using traditional functions
connect(TOP_RAIL, 1) # Connect top rail to breadboard node 1
connect(5, 30) # Connect breadboard nodes 5 and 30
# Control hardware using machine module
led = Pin(13, Pin.OUT) # LED on breadboard node 13
sensor = ADC(26) # Sensor on ADC channel 0
dac = PWM(Pin(16)) # DAC control for top rail
# Main loop
while True:
# Read sensor
reading = sensor.read_u16()
voltage = (reading / 65535) * 3.3
# Control LED based on sensor
if voltage > 1.5:
led.on()
else:
led.off()
# Set DAC output based on sensor
dac_voltage = voltage * 2 # Scale up
duty = int(((dac_voltage + 8.0) / 16.0) * 65535)
dac.duty_u16(duty)
time.sleep(0.1)
Pin Mapping Reference
Node to GPIO Mapping
Node Name | Node # | GPIO | Function |
---|---|---|---|
1-30 | 1-30 | 0-31 | Breadboard nodes |
NANO_D0 | 60 | 0 | UART RX |
NANO_D1 | 61 | 1 | UART TX |
NANO_D2 | 62 | 2 | Digital I/O |
NANO_D3 | 63 | 3 | Digital I/O, PWM |
NANO_D13 | 73 | 13 | Digital I/O, LED |
NANO_A0 | 100 | 26 | ADC0 |
NANO_A1 | 101 | 27 | ADC1 |
NANO_A2 | 102 | 28 | ADC2 |
NANO_A3 | 103 | 29 | ADC3 |
NANO_A4 | 104 | 4 | I2C SDA |
NANO_A5 | 105 | 5 | I2C SCL |
TOP_RAIL | 101 | 16 | DAC channel 2 |
BOTTOM_RAIL | 102 | 17 | DAC channel 3 |
DAC0 | 106 | 16 | DAC channel 2 |
DAC1 | 107 | 17 | DAC channel 3 |
Communication Interfaces
Interface | Pins | GPIO |
---|---|---|
I2C0 | SDA=4, SCL=5 | Primary I2C |
I2C1 | SDA=22, SCL=23 | Secondary I2C |
SPI0 | MOSI=23, MISO=20, SCK=22, CS=21 | Primary SPI |
SPI1 | MOSI=27, MISO=24, SCK=26, CS=25 | Secondary SPI |
UART0 | TX=0, RX=1 | USB Serial |
UART1 | TX=24, RX=25 | Hardware Serial |
Example Projects
1. Simple LED Blink
from machine import Pin
import time
led = Pin(13, Pin.OUT)
while True:
led.on()
time.sleep(0.5)
led.off()
time.sleep(0.5)
2. Analog Sensor Monitor
from machine import ADC
import time
sensor = ADC(26) # ADC0
while True:
reading = sensor.read_u16()
voltage = (reading / 65535) * 3.3
print(f"Sensor: {voltage:.3f}V")
time.sleep(1)
3. Voltage Controller
from machine import Pin, ADC, PWM
import time
# Set up hardware
sensor = ADC(26) # Input sensor
output_dac = PWM(Pin(16)) # TOP_RAIL DAC
led = Pin(13, Pin.OUT) # Status LED
target_voltage = 2.0 # Target 2V output
while True:
# Read input
reading = sensor.read_u16()
input_voltage = (reading / 65535) * 3.3
# Calculate output (simple proportional control)
error = target_voltage - input_voltage
output_voltage = target_voltage + (error * 0.5) # Simple P controller
# Clamp output to valid range (-8V to +8V)
output_voltage = max(-8, min(8, output_voltage))
# Set DAC output
duty = int(((output_voltage + 8.0) / 16.0) * 65535)
output_dac.duty_u16(duty)
# Status indicator
if abs(error) < 0.1:
led.on() # Close to target
else:
led.off() # Adjusting
print(f"In: {input_voltage:.2f}V, Out: {output_voltage:.2f}V, Error: {error:.2f}V")
time.sleep(0.1)
Development Tips
1. Module Development
- Place reusable code in
python_scripts/lib/
- Use descriptive module names
- Include docstrings and comments
- Test modules in REPL before using in scripts
2. Debugging
# Enable verbose output
import sys
print(f"Python version: {sys.version}")
print(f"Module paths: {sys.path}")
# Check available modules
import os
print("Available modules:")
for path in sys.path:
try:
files = os.listdir(path)
print(f" {path}: {files}")
except:
print(f" {path}: (not accessible)")
3. Performance
- Use .mpy files for large modules (faster loading)
- Avoid complex imports in tight loops
- Cache frequently-used objects
4. Memory Management
import gc
# Check memory usage
print(f"Free memory: {gc.mem_free()} bytes")
print(f"Allocated memory: {gc.mem_alloc()} bytes")
# Force garbage collection
gc.collect()
Migration Guide
From Legacy Jumperless Code
- Keep existing code - it will continue to work
- Gradually adopt machine module for new features
- Create modules for commonly-used functions
- Mix approaches as needed
Example Migration
Before:
# Legacy approach
gpio_set(13, 1)
voltage = adc_get(0)
dac_set(TOP_RAIL, voltage)
After (mixed approach):
# Mixed approach - use what's most convenient
from machine import Pin, ADC, PWM
led = Pin(13, Pin.OUT)
sensor = ADC(26)
# Still use legacy DAC function (simpler for this case)
voltage = sensor.read_u16() / 65535 * 3.3
led.on() if voltage > 1.5 else led.off()
dac_set(TOP_RAIL, voltage) # Legacy function still works
Troubleshooting
Common Issues
1. Module not found
ImportError: No module named 'my_module'
- Check file is in
python_scripts/lib/
- Verify filename matches import name
- Check for typos
2. Machine module not available
ImportError: No module named 'machine'
- Ensure you're using enhanced MicroPython build
- Check board configuration includes machine module
3. Pin name not recognized
ValueError: Unknown pin name
- Use GPIO numbers instead of names in current implementation
- Refer to pin mapping table above
4. Out of memory
MemoryError: memory allocation failed
- Use
gc.collect()
to free memory - Reduce module imports
- Use .mpy files instead of .py
Getting Help
- Check examples - Run
simple_machine_example.py
- Use help() -
help(Pin)
,help(ADC)
, etc. - Check memory -
gc.mem_free()
- Verify paths -
print(sys.path)
Technical Implementation
Files Modified/Added
lib/micropython/boards/JUMPERLESS_V5/mpconfigboard.h
- Board configurationlib/micropython/boards/JUMPERLESS_V5/pins.csv
- Pin definitionslib/micropython/port/mpconfigport.h
- Port configurationlib/micropython/port/machine_jumperless.c
- Machine module implementationsrc/Python_Proper.cpp
- Filesystem and path setup
Build System Integration
- Machine module automatically registered via
MP_REGISTER_MODULE
- Board definitions include peripheral mappings
- Filesystem mounted and paths configured at startup
- QSTR definitions auto-generated during build
This enhanced MicroPython implementation provides a powerful, flexible platform for Jumperless development while maintaining complete backward compatibility with existing code.