Skip to content

Commit 8cac071

Browse files
authored
Merge pull request #76 from Open-STEM/OfficialRelease
Moving out of Beta
2 parents c4376e2 + 28abebe commit 8cac071

14 files changed

+195
-57
lines changed

.DS_Store

8 KB
Binary file not shown.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ docs/_build/
55
.picowgo
66
secrets.json
77
venv
8-
.temp_xrplib
8+
.temp_xrplib
9+
XRPLib/__pycache__

XRPLib/board.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from machine import Pin, ADC, Timer
2+
from neopixel import NeoPixel
23
import time
4+
import sys
35

46
class Board:
57

@@ -11,12 +13,13 @@ def get_default_board(cls):
1113
Get the default board instance. This is a singleton, so only one instance of the board will ever exist.
1214
"""
1315
if cls._DEFAULT_BOARD_INSTANCE is None:
14-
cls._DEFAULT_BOARD_INSTANCE = cls(28,22)
16+
cls._DEFAULT_BOARD_INSTANCE = cls()
1517
return cls._DEFAULT_BOARD_INSTANCE
1618

17-
def __init__(self, vin_pin:int, button_pin:int):
19+
def __init__(self, vin_pin="BOARD_VIN_MEASURE", button_pin="BOARD_USER_BUTTON",
20+
rgb_led_pin = "BOARD_NEOPIXEL", led_pin = "LED"):
1821
"""
19-
Implements for extra features on the XRP v2 board. Handles the on/off switch, button, and LED.
22+
Implements for extra features on the XRP board. Handles the on/off switch, button, and LED.
2023
2124
:param vin_pin: The pin the on/off switch is connected to
2225
:type vin_pin: int
@@ -28,7 +31,10 @@ def __init__(self, vin_pin:int, button_pin:int):
2831

2932
self.button = Pin(button_pin, Pin.IN, Pin.PULL_UP)
3033

31-
self.led = Pin("LED", Pin.OUT)
34+
self.led = Pin(led_pin, Pin.OUT)
35+
36+
if "RP2350" in sys.implementation._machine:
37+
self.rgb_led = NeoPixel(Pin(rgb_led_pin, Pin.OUT), 1)
3238
# A timer ID of -1 is a virtual timer.
3339
# Leaves the hardware timers for more important uses
3440
self._virt_timer = Timer(-1)
@@ -102,3 +108,10 @@ def led_blink(self, frequency: int=0):
102108
else:
103109
self._virt_timer.deinit()
104110
self.is_led_blinking = False
111+
112+
def set_rgb_led(self, r:int, g:int, b:int):
113+
if "rgb_led" in self.__dict__:
114+
self.rgb_led[0] = (r, g, b)
115+
self.rgb_led.write()
116+
else:
117+
raise NotImplementedError("Board.set_rgb_led not implemented for the XRP Beta")

XRPLib/defaults.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .board import Board
22
from .differential_drive import DifferentialDrive
3-
from .motor import Motor
3+
from .motor import SinglePWMMotor, DualPWMMotor
44
from .encoder import Encoder
55
from .encoded_motor import EncodedMotor
66
from .rangefinder import Rangefinder
@@ -14,8 +14,8 @@
1414
Run "from XRPLib.defaults import *" to use
1515
"""
1616

17-
left_motor = EncodedMotor.get_default_encoded_motor(index=1)
18-
right_motor = EncodedMotor.get_default_encoded_motor(index=2)
17+
left_motor = EncodedMotor.get_default_encoded_motor(index=3)
18+
right_motor = EncodedMotor.get_default_encoded_motor(index=4)
1919
imu = IMU.get_default_imu()
2020
drivetrain = DifferentialDrive.get_default_differential_drive()
2121
rangefinder = Rangefinder.get_default_rangefinder()

XRPLib/differential_drive.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class DifferentialDrive:
1414
def get_default_differential_drive(cls):
1515

1616
"""
17-
Get the default XRP v2 differential drive instance. This is a singleton, so only one instance of the drivetrain will ever exist.
17+
Get the default XRP differential drive instance. This is a singleton, so only one instance of the drivetrain will ever exist.
1818
"""
1919

2020
if cls._DEFAULT_DIFFERENTIAL_DRIVE_INSTANCE is None:
@@ -46,6 +46,7 @@ def __init__(self, left_motor: EncodedMotor, right_motor: EncodedMotor, imu: IMU
4646
self.right_motor = right_motor
4747
self.imu = imu
4848

49+
self.brake_at_zero_power = False
4950
self.wheel_diam = wheel_diam
5051
self.track_width = wheel_track
5152

@@ -76,9 +77,19 @@ def set_speed(self, left_speed: float, right_speed: float) -> None:
7677
self.left_motor.set_speed(left_speed*cmpsToRPM)
7778
self.right_motor.set_speed(right_speed*cmpsToRPM)
7879

80+
def set_zero_effort_behavior(self, brake_at_zero_effort):
81+
82+
"""
83+
Sets the behavior of both motor at 0 effort to either brake (hold position) or coast (free spin)
84+
:param brake_at_zero_effort: Whether or not to brake at 0 effort. Can use EncodedMotor.ZERO_EFFORT_BREAK or EncodedMotor.ZERO_EFFORT_COAST for clarity.
85+
:type brake_at_zero_effort: bool
86+
"""
87+
self.left_motor.set_zero_effort_behavior(brake_at_zero_effort)
88+
self.right_motor.set_zero_effort_behavior(brake_at_zero_effort)
89+
7990
def stop(self) -> None:
8091
"""
81-
Stops both drivetrain motors
92+
Stops both drivetrain motors by setting power to zero.
8293
"""
8394
self.left_motor.set_speed()
8495
self.right_motor.set_speed()

XRPLib/encoded_motor.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
from .motor import Motor
1+
from .motor import SinglePWMMotor, DualPWMMotor
22
from .encoder import Encoder
33
from machine import Timer
44
from .controller import Controller
55
from .pid import PID
6+
import sys
67

78
class EncodedMotor:
89

10+
ZERO_EFFORT_BREAK = True
11+
ZERO_EFFORT_COAST = False
12+
913
_DEFAULT_LEFT_MOTOR_INSTANCE = None
1014
_DEFAULT_RIGHT_MOTOR_INSTANCE = None
1115
_DEFAULT_MOTOR_THREE_INSTANCE = None
@@ -14,49 +18,57 @@ class EncodedMotor:
1418
@classmethod
1519
def get_default_encoded_motor(cls, index:int = 1):
1620
"""
17-
Get one of the default XRP v2 motor instances. These are singletons, so only one instance of each of these will ever exist.
21+
Get one of the default XRP motor instances. These are singletons, so only one instance of each of these will ever exist.
1822
Raises an exception if an invalid index is requested.
1923
2024
:param index: The index of the motor to get; 1 for left, 2 for right, 3 for motor 3, 4 for motor 4
2125
:type index: int
2226
"""
27+
28+
if "RP2350" in sys.implementation._machine:
29+
MotorImplementation = DualPWMMotor
30+
else:
31+
MotorImplementation = SinglePWMMotor
32+
2333
if index == 1:
2434
if cls._DEFAULT_LEFT_MOTOR_INSTANCE is None:
2535
cls._DEFAULT_LEFT_MOTOR_INSTANCE = cls(
26-
Motor(6, 7, flip_dir=True),
27-
Encoder(0, 4, 5)
36+
MotorImplementation("MOTOR_L_IN_1", "MOTOR_L_IN_2", flip_dir=True),
37+
Encoder(0, "MOTOR_L_ENCODER_A", "MOTOR_L_ENCODER_B")
2838
)
2939
motor = cls._DEFAULT_LEFT_MOTOR_INSTANCE
3040
elif index == 2:
3141
if cls._DEFAULT_RIGHT_MOTOR_INSTANCE is None:
3242
cls._DEFAULT_RIGHT_MOTOR_INSTANCE = cls(
33-
Motor(14, 15),
34-
Encoder(1, 12, 13)
43+
MotorImplementation("MOTOR_R_IN_1", "MOTOR_R_IN_2"),
44+
Encoder(1, "MOTOR_R_ENCODER_A", "MOTOR_R_ENCODER_B")
3545
)
3646
motor = cls._DEFAULT_RIGHT_MOTOR_INSTANCE
3747
elif index == 3:
3848
if cls._DEFAULT_MOTOR_THREE_INSTANCE is None:
3949
cls._DEFAULT_MOTOR_THREE_INSTANCE = cls(
40-
Motor(2, 3),
41-
Encoder(2, 0, 1)
50+
MotorImplementation("MOTOR_3_IN_1", "MOTOR_3_IN_2", flip_dir=True),
51+
Encoder(2, "MOTOR_3_ENCODER_A", "MOTOR_3_ENCODER_B")
4252
)
4353
motor = cls._DEFAULT_MOTOR_THREE_INSTANCE
4454
elif index == 4:
4555
if cls._DEFAULT_MOTOR_FOUR_INSTANCE is None:
4656
cls._DEFAULT_MOTOR_FOUR_INSTANCE = cls(
47-
Motor(10, 11, flip_dir=True),
48-
Encoder(3, 8, 9)
57+
MotorImplementation("MOTOR_4_IN_1", "MOTOR_4_IN_2"),
58+
Encoder(3, "MOTOR_4_ENCODER_A", "MOTOR_4_ENCODER_B")
4959
)
5060
motor = cls._DEFAULT_MOTOR_FOUR_INSTANCE
5161
else:
5262
return Exception("Invalid motor index")
5363
return motor
5464

55-
def __init__(self, motor: Motor, encoder: Encoder):
65+
def __init__(self, motor, encoder: Encoder):
5666

5767
self._motor = motor
5868
self._encoder = encoder
5969

70+
self.brake_at_zero = False
71+
6072
self.target_speed = None
6173
self.DEFAULT_SPEED_CONTROLLER = PID(
6274
kp=0.035,
@@ -71,12 +83,38 @@ def __init__(self, motor: Motor, encoder: Encoder):
7183
# If the update timer is not running, start it at 50 Hz (20ms updates)
7284
self.updateTimer.init(period=20, callback=lambda t:self._update())
7385

86+
7487
def set_effort(self, effort: float):
7588
"""
7689
:param effort: The effort to set this motor to, from -1 to 1
7790
:type effort: float
7891
"""
79-
self._motor.set_effort(effort)
92+
if self.brake_at_zero and effort == 0:
93+
self.brake()
94+
else:
95+
self._motor.set_effort(effort)
96+
97+
# EncodedMotor.set_zero_effort_behavior(EncodedMotor.ZERO_POWER_BRAKE)
98+
def set_zero_effort_behavior(self, brake_at_zero_effort):
99+
"""
100+
Sets the behavior of the motor at 0 effort to either brake (hold position) or coast (free spin)
101+
:param brake_at_zero_effort: Whether or not to brake at 0 effort. Can use EncodedMotor.ZERO_EFFORT_BREAK or EncodedMotor.ZERO_EFFORT_COAST for clarity.
102+
:type brake_at_zero_effort: bool
103+
"""
104+
self.brake_at_zero = brake_at_zero_effort
105+
106+
def brake(self):
107+
"""
108+
Causes the motor to resist rotation.
109+
"""
110+
# Exact impl of brake depends on which board is being used.
111+
self._motor.brake()
112+
113+
def coast(self):
114+
"""
115+
Allows the motor to spin freely.
116+
"""
117+
self._motor.coast()
80118

81119
def get_position(self) -> float:
82120
"""

XRPLib/encoder.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Encoder:
99
_counts_per_motor_shaft_revolution = 12
1010
resolution = _counts_per_motor_shaft_revolution * _gear_ratio # 585
1111

12-
def __init__(self, index, encAPin, encBPin):
12+
def __init__(self, index, encAPin: int|str, encBPin: int|str):
1313
"""
1414
Uses the on board PIO State Machine to keep track of encoder positions.
1515
Only 4 encoders can be instantiated this way.
@@ -21,9 +21,10 @@ def __init__(self, index, encAPin, encBPin):
2121
:param encBPin: The pin the right reflectance sensor is connected to
2222
:type encBPin: int
2323
"""
24-
if(abs(encAPin - encBPin) != 1):
25-
raise Exception("Encoder pins must be successive!")
26-
basePin = machine.Pin(min(encAPin, encBPin))
24+
# if(abs(encAPin - encBPin) != 1):
25+
# raise Exception("Encoder pins must be successive!")
26+
basePin = machine.Pin(min(encAPin, encBPin), machine.Pin.IN)
27+
nextPin = machine.Pin(max(encAPin, encBPin), machine.Pin.IN)
2728
self.sm = rp2.StateMachine(index, self._encoder, in_base=basePin)
2829
self.reset_encoder_position()
2930
self.sm.active(1)

XRPLib/imu.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,15 @@ class IMU():
1919
@classmethod
2020
def get_default_imu(cls):
2121
"""
22-
Get the default XRP v2 IMU instance. This is a singleton, so only one instance of the drivetrain will ever exist.
22+
Get the default XRP IMU instance. This is a singleton, so only one instance of the drivetrain will ever exist.
2323
"""
2424

2525
if cls._DEFAULT_IMU_INSTANCE is None:
26-
cls._DEFAULT_IMU_INSTANCE = cls(
27-
scl_pin=19,
28-
sda_pin=18,
29-
addr=LSM_ADDR_PRIMARY
30-
)
26+
cls._DEFAULT_IMU_INSTANCE = cls()
3127
cls._DEFAULT_IMU_INSTANCE.calibrate()
3228
return cls._DEFAULT_IMU_INSTANCE
3329

34-
def __init__(self, scl_pin: int, sda_pin: int, addr):
30+
def __init__(self, scl_pin: int|str = "I2C_SCL_1", sda_pin: int|str = "I2C_SDA_1", addr=LSM_ADDR_PRIMARY):
3531
# I2C values
3632
self.i2c = I2C(id=1, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=400000)
3733
self.addr = addr
@@ -61,8 +57,10 @@ def __init__(self, scl_pin: int, sda_pin: int, addr):
6157
pass
6258

6359
# Reset sensor to clear any previous configuration
60+
# reset() also sets the board to the default config
6461
self.reset()
6562

63+
def _default_config(self):
6664
# Enable block data update
6765
self._set_bdu()
6866

@@ -186,10 +184,17 @@ def reset(self, wait_for_reset = True, wait_timeout_ms = 100):
186184
# Check if register has returned to default value (0x04)
187185
self.reg_ctrl3_c_byte[0] = self._getreg(LSM_REG_CTRL3_C)
188186
if self.reg_ctrl3_c_byte[0] == 0x04:
187+
self._default_config()
188+
self._start_timer()
189189
return True
190190
# Timeout occurred
191+
# Attempt to set default config anyways
192+
self._default_config()
193+
self._start_timer()
191194
return False
192195
else:
196+
self._default_config()
197+
self._start_timer()
193198
return True
194199

195200
def get_acc_x(self):

0 commit comments

Comments
 (0)