STARLEAP / SPIKE PRIME PYTHON / 2026–27
Build 2026.09.22-090630-0400
On this page
- Goal
- Step 1: Read The Hub While The Bike Stays Still
- Step 2: Choose The Number That Follows The Slope
- Step 3: Turn Tenths Of A Degree Into Degrees
- Step 4: Remember What Level Looks Like
- Step 5: Measure Change From Level
- Step 6: Make Positive Mean Uphill
- Step 7: Recognize Uphill
- Step 8: Separate Downhill From Level
- Step 9: End A Test If The Tilt Is Too Large
- Step 10: Limit How Many Readings We Take
- Step 11: Choose A Speed Without Moving Yet
- Step 12: Prove The Motors In A Separate Short Test
- Step 13: Let The Slope Choose A Short Movement
- Physical Challenge: A Gentle Slope Ride
- Teacher Check
- Before You Put The Kit Away
Project 17: Smart Bike Slope Lab
You need: a LEGO SPIKE Prime set, the SPIKE App Python editor, and the Smart Bike build. Complete both Bike and Biker instructions in the app. Connect the two motors to C and E. Keep the model's support wheels in place. Later, use a wide, low, stable board on the floor for gentle slopes.
Goal
Use the hub's built-in motion sensor to tell when the bike points uphill, downhill, or along level ground. Then let that reading choose a motor speed for a short ride.
The hub contains a gyro and an accelerometer. motion_sensor.tilt_angles() gives its estimated orientation. No separate gyro sensor or sensor cable is needed. This lesson uses tilt angles, not the speed at which the hub rotates.
Build on the loops from Project 04 and the choices from Project 09. Work at your own pace; building, sensor tests, and driving can take separate sessions.
Using the code: gray lines are already there; dark lines are the edit. Keep four spaces inside main(), eight inside its loop, and twelve inside a choice in that loop. Stop the program before each edit, and save after each successful step.
Step 1: Read The Hub While The Bike Stays Still
Create a Python project named Smart Bike Slope Lab. Place the bike on a level surface and enter this small program:
from hub import motion_sensor
import runloop
async def main():
while True:
angles = motion_sensor.tilt_angles()
print(angles)
await runloop.sleep_ms(100)
runloop.run(main())
Run the program. Watch the console, then gently raise the front of the bike a little. Return it to level, then raise the back a little. Support the bike by its frame. Keep its heading and sideways lean as steady as you can.
Observe: Three numbers print together. Some change when you tilt the bike. The motors stay still because this program has no motor instructions.
Step 2: Choose The Number That Follows The Slope
The three numbers form a tuple, an ordered group of values. Python counts their positions from zero:
| Position | Hub angle | What to know |
|---|---|---|
| 0 | yaw | turning around the vertical axis |
| 1 | pitch | one direction of tilt |
| 2 | roll | the other direction of tilt |
These directions belong to the hub. Which one follows the bike's nose depends on how the hub is mounted.
Add tilt_axis = 1 above main(). Inside the loop, replace print(angles) with these two lines:
angles = motion_sensor.tilt_angles()
raw_angle = angles[tilt_axis]
print(raw_angle)
await runloop.sleep_ms(100)
angles[1] selects pitch; angles[2] selects roll. The brackets select one value. Keep the parentheses in tilt_angles() because that is a function call.
Run the program and repeat the gentle front-up and back-up tests. If this value barely changes, change tilt_axis to 2 and run again. Choose the value that changes smoothly in opposite directions for the two tilts. Keep that choice for the rest of the lesson.
Fix it: If neither choice follows the slope, or the number suddenly jumps between large positive and negative values, show the teacher your build and readings before continuing. Do not use yaw as a substitute for slope.
Step 3: Turn Tenths Of A Degree Into Degrees
A raw reading of 120 means 12 degrees. The API reports tenths of a degree, also called decidegrees. Divide by 10 to get degrees.
Keep the raw_angle line. Replace its print line with:
raw_angle = angles[tilt_axis]
angle = raw_angle / 10
print(angle)
await runloop.sleep_ms(100)
Run the program. Repeat the same gentle tilt.
Observe: The number is one tenth as large, and may include a decimal point. A level bike may not read zero because the hub is mounted at an angle. Next we will give our program a level starting value.
Step 4: Remember What Level Looks Like
Place the bike level and keep it still. Add these three lines inside main(), before the loop:
async def main():
await runloop.sleep_ms(1000)
level_angle = motion_sensor.tilt_angles()[tilt_axis] / 10
print("level angle", level_angle)
while True:
The one-second pause gives you time to let go. level_angle saves one reading in degrees. Keep this line outside the loop so the starting value is not replaced on every reading.
Run the program with the bike level. Then gently tilt it.
Observe: level angle prints once, followed by changing angle readings. Run again only after returning the bike to level. Each new run records a new starting value.
Step 5: Measure Change From Level
Inside the loop, replace print(angle) with:
angle = raw_angle / 10
slope = angle - level_angle
print("slope", slope)
await runloop.sleep_ms(100)
For example, if the level reading was 30 degrees and the new reading is 38 degrees, the change is 8 degrees. Subtracting the starting value makes level close to zero.
Run the program, starting level. Raise the front, return to level, then raise the back.
Observe: Level is near zero. The two tilts should give opposite signs. Note whether front-up is positive or negative; the next edit will make our program use one consistent meaning.
Step 6: Make Positive Mean Uphill
Add up_sign = 1 beside tilt_axis above main(). Replace only the slope calculation:
slope = (angle - level_angle) * up_sign
print("slope", slope)
Run the program from level. If raising the front gives a negative slope, change up_sign to -1, then run again from level. Multiplying by -1 reverses the sign. Leave it as 1 if front-up is already positive.
Observe: Front-up is now positive, back-up is negative, and level is near zero. Show these three readings to your teacher before adding choices. Use small tilts with no sudden reading jumps.
Step 7: Recognize Uphill
Add hill_cutoff = 5 beside the variables above main(). Keep print("slope", slope) and add this choice just below it, before the pause:
if slope > hill_cutoff:
print("uphill")
else:
print("not uphill")
await runloop.sleep_ms(100)
Run the program from level. Gently raise the front until the printed slope is more than 5 degrees.
Observe: The label changes to uphill. At 5 degrees or less, this version says not uphill. The motors still stay still.
Step 8: Separate Downhill From Level
Insert one elif before the existing else. Change the text in else to level:
if slope > hill_cutoff:
print("uphill")
elif slope < -hill_cutoff:
print("downhill")
else:
print("level")
await runloop.sleep_ms(100)
-hill_cutoff is -5. Python checks the downhill condition only if the uphill condition was false. The final else handles everything left: from -5 through +5 degrees, including both endpoints.
Run the program from level. Test all three labels while the bike stays in your hands or on the surface.
Observe: Small movements around zero stay level. This range prevents a tiny wobble from changing the label constantly. We do not need the reading to equal exactly zero.
Step 9: End A Test If The Tilt Is Too Large
Add stop_slope = 20 above main(). Insert this separate guard after the slope print and before the uphill choice:
print("slope", slope)
if abs(slope) > stop_slope:
print("tilt limit - test ended")
break
if slope > hill_cutoff:
print("uphill")
abs() gives a number's size without its sign: abs(-21) and abs(21) are both 21. break ends the loop, so no later instruction in that loop runs for this reading.
Run the program from level. While the motors are still unused, support the frame and gently tilt past 20 degrees in either direction.
Observe: The limit message prints and the program ends. This is a software test limit, not a guarantee that every smaller physical slope is suitable. Return to level before running again.
Step 10: Limit How Many Readings We Take
Replace only while True: with for sample in range(20):. Keep everything inside the loop indented as it was. Add the final print below the whole loop, four spaces in:
for sample in range(20):
angles = motion_sensor.tilt_angles()
await runloop.sleep_ms(100)
print("test finished")
Run the program from level. Keep the tilt small.
Observe: The program takes 20 readings, then ends. It also ends early if the tilt guard runs. This gives our later driving test a clear stopping point.
Step 11: Choose A Speed Without Moving Yet
Add one assignment to each branch of the existing uphill/downhill/level chain. Add one print after the entire chain, aligned with if:
if slope > hill_cutoff:
print("uphill")
speed = 180
elif slope < -hill_cutoff:
print("downhill")
speed = 80
else:
print("level")
speed = 120
print("motor speed", speed)
await runloop.sleep_ms(100)
Run the program from level, then tilt gently uphill or downhill. Repeat for another test.
Observe: The program selects 180 uphill, 80 downhill, and 120 level. These are starting values for motor degrees per second. They are not wheel travel distances or percentages of electrical power. The bike's actual ground speed also depends on its build and the surface.
Each branch gives speed a value. The print below the chain uses the one that was selected. The motors still stay still.
Step 12: Prove The Motors In A Separate Short Test
Save Smart Bike Slope Lab. Create a second Python project named Smart Bike Motor Test. Keep your slope program saved; you will return to it in Step 13.
With the drive wheels lifted clear, enter this short program:
from hub import port
import motor_pair
import runloop
bike_motor = port.C
rider_motor = port.E
async def main():
motor_pair.pair(motor_pair.PAIR_1, bike_motor, rider_motor)
await motor_pair.move_for_time(
motor_pair.PAIR_1, 300, 0, velocity=120)
motor_pair.stop(motor_pair.PAIR_1)
runloop.run(main())
bike_motor names the bike motor on C; rider_motor names the rider motor on E. These variables keep the wiring at the top and make the commands easier to read.
Run the program. The two motors should turn briefly and stop. 300 is the duration in milliseconds; 0 is straight steering. await lets this movement finish before the next instruction.
Then place the bike on a clear, level floor and run once. Check that it travels forward a short distance. If it twists, goes backward, or strains, stop and check the C/E wiring and model assembly with your teacher before continuing. Keep hands clear of moving parts.
Step 13: Let The Slope Choose A Short Movement
Return to your saved Smart Bike Slope Lab project. Change the first import, add the motor-pair import, and define the two port variables after the imports at the top:
from hub import motion_sensor, port
import motor_pair
import runloop
bike_motor = port.C
rider_motor = port.E
At the start of main(), before the existing one-second pause, add:
async def main():
motor_pair.pair(motor_pair.PAIR_1, bike_motor, rider_motor)
motor_pair.stop(motor_pair.PAIR_1)
await runloop.sleep_ms(1000)
Keep the calibration, 20-reading loop, tilt guard, and speed choices. After print("motor speed", speed) and before the existing loop pause, add:
print("motor speed", speed)
await motor_pair.move_for_time(
motor_pair.PAIR_1, 200, 0, velocity=speed)
await runloop.sleep_ms(100)
Finally, add a stop after the whole loop and before its final print, four spaces in:
motor_pair.stop(motor_pair.PAIR_1)
print("test finished")
For the first run, support the bike securely with its drive wheels clear and its frame level during the starting pause. After calibration, tilt gently while keeping clear of the wheels.
Run the program.
Observe: Each reading chooses a speed for a 200-millisecond movement, which brakes at the end by default. The pause follows, then the next reading. Expect short pulses. At most 20 pulses run; the tilt guard can end the test sooner. It checks between pulses, so use the app's stop control whenever you need to stop immediately.
Physical Challenge: A Gentle Slope Ride
Keep the support wheels fitted. Use a wide, stable board close to the floor, with a gentle incline and a clear stopping area. Secure it so it cannot slide or tip. Have your teacher check the setup.
Start each run with the bike on level ground for calibration. Give it a level approach to the board. Do not start on the slope: the program would record that slope as its new zero. Test uphill and downhill as separate short runs, each beginning with a level approach. For downhill, use a level upper starting section before the descent. Stop before repositioning the bike.
- Level 1: Show all three slope labels in the stationary sensor program.
- Level 2: Complete a bounded level-floor ride with the motor version.
- Level 3: Show the speed choice changing during a gentle slope ride.
- Level 4: Tune one speed value, keep the time and tilt limits, and repeat the same route twice. Explain what changed.
If the bike stalls or slips, stop and reduce the slope or fix the build before tuning the speed. The printed number is the requested motor speed; it does not prove the bike maintained a particular ground speed.
Teacher Check
Show your teacher:
- which tuple position follows the bike's slope, and why you divide by 10
- where
level_angleis saved once, and why every run starts level - positive uphill, negative downhill, and near-zero level readings
- why exactly -5 and +5 use the
elsebranch - a short ride that stops, plus the tilt-limit behavior with the wheels clear
- one number you changed and the effect you observed
Before You Put The Kit Away
Save both programs. Stop the program before moving wires or changing the build. Keep your working tilt_axis and up_sign values. Recheck them if you remount the hub. Mark the last step that worked so you can continue next time.