Skip to content

Commit

Permalink
Updated with improvements from CPy port
Browse files Browse the repository at this point in the history
  • Loading branch information
ZodiusInfuser committed Sep 21, 2023
1 parent 2a6bb0a commit 123c81d
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 67 deletions.
65 changes: 46 additions & 19 deletions lib/pimoroni_yukon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import tca
from machine import ADC, Pin
from pimoroni_yukon.modules import KNOWN_MODULES
from pimoroni_yukon.modules.common import ADC_FLOAT, ADC_LOW, ADC_HIGH
from pimoroni_yukon.modules.common import ADC_FLOAT, ADC_LOW, ADC_HIGH, YukonModule
import pimoroni_yukon.logging as logging
from pimoroni_yukon.errors import OverVoltageError, UnderVoltageError, OverCurrentError, OverTemperatureError, FaultError, VerificationError
from pimoroni_yukon.timing import ticks_ms, ticks_add, ticks_diff
Expand Down Expand Up @@ -116,16 +116,21 @@ class Yukon:
ABSOLUTE_MAX_VOLTAGE_LIMIT = 18

DETECTION_SAMPLES = 64
DETECTION_ADC_LOW = 0.1
DETECTION_ADC_LOW = 0.2
DETECTION_ADC_HIGH = 3.2

CURRENT_SENSE_ADDR = 12 # 0b1100
TEMP_SENSE_ADDR = 13 # 0b1101
VOLTAGE_OUT_SENSE_ADDR = 14 # 0b1110
VOLTAGE_IN_SENSE_ADDR = 15 # 0b1111

OUTPUT_STABLISE_TIMEOUT_US = 100 * 1000
OUTPUT_STABLISE_TIME_US = 5 * 1000
OUTPUT_STABLISE_TIMEOUT_US = 200 * 1000
OUTPUT_STABLISE_TIME_US = 10 * 1000
OUTPUT_STABLISE_V_DIFF = 0.1

OUTPUT_DISSIPATE_TIMEOUT_S = 15 # When a bench power module is attached and there is no additional output load, it can take a while for it to return to an idle state
OUTPUT_DISSIPATE_TIMEOUT_US = OUTPUT_DISSIPATE_TIMEOUT_S * 1000 * 1000
OUTPUT_DISSIPATE_LEVEL = 0.4 # The voltage below which we can reliably obtain the address of attached modules

def __init__(self, voltage_limit=DEFAULT_VOLTAGE_LIMIT, current_limit=DEFAULT_CURRENT_LIMIT, temperature_limit=DEFAULT_TEMPERATURE_LIMIT, logging_level=logging.LOG_INFO):
self.__voltage_limit = min(voltage_limit, self.ABSOLUTE_MAX_VOLTAGE_LIMIT)
Expand Down Expand Up @@ -153,8 +158,8 @@ def __init__(self, voltage_limit=DEFAULT_VOLTAGE_LIMIT, current_limit=DEFAULT_CU
# ADC mux enable pins
self.__adc_mux_ens = (Pin.board.ADC_MUX_EN_1,
Pin.board.ADC_MUX_EN_2)
self.__adc_mux_ens[0].init(Pin.OUT, value=False)
self.__adc_mux_ens[1].init(Pin.OUT, value=False)
self.__adc_mux_ens[0].init(Pin.OUT, value=True) # Active low
self.__adc_mux_ens[1].init(Pin.OUT, value=True) # Active low

# ADC mux address pins
self.__adc_mux_addrs = (Pin.board.ADC_ADDR_1,
Expand Down Expand Up @@ -192,25 +197,20 @@ def __init__(self, voltage_limit=DEFAULT_VOLTAGE_LIMIT, current_limit=DEFAULT_CU

self.__monitor_action_callback = None

def reset(self) -> None:
def reset(self):
# Only disable the output if enabled (avoids duplicate messages)
if self.is_main_output_enabled() is True:
self.disable_main_output()

self.__adc_mux_ens[0].value(False)
self.__adc_mux_ens[1].value(False)

self.__adc_mux_addrs[0].value(False)
self.__adc_mux_addrs[1].value(False)
self.__adc_mux_addrs[2].value(False)
self.__deselect_address()

self.__leds[0].value(False)
self.__leds[1].value(False)

# Configure each module so they go back to their default states
for module in self.__slot_assignments.values():
if module is not None and module.is_initialised():
module.configure()
module.reset()

def change_logging(self, logging_level):
logging.level = logging_level
Expand Down Expand Up @@ -244,6 +244,8 @@ def find_slots_with_module(self, module_type):
else:
logging.info(f"No '{module_type.NAME}` module")

logging.info() # New line

return slots

def register_with_slot(self, module, slot):
Expand All @@ -252,6 +254,13 @@ def register_with_slot(self, module, slot):

slot = self.__check_slot(slot)

module_type = type(module)
if module_type is YukonModule:
raise ValueError("Cannot register YukonModule")

if module_type not in KNOWN_MODULES:
raise ValueError(f"{module_type} is not a known module. If this is custom module, be sure to include it in the KNOWN_MODULES list.")

if self.__slot_assignments[slot] is None:
self.__slot_assignments[slot] = module
else:
Expand All @@ -272,6 +281,8 @@ def __match_module(self, adc_level, slow1, slow2, slow3):
for m in KNOWN_MODULES:
if m.is_module(adc_level, slow1, slow2, slow3):
return m
if YukonModule.is_module(adc_level, slow1, slow2, slow3):
return YukonModule
return None

def __detect_module(self, slot):
Expand Down Expand Up @@ -382,6 +393,21 @@ def initialise_modules(self, allow_unregistered=False, allow_undetected=False, a
if self.is_main_output_enabled():
raise RuntimeError("Cannot verify modules whilst the main output is active")

logging.info("> Checking output voltage ...")
if self.read_output_voltage() >= self.OUTPUT_DISSIPATE_LEVEL:
logging.info("> Waiting for output voltage to dissipate ...")

start = time.ticks_us()
while True:
new_voltage = self.read_output_voltage()
if new_voltage < self.OUTPUT_DISSIPATE_LEVEL:
break

new_time = time.ticks_us()
if new_time - start > self.OUTPUT_DISSIPATE_TIMEOUT_US:
raise FaultError("[Yukon] Output voltage did not dissipate in an acceptable time. Aborting module initialisation")


logging.info("> Verifying modules")

self.__verify_modules(allow_unregistered, allow_undetected, allow_discrepencies, allow_no_modules)
Expand Down Expand Up @@ -453,7 +479,7 @@ def enable_main_output(self):
raise OverVoltageError(f"[Yukon] Output voltage of {new_voltage}V exceeded the user set limit of {self.__voltage_limit}V! Turning off output")

new_time = time.ticks_us()
if abs(new_voltage - old_voltage) < 0.05:
if abs(new_voltage - old_voltage) < self.OUTPUT_STABLISE_V_DIFF:
if first_stable_time == 0:
first_stable_time = new_time
elif new_time - first_stable_time > self.OUTPUT_STABLISE_TIME_US:
Expand Down Expand Up @@ -492,8 +518,9 @@ def is_main_output_enabled(self):
return self.__main_en.value() == 1

def __deselect_address(self):
self.__adc_mux_ens[0].value(False)
self.__adc_mux_ens[1].value(False)
# Deselect the muxes and reset the address to zero
state = self.__adc_io_ens_addrs[0] | self.__adc_io_ens_addrs[1]
tca.change_output_mask(self.__adc_io_chip, self.__adc_io_mask, state)

def __select_address(self, address):
if address < 0:
Expand Down Expand Up @@ -593,7 +620,7 @@ def monitor(self):

# Run some user action based on the latest readings
if self.__monitor_action_callback is not None:
self.__monitor_action_callback(voltage_out, current, temperature)
self.__monitor_action_callback(voltage_in, voltage_out, current, temperature)

for module in self.__slot_assignments.values():
if module is not None:
Expand All @@ -606,7 +633,7 @@ def monitor(self):
self.__max_voltage_in = max(voltage_in, self.__max_voltage_in)
self.__min_voltage_in = min(voltage_in, self.__min_voltage_in)
self.__avg_voltage_in += voltage_in

self.__max_voltage_out = max(voltage_out, self.__max_voltage_out)
self.__min_voltage_out = min(voltage_out, self.__min_voltage_out)
self.__avg_voltage_out += voltage_out
Expand Down
4 changes: 4 additions & 0 deletions lib/pimoroni_yukon/conversion.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2023 Christopher Parrott for Pimoroni Ltd
#
# SPDX-License-Identifier: MIT

from math import log

# -----------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions lib/pimoroni_yukon/errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2023 Christopher Parrott for Pimoroni Ltd
#
# SPDX-License-Identifier: MIT

class OverVoltageError(Exception):
"""Exception to be used when a voltage value exceeds safe levels"""
pass
Expand Down
4 changes: 4 additions & 0 deletions lib/pimoroni_yukon/logging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2023 Christopher Parrott for Pimoroni Ltd
#
# SPDX-License-Identifier: MIT

LOG_NONE = 0
LOG_WARN = 1
LOG_INFO = 2
Expand Down
24 changes: 13 additions & 11 deletions lib/pimoroni_yukon/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
#
# SPDX-License-Identifier: MIT

from .led_strip import LEDStripModule
from .quad_servo_direct import QuadServoDirectModule
from .quad_servo_reg import QuadServoRegModule
from .audio_amp import AudioAmpModule
from .bench_power import BenchPowerModule
from .big_motor import BigMotorModule
from .dual_motor import DualMotorModule
from .dual_switched import DualSwitchedModule
from .bench_power import BenchPowerModule
from .audio_amp import AudioAmpModule
from .led_strip import LEDStripModule
from .proto import ProtoPotModule
from .quad_servo_direct import QuadServoDirectModule
from .quad_servo_reg import QuadServoRegModule


KNOWN_MODULES = (
LEDStripModule,
QuadServoDirectModule,
QuadServoRegModule,
AudioAmpModule,
BenchPowerModule,
BigMotorModule,
DualMotorModule,
DualSwitchedModule,
BenchPowerModule,
AudioAmpModule,
ProtoPotModule)
LEDStripModule,
ProtoPotModule,
QuadServoDirectModule,
QuadServoRegModule
)
7 changes: 3 additions & 4 deletions lib/pimoroni_yukon/modules/audio_amp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: MIT

from .common import YukonModule, ADC_FLOAT, HIGH
from .common import YukonModule, ADC_FLOAT, LOW, HIGH
import tca
from machine import Pin
from ucollections import OrderedDict
Expand Down Expand Up @@ -130,11 +130,10 @@ class AudioAmpModule(YukonModule):

# | ADC1 | SLOW1 | SLOW2 | SLOW3 | Module | Condition (if any) |
# |-------|-------|-------|-------|----------------------|-----------------------------|
# | FLOAT | 0 | 1 | 1 | [Proposed] Audio Amp | |
# | FLOAT | 0 | 1 | 1 | Audio Amp | |
@staticmethod
def is_module(adc_level, slow1, slow2, slow3):
# return adc_level == ADC_FLOAT and slow1 is LOW and slow2 is HIGH and slow3 is HIGH
return adc_level == ADC_FLOAT and slow1 is HIGH and slow2 is HIGH and slow3 is HIGH
return adc_level == ADC_FLOAT and slow1 is LOW and slow2 is HIGH and slow3 is HIGH

def __init__(self):
super().__init__()
Expand Down
12 changes: 5 additions & 7 deletions lib/pimoroni_yukon/modules/bench_power.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: MIT

from .common import YukonModule, LOW, HIGH
from .common import YukonModule, ADC_LOW, LOW, HIGH
from machine import Pin, PWM
from ucollections import OrderedDict
from pimoroni_yukon.errors import FaultError, OverTemperatureError
Expand All @@ -26,11 +26,9 @@ class BenchPowerModule(YukonModule):
# | ADC1 | SLOW1 | SLOW2 | SLOW3 | Module | Condition (if any) |
# |-------|-------|-------|-------|----------------------|-----------------------------|
# | LOW | 1 | 0 | 0 | Bench Power | |
# | FLOAT | 1 | 0 | 0 | Bench Power | When V+ is discharging |
# FLOAT address included as may not be able to rely on the ADC level being low
@staticmethod
def is_module(adc_level, slow1, slow2, slow3):
return slow1 is HIGH and slow2 is LOW and slow3 is LOW
return adc_level is ADC_LOW and slow1 is HIGH and slow2 is LOW and slow3 is LOW

def __init__(self, halt_on_not_pgood=False):
super().__init__()
Expand All @@ -42,7 +40,7 @@ def __init__(self, halt_on_not_pgood=False):
def initialise(self, slot, adc1_func, adc2_func):
try:
# Create the voltage pwm object
self.voltage_pwm = PWM(slot.FAST2, freq=250000, duty_u16=0)
self.__voltage_pwm = PWM(slot.FAST2, freq=250000, duty_u16=0)
except ValueError as e:
if slot.ID <= 2 or slot.ID >= 5:
conflicting_slot = (((slot.ID - 1) + 4) % 8) + 1
Expand All @@ -57,7 +55,7 @@ def initialise(self, slot, adc1_func, adc2_func):
super().initialise(slot, adc1_func, adc2_func)

def reset(self):
self.voltage_pwm.duty_u16(0)
self.__voltage_pwm.duty_u16(0)

self.__power_en.init(Pin.OUT, value=False)
self.__power_good.init(Pin.IN)
Expand All @@ -72,7 +70,7 @@ def is_enabled(self):
return self.__motors_en.value() == 1

def __set_pwm(self, percent):
self.voltage_pwm.duty_u16(int(((2 ** 16) - 1) * percent))
self.__voltage_pwm.duty_u16(int(((2 ** 16) - 1) * percent))

def set_target_voltage(self, voltage):
if voltage >= self.VOLTAGE_MID:
Expand Down
13 changes: 8 additions & 5 deletions lib/pimoroni_yukon/modules/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@
#
# SPDX-License-Identifier: MIT

from ucollections import OrderedDict
from collections import OrderedDict
from pimoroni_yukon.conversion import analog_to_temp

ADC_LOW = 0
ADC_HIGH = 1
ADC_FLOAT = 2
ADC_ANY = 3
LOW = False
HIGH = True


class YukonModule:
NAME = "Unnamed"
NAME = "Unknown"

# | ADC1 | SLOW1 | SLOW2 | SLOW3 | Module | Condition (if any) |
# |-------|-------|-------|-------|----------------------|-----------------------------|
# | FLOAT | 1 | 1 | 1 | Empty | |
@staticmethod
def is_module(adc_level, slow1, slow2, slow3):
return False
# This will return true if a slot is detected as not being empty, so as to give useful error information
return adc_level is not ADC_FLOAT or slow1 is not HIGH or slow2 is not HIGH or slow3 is not HIGH

def __init__(self):
self.slot = None
Expand Down Expand Up @@ -78,7 +81,7 @@ def process_readings(self):
pass

def clear_readings(self):
# Clear any readings that may accumulate, such as min, max, or average
# Override this to clear any readings that may accumulate, such as min, max, or average
pass

def __message_header(self):
Expand Down
Loading

0 comments on commit 123c81d

Please sign in to comment.