-
Notifications
You must be signed in to change notification settings - Fork 0
4 Software
Guide and general tips on using MASLAB software and other software to interact with sensors and actuators on the robot. Checkout electrical how-to to learn how to connect the hardwares.
- Raven board fully soldered and installed onto the Pi.
- Raspberry Pi 5 fully set up and connected to internet.
Caution
DO NOT USE THE BATTERY WHILE UPDATING MASLAB SOFTWARE. POWER THE SYSTEM WITH THE USB-C POWER ADAPTER INSTEAD. The power from the battery to the Pi is managed by Raven such that the update process will disturb its operation and cause the entire system to turn off.
Raven board is completely new! That also means it comes with no firmware installed. Therefore, we need to deploy the firmware by running this command on the Pi:
maslab-updateIf everything goes well, you should see something similar to this in your shell:
This command will grab the latest version of the Raven firmware and deploy it on the Raven, so make sure Raven is installed on the Pi before running this command. It will also install the latest version of our software library to use Raven.
Tip
MASLAB will always be a work in progress. We may release updated versions of Raven firmware and software library as we (and you) discover bugs and improve our software. Therefore, remember to run this command again when we announce software updates.
If you are using VSCode for developing and connecting to the Pi, it is highly recommended to also install VSCode's Python extension.
If you are not familiar with Python, please follow VSCode's tutorial on how to develop with Python: https://code.visualstudio.com/docs/python/python-tutorial.
And of course please feel free to ask course staffs any question!
MASLAB staffs maintain a Python library that supports using the motors and servos on the Raven board (raven).
Important
Since these library is communicating with a hardware, no two instances may run at the same time. DO NOT have two different Python scripts trying to use Raven.
The library is available at https://github.com/MASLAB/maslab-lib.
from raven import Raven
raven_board = Raven()Important
The motor is powered by the battery such that your Pi + Raven setup needs to be powered by the battery to run the motor. The provided motor is a POWERFUL motor. Please have it secured to your table / robot before testing. Test at low speed / torque before going full power!
DC motors may come with encoders to estimate how many rotation has the motor rotated. To increase the resolution of the encoder, the encoder spins with some gear ratio with respect to the motor, which also has another gear ratio. For example, the provided motor is a 50:1 motor with an encoder that does 64 counts per revolution (also called pulse per revolution). This means the encoder will count 50 * 64 = 3200 counts per motor rotation.
Raven supports up to 5 DC motors with encoders.
With Raven, you can get the encoder values. You can also set them to a new initial value. Here is an example of how to use it:
raven_board.set_motor_encoder(Raven.MotorChannel.CH1, 0) # Set encoder count for motor 1 to zero
print(raven_board.get_motor_encoder(Raven.MotorChannel.CH1)) # Print encoder count = "0"Important
Encoder count should matches with the motor direction. To test, run the motor with direct mode (see direct mode) and read out encoder reading. When motor is going forward (reverse=False), encoder counts should go up. If not, swap the C1 and C2 wires. See motor connection for more information.
There are 3 ways to drive the motors:
- Direct - Motor moves according to a torque and speed factor. We recommend driving in direct mode first to get things started.
- Position (requires encoder) - Motor moves to an encoder position with PID control
- Velocity (requires encoder) - Motor moves with a set encoder velocity using PID control
- Disabled - Turn off the motor
To use the motors, make sure you have imported Raven. Then follow the following examples for each mode.
Tip
Once motor mode and PID values are set, you can set motor drive values without having to set the mode and PID values again.
In direct mode, you get to set the torque factor as 0% to 100% of available torque. You also get to set the speed as 0% to 100% of max speed and a direction of rotation. These conditions are subjected to the battery's voltage. Do this for most reliable actuation or custom controls. Also useful as a part of mechanical designs.
raven_board.set_motor_mode(Raven.MotorChannel.CH1, Raven.MotorMode.DIRECT) # Set motor mode to DIRECT
# Speed controlled:
raven_board.set_motor_torque_factor(Raven.MotorChannel.CH1, 100) # Let the motor use all the torque to get to speed factor
raven_board.set_motor_speed_factor(Raven.MotorChannel.CH1, 10, reverse=True) # Spin at 10% max speed in reverse
# Torque controlled:
raven_board.set_motor_speed_factor(Raven.MotorChannel.CH1, 100) # Make motor try to run at max speed forward
raven_board.set_motor_torque_factor(Raven.MotorChannel.CH1, 10) # Let it use up to 10% available torqueImportant
The control loop for Raven motor runs at 1kHz (dt = 0.001). This will be important to playing with PID values.
Caution
The PID values provided are only as examples. They are untested and may not work for your motors. Here is some tip to tune the values:
- Start with some P and adjust until your motor starts going to desired target quickly but may not reach / little overshoot.
- Include I and adjust until your motor gets up to target set point quickly and settle to correct target point with some overshoot.
- Include D and adjust until your motor get to set point still quickly and correctly but does not overshoot.
Often time you only needs PI or PD to control your motors. Here is also a great video to demonstrate the effects of PID: https://www.youtube.com/watch?v=fusr9eTceEo
Tip
Check control theory lecture notes to refresh about PID control: https://maslab.mit.edu/2025/lectures
For controlled modes, you will need to provide a current limit for the motor effort. Maximum allowed current is 6.4 amps.
raven_board.set_motor_max_current(Raven.MotorChannel.CH1, 5) # Set motor current to 5 ampsIn position controlled mode, you get to set the PID value for the controller and a target in encoder counts.
raven_board.set_motor_encoder(Raven.MotorChannel.CH1, 0) # Reset encoder
raven_board.set_motor_max_current(Raven.MotorChannel.CH1, 5) # Set motor current to 5 amps
raven_board.set_motor_mode(Raven.MotorChannel.CH1, Raven.MotorMode.POSITION) # Set motor mode to POSITION
raven_board.set_motor_pid(Raven.MotorChannel.CH1, p_gain = 100, i_gain = 0, d_gain = 0, percent = 20) # Set PID values and 20% effort to reduce speed
# Make the motor spin until 4400 counts (10 rev of wheel motor)
raven_board.set_motor_target(Raven.MotorChannel.CH1, 4400)In velocity controlled mode, you also get to set PID value and target in encoder counts per second.
raven_board.set_motor_encoder(Raven.MotorChannel.CH1, 0) # Reset encoder
raven_board.set_motor_max_current(Raven.MotorChannel.CH1, 5) # Set motor current to 5 amps
raven_board.set_motor_mode(Raven.MotorChannel.CH1, Raven.MotorMode.VELOCITY) # Set motor mode to POSITION
raven_board.set_motor_pid(Raven.MotorChannel.CH1, p_gain = 10, i_gain = 0, d_gain = 0, percent = 20) # Set PID values and 20% effort to reduce acceleration
# Make the motor spin at -4400 counts/second (-10 rev/sec of wheel motor)
raven_board.set_motor_target(Raven.MotorChannel.CH1, -4400)Servos are position controlled motors that can move from -90 degree to 90 degree. Their signal is based on a timed pulse. Typically, 1000us means -90 degree and 2000us means 90 degree. Some servo may have different values.
Raven board supports up to 4 servos. For each servo, you get to set the position in degree. Optionally, you can set the minimum and maximum pulse microsecond as min_us and max_us for you appropriate motor. Otherwise, they are defaulted to min_us=1000 and max_us=2000. Here is an example of how to use them once you have imported Raven.
# Set the servo 1 to -75 degrees with custom pulse microseconds
raven_board.set_servo_position(Raven.ServoChannel.CH1, -75, min_us=500, max_us=2500)For switches/buttons or LED, Raven includes breakout to 5 Pi's GPIO pins. To use the pins, we have preinstalled gpiozero on the Pi. The documentation for gpiozero can be found here: https://gpiozero.readthedocs.io/en/stable/
Here are a couple useful examples:
- Button (also limit switch) - https://gpiozero.readthedocs.io/en/stable/api_input.html#button
- Light sensor - https://gpiozero.readthedocs.io/en/stable/api_input.html#lightsensor-ldr
- LED - https://gpiozero.readthedocs.io/en/stable/api_output.html#led
We also have CircuitPython digitalio preinstalled. Here are a few other useful examples:
- Break beam - https://learn.adafruit.com/ir-breakbeam-sensors/circuitpython
- Ultrasonic distance sensor - https://github.com/adafruit/Adafruit_CircuitPython_HCSR04
Raven board also includes 2 qwiic connectors to use sensor boards with qwiic connections. For example, Adafruit's AS7341 color sensor board. You can use it with any i2c devices with qwiic connector breakout cables. If the board does not come with a qwiic connector but is I2C compatible (has SDA and SCL connection), you can still use it with a qwiic to male header cable or manual wiring (ask staff).
Important
The default qwiic connector is the one nearest to the motor encoder pins. It is labeled I2C1. Talk to staffs if you need to use the secondary connector near the servo pins.
The Pi comes with Adafruit's Circuit Python library preinstalled such that it is compatible with Adafruit's Python libraries for communicating with the sensor board. Here are a few useful sensors that we may stock:
This is a powerful color sensor that can sense red, orange, yellow, green, blue, and violet. It also has a built in LED to make color sensing more consistent. This can be useful to verify color of a game piece at the robot's intake or where the camera cannot see.
sudo pip3 install adafruit-circuitpython-as726x --break-system-packagesimport board
from adafruit_as726x import AS726x_I2C
i2c = board.I2C()
as726x = AS726x_I2C(i2c)
while True:
# Wait for data to be ready
while not as726x.data_ready:
time.sleep(0.1)
# Print red value
print("\n")
print(f"Red: {as726x.red}")
time.sleep(1)This is another powerful color sensor that can also function as a proximity and gesture sensor.
sudo pip3 install adafruit-circuitpython-apds9960 --break-system-packagesimport board
import adafruit_apds9960.apds9960
i2c = board.I2C()
adps = adafruit_apds9960.apds9960.APDS9960(i2c)
adps.enable_gesture = True
adps.enable_proximity = True
adps.enable_color = True
while True:
'''
0 = No gesture detected
1 = Up gesture detected
2 = Down gesture detected
3 = Left gesture detected
4 = Right gesture detected
'''
print(f"Gesture: {adps.gesture()}")
# Proximity to determine if something is close
print(f"Proximity: {adps.proximity()}")
# 0 to 65535
r, g, b, c = adps.color_data
print(f"Red: {r}, Green: {g}, Blue: {b}, Clear: {c}")
time.sleep(1)https://learn.adafruit.com/adafruit-as7262-6-channel-visible-light-sensor/overview
IMUs are important to estimate how fast your robot is moving and rotating. Typical IMU measures the linear acceleration using an accelerometer and rotational velocity using a gyroscope. This is very useful for figuring where your robot is heading for accurate turning and straight driving manuever. The Adafruit BNO085 is special as it can gives you the orientation of the sensor without you having to manually integrate the rotational velocity. The BNO085 provides the orientation using quaternion, an angle representation that is useful for graphics and robotics but less intuitive. This can be converted to Euler rotation (roll, pitch, and yaw).
sudo pip3 install adafruit-circuitpython-bno08x --break-system-packagesimport time
import board
import busio
import adafruit_bno08x
from adafruit_bno08x.i2c import BNO08X_I2C
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
bno = BNO08X_I2C(i2c)
from adafruit_bno08x import BNO_REPORT_ROTATION_VECTOR
bno.enable_feature(BNO_REPORT_ROTATION_VECTOR) # Enable rotation vector
# Calculate yaw from quaternion
def find_heading(dqw, dqx, dqy, dqz):
norm = sqrt(dqw * dqw + dqx * dqx + dqy * dqy + dqz * dqz)
dqw = dqw / norm
dqx = dqx / norm
dqy = dqy / norm
dqz = dqz / norm
ysqr = dqy * dqy
t3 = +2.0 * (dqw * dqz + dqx * dqy)
t4 = +1.0 - 2.0 * (ysqr + dqz * dqz)
yaw_raw = atan2(t3, t4)
yaw = yaw_raw * 180.0 / pi
if yaw > 0:
yaw = 360 - yaw
else:
yaw = abs(yaw)
return yaw # heading in 360 clockwise
while True:
# Print heading (yaw) with quaternion rotation vector
quat_i, quat_j, quat_k, quat_real = bno.quaternion
heading = find_heading(quat_real, quat_i, quat_j, quat_k)
print("Heading:", heading)
print("")
time.sleep(0.1)https://learn.adafruit.com/adafruit-9-dof-orientation-imu-fusion-breakout-bno085/overview
We may also stock some Time of Flight (ToF) sensors that send out a beams of light and measure the time it takes to bounce back to sense distance. This can be very useful to sense objects near the robot once the object goes out of the camera's field of view. We may have these sensors from Adafruit with qwiic connector, or from Pololu with header pins. Adafruit's library can be used for both of these.
VL53L0X
sudo pip3 install adafruit-circuitpython-vl53l0x --break-system-packagesVL53L1X
sudo pip3 install adafruit-circuitpython-vl53l1x --break-system-packagesVL53L0X
import time
import board
from adafruit_vl53l0x import VL53L0X
i2c = board.I2C()
vl53 = VL53L0X(i2c)
while True:
# Print distance
print(f"Range: {vl53.range}mm")
time.sleep(1.0)VL53L1X
import time
import board
from adafruit_vl53l1x import VL53L1X
i2c = board.I2C()
vl53 = VL53L1X(i2c)
vl53.start_ranging()
while True:
# Print distance
if vl53.data_ready:
print(f"Distance: {vl53.distance} cm")
vl53.clear_interrupt()
time.sleep(1.0)https://learn.adafruit.com/adafruit-vl53l0x-micro-lidar-distance-sensor-breakout/overview https://learn.adafruit.com/adafruit-vl53l1x/overview
It's often nice to be able to quickly read telemetry from the robot, either for debugging or monitoring purposes. The MASLAB library ships with a small module called streamer that exposes a web streaming service, allowing you to visualize simple telemetry data, computer vision outputs, and robot odometry without an RDP connection.
Initialize Streamer as follows:
from streamer import Streamer
s = Streamer()This should start a web server. It should print out something like this:
!! MASLAB streamer is running !!
* Serving Flask app 'streamer.server'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://10.31.141.10:5000
Press CTRL+C to quit
Navigate to team<X>pi.local:5000 (where is your team number) to see the web interface. Right now, it should be blank. The following functions are used to display data:
-
s.set_data(data: Dict): write any kind of text data as key-value pairs. -
s.set_img(img: cv2.Mat): display an OpenCV image. -
s.update_odom_state(x: float, y: float, theta: float): set the current estimated robot position and heading (in radians). -
s.update_circles(circles: List[Tuple(float, float, str)]): Render circles, for example the can positions. Each entry must be of the form(x, y, c)wherexandyare the position andcis the color. Any color that HTML recognizes can be used. -
s.update_lines(lines: List[Tuple(float, float, float, float, str]): Render lines. Each entry must be of the form(x1, y1, x2, y2, c).
Here is a minimal example:
from streamer import Streamer
import time
import cv2
import random
import math
stream = Streamer()
pos = [0, 0, random.uniform(0, 2*math.pi)] # x, y, theta
# display an image
stream.set_img(cv2.imread("test.jpg"))
# continuously update data and odometry
curr_data = {}
while True:
pos[0] += math.cos(pos[2]) * 0.01
pos[1] += math.sin(pos[2]) * 0.01
pos[2] += random.uniform(-0.2, 0.2)
curr_data["timestamp"] = time.time()
curr_data["random_value"] = random.random()
# stream data and odometry
stream.set_data(curr_data)
stream.update_odom_state(pos[0], pos[1], pos[2])
stream.update_circles(
[
(0.40, 0.20, "red"),
(0.13, -0.40, "green"),
]
)
stream.update_lines(
[
(0, 0, 0.75, 0.75, "blue"),
]
)
time.sleep(0.05)The interface should look something like this:
Now that you have the software and firmware to run Raven. AFTER the battery lecture and reviewing the battery guide. Feel free to power your Pi with the battery and see if you can make your motors and servos move!
Once you are familiar with using the Pi and the MASLAB library to interact with Raven, you can start thinking about writing and testing out your robot software. To keep your software development progress organized, we will be using Git version control system. Checkout Git guide to "git" started.