"""
Filename: main.py
Author: pr0fe550rT0m
Created: 2025-06-24
__updated__="2025-06-26"
MicroPython program for the ClackTronics BYOM Micro Module used to calibrate the VCO modules.
This script controls the BYOM Micro Module which uses a Raspberry Pi Pico.
It reads analog input from a potentiometer (GP27) to select a specific
"octave" voltage, which is then output via PWM (GP6).
The two LEDs (GP3, GP4) provide a binary indication, 00, 01, 10, 11, of the selected octave.
References:
- ClackTronics BYOM: https://clacktronics.co.uk/byom
- Discord Channel: https://discord.com/channels/1212465579298918450/1212475155796852776
- Schematic: https://github.com/clacktronics/EuroClack_BYOM_Modules/blob/main/pdf_schematics/BYOM_Micro_main.pdf
Notes:
- Wokwi simulator does not fully represent analog components like transistors or capacitors.
The CV_OUT circuit is not fully shown in the simulation.
- The 'octave' PWM values were measured on a physical Micro Module with a Volt Meter.
You may need to adjust these values for your specific hardware to achieve accurate voltages.
- I rotated the Pico 180-degrees to match how it is mounted in the Micro Module.
"""
from time import sleep
from machine import Pin, ADC, PWM
# --- Constants ---
# --- PWM Octave Constants ---
# These are 16-bit duty cycle values (0 to 65535)
# Adjust these values to represent your desired discrete PWM outputs.
# Higher octave values produce lower voltages
# C4 is 261.63 Hz, then C3 is 130.81 Hz and C2 is 65.41 Hz, and C1 would be 32.70 Hz, and C0 would be 16.35 Hz
# Note CV (Volts)
# Frequency (Hz)
# C0 0V 16.35
# C1 1V 32.70
# C2 2V 65.41
# C3 3V 130.81
# C4 4V 261.63
# C5 5V 523.26
# C6 6V 1046.50
# C7 7V 2093.00
OCTAVE_PWM_1 = 55000 # C1 voltage equivalent
OCTAVE_PWM_2 = 40400 # C2 voltage equivalent
OCTAVE_PWM_3 = 26000 # C3 voltage equivalent
OCTAVE_PWM_4 = 11800 # C4 voltage equivalent
# Map Hardware Pins
# Analog-to-Digital Converter (ADC) pins for potentiometers.
ADC_POT_GP27 = ADC(Pin(27))
ADC_POT_GP28 = ADC(Pin(28))
# Digital Output Pins for LEDs.
LED_GP3 = Pin(3, Pin.OUT)
LED_GP4 = Pin(4, Pin.OUT)
# Pulse Width Modulation (PWM) output pin for CV_OUT.
PWM_GP6 = PWM(Pin(6))
# --- Function Definitions ---
def set_led_states(state_gp3: bool, state_gp4: bool):
"""
Sets the ON/OFF state of LED_GP3 and LED_GP4.
Args:
state_gp3 (bool): True for LED_GP3 ON, False for OFF.
state_gp4 (bool): True for LED_GP4 ON, False for OFF.
"""
if state_gp3:
LED_GP3.on()
else:
LED_GP3.off()
if state_gp4:
LED_GP4.on()
else:
LED_GP4.off()
def update_cv_output(pot_voltage: float):
"""
Determines and sets the PWM duty cycle for CV_OUT (GP6) based on
the input potentiometer voltage (GP27). Also updates LED indicators.
Args:
pot_voltage (float): The measured voltage from the potentiometer on GP27.
"""
# Use thresholds based on 25%, 50%, 75% of 3.3V
highVoltage = 3.3
threshold_25 = highVoltage * 0.25 # About 1/4 turn
threshold_50 = highVoltage * 0.55 # About 1/2 turn
threshold_75 = highVoltage * 0.85 # about 3/4
if pot_voltage <= threshold_25:
print(f"== Range 1 (0-25%): LED 00, PWM set to Octave 1: {OCTAVE_PWM_1}")
set_led_states(False, False)
PWM_GP6.duty_u16(OCTAVE_PWM_1)
elif pot_voltage <= threshold_50:
print(f"== Range 2 (25-50%): LED 01, PWM set to Octave 2: {OCTAVE_PWM_2}")
set_led_states(False, True)
PWM_GP6.duty_u16(OCTAVE_PWM_2)
elif pot_voltage <= threshold_75:
print(f"== Range 3 (50-75%): LED 10, PWM set to Octave 3: {OCTAVE_PWM_3}")
set_led_states(True, False)
PWM_GP6.duty_u16(OCTAVE_PWM_3)
else: # pot_voltage > threshold_75
print(f"== Range 4 (75-100%): LED 11, PWM set to Octave 4: {OCTAVE_PWM_4}")
set_led_states(True, True)
PWM_GP6.duty_u16(OCTAVE_PWM_4)
# --- Initialization ---
def initialize_hardware():
"""Initializes all hardware components for the BYOM Micro Module."""
PWM_GP6.freq(OCTAVE_PWM_1) # Set PWM frequency once during initialization
print(f"Hardware initialized. PWM frequency set to {OCTAVE_PWM_1} Hz.")
# --- Main Program Loop ---
def main_loop():
"""
The main program loop that continuously reads potentiometer inputs,
updates LED indicators, and sets the CV output voltage.
"""
while True:
# Read raw 16-bit analog values from potentiometers
pot_gp27_raw = ADC_POT_GP27.read_u16()
pot_gp28_raw = ADC_POT_GP28.read_u16()
print(f"Analog raw values: GP27={pot_gp27_raw}, GP28={pot_gp28_raw}")
# Convert raw analog values to voltages (0V to 3.3V)
voltage_gp27 = pot_gp27_raw * (3.3 / 65535)
voltage_gp28 = pot_gp28_raw * (3.3 / 65535)
print(f"Voltages: GP27={voltage_gp27:.3f}V, GP28={voltage_gp28:.3f}V") # Format to 3 decimal places
# Update the CV output and LED states based on GP27's voltage
update_cv_output(voltage_gp27)
print("==========")
sleep(0.1) # Short delay to prevent busy-waiting and allow other tasks
# --- Program Entry Point ---
if __name__ == "__main__":
initialize_hardware()
main_loop()
GP27
GP28
GP3
GP4
CV_OUT
GP6