2021-07-28 11:21:27 +01:00
|
|
|
import time
|
2021-08-12 15:47:59 +01:00
|
|
|
from machine import Pin, PWM, ADC
|
|
|
|
|
|
|
|
|
2022-01-27 10:50:37 +00:00
|
|
|
BREAKOUT_GARDEN_I2C_PINS = {"sda": 4, "scl": 5}
|
|
|
|
PICO_EXPLORER_I2C_PINS = {"sda": 20, "scl": 21}
|
2022-03-26 00:08:41 +00:00
|
|
|
HEADER_I2C_PINS = {"sda": 20, "scl": 21}
|
2022-01-27 10:50:37 +00:00
|
|
|
|
|
|
|
|
2021-08-12 15:47:59 +01:00
|
|
|
class Analog:
|
2022-03-18 22:35:59 +00:00
|
|
|
def __init__(self, pin, amplifier_gain=1, resistor=0, offset=0):
|
2021-08-12 15:47:59 +01:00
|
|
|
self.gain = amplifier_gain
|
|
|
|
self.resistor = resistor
|
2022-03-18 22:35:59 +00:00
|
|
|
self.offset = offset
|
2021-08-12 15:47:59 +01:00
|
|
|
self.pin = ADC(pin)
|
|
|
|
|
|
|
|
def read_voltage(self):
|
2022-03-18 22:35:59 +00:00
|
|
|
return max((((self.pin.read_u16() * 3.3) / 65535) + self.offset) / self.gain, 0.0)
|
2021-08-12 15:47:59 +01:00
|
|
|
|
|
|
|
def read_current(self):
|
|
|
|
if self.resistor > 0:
|
|
|
|
return self.read_voltage() / self.resistor
|
|
|
|
else:
|
|
|
|
return self.read_voltage()
|
2021-07-28 11:21:27 +01:00
|
|
|
|
|
|
|
|
2022-03-18 22:35:59 +00:00
|
|
|
class AnalogMux:
|
|
|
|
def __init__(self, addr0, addr1=None, addr2=None, en=None, muxed_pin=None):
|
|
|
|
self.addr0_pin = Pin(addr0, Pin.OUT)
|
|
|
|
self.addr1_pin = Pin(addr1, Pin.OUT) if addr1 is not None else None
|
|
|
|
self.addr2_pin = Pin(addr2, Pin.OUT) if addr2 is not None else None
|
|
|
|
self.en_pin = Pin(en, Pin.OUT) if en is not None else None
|
|
|
|
self.max_address = 0b001
|
|
|
|
if addr1 is not None:
|
|
|
|
self.max_address = 0b011
|
|
|
|
if addr2 is not None:
|
|
|
|
self.max_address = 0b111
|
|
|
|
self.pulls = [None] * (self.max_address + 1)
|
|
|
|
self.muxed_pin = muxed_pin
|
|
|
|
|
|
|
|
def select(self, address):
|
|
|
|
if address < 0:
|
|
|
|
raise ValueError("address is less than zero")
|
|
|
|
elif address > self.max_address:
|
|
|
|
raise ValueError("address is greater than number of available addresses")
|
|
|
|
else:
|
|
|
|
if self.muxed_pin and self.pulls[address] is None:
|
|
|
|
self.muxed_pin.init(Pin.IN, None)
|
|
|
|
|
|
|
|
self.addr0_pin.value(address & 0b001)
|
|
|
|
|
|
|
|
if self.addr1_pin is not None:
|
|
|
|
self.addr1_pin.value(address & 0b010)
|
|
|
|
|
|
|
|
if self.addr2_pin is not None:
|
|
|
|
self.addr2_pin.value(address & 0b100)
|
|
|
|
|
|
|
|
if self.en_pin is not None:
|
|
|
|
self.en_pin.value(1)
|
|
|
|
|
|
|
|
if self.muxed_pin and self.pulls[address] is not None:
|
|
|
|
self.muxed_pin.init(Pin.IN, self.pulls[address])
|
|
|
|
|
|
|
|
def disable(self):
|
|
|
|
if self.en_pin is not None:
|
|
|
|
self.en_pin.value(0)
|
|
|
|
else:
|
|
|
|
raise RuntimeError("there is no enable pin assigned to this mux")
|
|
|
|
|
|
|
|
def configure_pull(self, address, pull=None):
|
|
|
|
if address < 0:
|
|
|
|
raise ValueError("address is less than zero")
|
|
|
|
elif address > self.max_address:
|
|
|
|
raise ValueError("address is greater than number of available addresses")
|
|
|
|
else:
|
|
|
|
self.pulls[address] = pull
|
|
|
|
|
|
|
|
|
2021-07-28 11:21:27 +01:00
|
|
|
class Button:
|
|
|
|
def __init__(self, button, invert=True, repeat_time=200, hold_time=1000):
|
|
|
|
self.invert = invert
|
|
|
|
self.repeat_time = repeat_time
|
|
|
|
self.hold_time = hold_time
|
|
|
|
self.pin = Pin(button, pull=Pin.PULL_UP if invert else Pin.PULL_DOWN)
|
|
|
|
self.last_state = False
|
|
|
|
self.pressed = False
|
|
|
|
self.pressed_time = 0
|
|
|
|
|
|
|
|
def read(self):
|
|
|
|
current_time = time.ticks_ms()
|
|
|
|
state = self.raw()
|
|
|
|
changed = state != self.last_state
|
|
|
|
self.last_state = state
|
|
|
|
|
|
|
|
if changed:
|
|
|
|
if state:
|
|
|
|
self.pressed_time = current_time
|
|
|
|
self.pressed = True
|
|
|
|
self.last_time = current_time
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
self.pressed_time = 0
|
|
|
|
self.pressed = False
|
|
|
|
self.last_time = 0
|
|
|
|
|
|
|
|
if self.repeat_time == 0:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self.pressed:
|
|
|
|
repeat_rate = self.repeat_time
|
|
|
|
if self.hold_time > 0 and current_time - self.pressed_time > self.hold_time:
|
|
|
|
repeat_rate /= 3
|
|
|
|
if current_time - self.last_time > repeat_rate:
|
|
|
|
self.last_time = current_time
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def raw(self):
|
|
|
|
if self.invert:
|
|
|
|
return not self.pin.value()
|
|
|
|
else:
|
|
|
|
return self.pin.value()
|
|
|
|
|
|
|
|
|
|
|
|
class RGBLED:
|
|
|
|
def __init__(self, r, g, b, invert=True):
|
|
|
|
self.invert = invert
|
|
|
|
self.led_r = PWM(Pin(r))
|
|
|
|
self.led_r.freq(1000)
|
|
|
|
self.led_g = PWM(Pin(g))
|
|
|
|
self.led_g.freq(1000)
|
|
|
|
self.led_b = PWM(Pin(b))
|
|
|
|
self.led_b.freq(1000)
|
|
|
|
|
|
|
|
def set_rgb(self, r, g, b):
|
|
|
|
if self.invert:
|
|
|
|
r = 255 - r
|
|
|
|
g = 255 - g
|
|
|
|
b = 255 - b
|
2021-08-17 13:07:34 +01:00
|
|
|
self.led_r.duty_u16(int((r * 65535) / 255))
|
|
|
|
self.led_g.duty_u16(int((g * 65535) / 255))
|
|
|
|
self.led_b.duty_u16(int((b * 65535) / 255))
|
2022-03-28 18:32:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Motor:
|
|
|
|
FAST_DECAY = 0 # Recirculation current fast decay mode (coasting)
|
|
|
|
SLOW_DECAY = 1 # Recirculation current slow decay mode (braking)
|
|
|
|
|
|
|
|
def __init__(self, pos, neg, freq=25000, decay_mode=SLOW_DECAY):
|
|
|
|
self.speed = 0.0
|
|
|
|
self.freq = freq
|
|
|
|
if decay_mode in (self.FAST_DECAY, self.SLOW_DECAY):
|
|
|
|
self.decay_mode = decay_mode
|
|
|
|
else:
|
|
|
|
raise ValueError("Decay mode value must be either Motor.FAST_DECAY or Motor.SLOW_DECAY")
|
|
|
|
|
|
|
|
self.pos_pwm = PWM(Pin(pos))
|
|
|
|
self.pos_pwm.freq(freq)
|
|
|
|
self.neg_pwm = PWM(Pin(neg))
|
|
|
|
self.neg_pwm.freq(freq)
|
|
|
|
|
|
|
|
def get_speed(self):
|
|
|
|
return self.speed
|
|
|
|
|
|
|
|
def set_speed(self, speed):
|
|
|
|
if speed > 1.0 or speed < -1.0:
|
|
|
|
raise ValueError("Speed must be between -1.0 and +1.0")
|
|
|
|
self.speed = speed
|
|
|
|
self._update_pwm()
|
|
|
|
|
|
|
|
def get_frequency(self):
|
|
|
|
return self.freq
|
|
|
|
|
|
|
|
def set_frequency(self, freq):
|
|
|
|
self.pos_pwm.freq(freq)
|
|
|
|
self.neg_pwm.freq(freq)
|
|
|
|
self._update_pwm()
|
|
|
|
|
|
|
|
def get_decay_mode(self):
|
|
|
|
return self.decay_mode
|
|
|
|
|
|
|
|
def set_decay_mode(self, mode):
|
|
|
|
if mode in (self.FAST_DECAY, self.SLOW_DECAY):
|
|
|
|
self.decay_mode = mode
|
|
|
|
self._update_pwm()
|
|
|
|
else:
|
|
|
|
raise ValueError("Decay mode value must be either Motor.FAST_DECAY or Motor.SLOW_DECAY")
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self.speed = 0.0
|
|
|
|
self._update_pwm()
|
|
|
|
|
|
|
|
def disable(self):
|
|
|
|
self.speed = 0.0
|
|
|
|
self.pos_pwm.duty_u16(0)
|
|
|
|
self.neg_pwm.duty_u16(0)
|
|
|
|
|
|
|
|
def _update_pwm(self):
|
|
|
|
signed_duty_cycle = int(self.speed * 0xFFFF)
|
|
|
|
|
|
|
|
if self.decay_mode is self.SLOW_DECAY: # aka 'Braking'
|
|
|
|
if signed_duty_cycle >= 0:
|
|
|
|
self.pos_pwm.duty_u16(0xFFFF)
|
|
|
|
self.neg_pwm.duty_u16(0xFFFF - signed_duty_cycle)
|
|
|
|
else:
|
|
|
|
self.pos_pwm.duty_u16(0xFFFF + signed_duty_cycle)
|
|
|
|
self.neg_pwm.duty_u16(0xFFFF)
|
|
|
|
|
|
|
|
else: # aka 'Coasting'
|
|
|
|
if signed_duty_cycle >= 0:
|
|
|
|
self.pos_pwm.duty_u16(signed_duty_cycle)
|
|
|
|
self.neg_pwm.duty_u16(0)
|
|
|
|
else:
|
|
|
|
self.pos_pwm.duty_u16(0)
|
|
|
|
self.neg_pwm.duty_u16(0 - signed_duty_cycle)
|