Tutorial 7.2: Sensor Integration (Advanced)¶
Time: ~20 minutes Prerequisites: Tutorial 7.1: PID Control Level: Bonus/Advanced
Why Use Sensors?¶
Sensors are your robot's eyes, ears, and sense of direction. Without them, your robot is guessing. With them, it knows.
Real-World Analogies¶
Eyes and Ears for Your Robot¶
Just like you use your senses to navigate: - Eyes → Distance sensor, Optical sensor (see obstacles and colors) - Ears → Not applicable, but sensors "listen" to the world - Balance → Inertial sensor (like your inner ear for orientation) - GPS in phone → GPS sensor (knows exact position)
Car Sensors You Already Know¶
| Car Feature | Robot Equivalent | What It Does |
|---|---|---|
| Backup camera beeps | Distance sensor | Detects how far from objects |
| Dashboard compass | Inertial sensor | Tracks which direction you're facing |
| Phone GPS | GPS sensor | Knows exact position on field |
| Color-sorting machine | Optical sensor | Identifies block colors |
Video Game Comparison¶
WITHOUT SENSORS (like playing blindfolded):
"I pressed forward for 2 seconds... I think I'm near the goal?"
WITH SENSORS (like playing with eyes open):
"I can SEE the goal is 100mm away. I'll stop when I reach it!"
Open-Loop vs Closed-Loop Control¶
flowchart LR
subgraph "Open-Loop (Guessing)"
A1["Command:\ndrive 500mm"] --> B1["Robot moves"]
B1 --> C1["Hope it\nworked?"]
end
subgraph "Closed-Loop (Sensors)"
A2["Command:\ndrive until\n500mm"] --> B2["Robot moves"]
B2 --> C2["Sensor:\n487mm..."]
C2 --> D2["Keep going"]
D2 --> E2["Sensor:\n500mm"]
E2 --> F2["STOP!"]
C2 -.-> B2
end
style C1 fill:#ffcdd2,stroke:#c62828
style F2 fill:#c8e6c9,stroke:#2e7d32
Inertial Sensor (IMU)¶
The most useful sensor for competition robots! It's like your robot's inner ear - it knows which way it's facing even if the wheels slip.
How the Inertial Sensor Works¶
flowchart TB
subgraph "Inertial Sensor (IMU)"
A["3-Axis Gyroscope"] --> D["Tracks Rotation"]
B["3-Axis Accelerometer"] --> E["Tracks Movement"]
D --> F["Heading: 0-360°"]
E --> G["Tilt Detection"]
end
style F fill:#c8e6c9,stroke:#2e7d32
Calibration Process¶
flowchart LR
A["Call\ncalibrate()"] --> B["Robot must\nbe STILL!"]
B --> C["Wait 3\nseconds"]
C --> D["Sensor\nready ✅"]
style B fill:#fff3e0,stroke:#ef6c00
style D fill:#c8e6c9,stroke:#2e7d32
Setup¶
# In robot_config.py
inertial_sensor = Inertial(Ports.PORT5)
# Before using (in autonomous) - CRITICAL!
inertial_sensor.calibrate()
wait(3, SECONDS) # Wait for calibration! Robot must be STILL!
Important: If the robot moves during calibration, all readings will be wrong!
Heading vs Rotation¶
flowchart TB
subgraph "heading() - Compass Style"
H1["0°"] --> H2["90°"] --> H3["180°"] --> H4["270°"] --> H5["359°"]
H5 --> H6["0° (resets!)"]
end
subgraph "rotation() - Total Turns"
R1["0°"] --> R2["90°"] --> R3["180°"] --> R4["360°"]
R4 --> R5["720° (2 full turns)"]
R5 --> R6["Keeps counting..."]
end
style H6 fill:#fff3e0,stroke:#ef6c00
style R6 fill:#c8e6c9,stroke:#2e7d32
# Heading: 0-360 degrees (resets at 360) - like a compass
heading = inertial_sensor.heading()
# Rotation: Continuous (-∞ to +∞) - counts total rotation
rotation = inertial_sensor.rotation()
When to use which:
- heading() → "Face north" (absolute direction)
- rotation() → "Turn exactly 720°" (2 full spins)
Accurate Turning¶
def turn_to_heading(target):
"""Turn to exact heading using inertial sensor."""
Kp = 0.8
while True:
current = inertial_sensor.heading()
error = target - current
# Handle 0-360 wraparound
if error > 180:
error -= 360
elif error < -180:
error += 360
if abs(error) < 2:
drivetrain.stop()
return
speed = Kp * error
speed = max(-50, min(50, speed)) # Limit speed
left_motors.spin(FORWARD, speed, PERCENT)
right_motors.spin(FORWARD, -speed, PERCENT)
wait(20, MSEC)
Straight-Line Driving¶
def drive_straight(distance_mm):
"""Drive straight using inertial sensor for correction."""
start_heading = inertial_sensor.heading()
Kp = 0.5
drivetrain.set_drive_velocity(50, PERCENT)
drivetrain.drive(FORWARD)
start_position = left_motor_front.position(DEGREES)
target_degrees = distance_mm / WHEEL_TRAVEL_MM * 360
while True:
# Check distance
current_position = left_motor_front.position(DEGREES)
if current_position - start_position >= target_degrees:
drivetrain.stop()
return
# Heading correction
current_heading = inertial_sensor.heading()
error = start_heading - current_heading
if error > 180:
error -= 360
elif error < -180:
error += 360
correction = Kp * error
left_motors.set_velocity(50 + correction, PERCENT)
right_motors.set_velocity(50 - correction, PERCENT)
wait(20, MSEC)
Distance Sensor¶
Detects objects in front of the robot - like a bat using echolocation or a car's parking sensors!
How It Works¶
flowchart LR
subgraph "Distance Sensor"
A["Sends laser\nbeam"] --> B["Beam bounces\noff object"]
B --> C["Sensor measures\ntime"]
C --> D["Calculates\ndistance"]
end
E["Object"] -.-> B
style D fill:#c8e6c9,stroke:#2e7d32
The Parking Sensor Analogy¶
Just like your car beeps faster as you get closer to an obstacle:
| Distance | Car Beeping | Robot Action |
|---|---|---|
| > 500mm | No beeps | Drive fast |
| 300-500mm | Slow beeps | Slow down |
| 100-300mm | Fast beeps | Drive carefully |
| < 100mm | Constant beep! | STOP! |
Setup¶
Basic Usage¶
# Read distance in millimeters
dist = distance_sensor.object_distance(MM)
# Check if object is present
if distance_sensor.is_object_detected():
print("Something is there!")
Distance Sensor Decision Flowchart¶
flowchart TD
A["Read distance"] --> B{"dist > 500mm?"}
B -->|"YES"| C["Drive fast\n(80%)"]
B -->|"NO"| D{"dist > 200mm?"}
D -->|"YES"| E["Medium speed\n(50%)"]
D -->|"NO"| F{"dist > 100mm?"}
F -->|"YES"| G["Slow speed\n(30%)"]
F -->|"NO"| H["STOP!"]
C --> A
E --> A
G --> A
style H fill:#ffcdd2,stroke:#c62828
Stop Before Wall¶
def drive_until_wall(stop_distance=100):
"""Drive forward until wall is detected."""
drivetrain.drive(FORWARD)
while True:
dist = distance_sensor.object_distance(MM)
if dist < stop_distance:
drivetrain.stop()
return
wait(20, MSEC)
Wall Following¶
def follow_wall(target_distance=200):
"""Follow wall at constant distance."""
Kp = 0.2
while True:
dist = distance_sensor.object_distance(MM)
error = target_distance - dist
correction = Kp * error
# Adjust steering
left_speed = 50 + correction
right_speed = 50 - correction
left_motors.spin(FORWARD, left_speed, PERCENT)
right_motors.spin(FORWARD, right_speed, PERCENT)
wait(20, MSEC)
Optical Sensor¶
Detects colors - useful for block detection! Think of it like sorting M&Ms by color - the sensor can tell red from blue!
How It Works¶
flowchart LR
subgraph "Optical Sensor"
A["LED shines\nlight"] --> B["Light reflects\noff object"]
B --> C["Sensor reads\ncolor wavelength"]
C --> D["Returns\ncolor!"]
end
style A fill:#fff3e0,stroke:#ef6c00
style D fill:#c8e6c9,stroke:#2e7d32
The M&M Sorting Analogy¶
Imagine sorting candy by color: 1. Shine a light on the candy 2. Look at what color bounces back 3. Put red in one pile, blue in another 4. Reject any other colors
That's exactly what your robot does with blocks!
Setup¶
Basic Usage¶
# Turn on LED for color detection - REQUIRED!
optical_sensor.set_light_power(100)
optical_sensor.set_light(LedStateType.ON)
# Check for nearby object
if optical_sensor.is_near_object():
color = optical_sensor.color()
if color == Color.RED:
print("Red block!")
elif color == Color.BLUE:
print("Blue block!")
Block Color Sorting Flowchart¶
flowchart TD
A["Object near\nsensor?"] -->|"YES"| B["Read color"]
A -->|"NO"| A
B --> C{"color ==\nour alliance?"}
C -->|"YES (RED)"| D["Intake\nFORWARD"]
C -->|"NO (BLUE)"| E["Intake\nREVERSE"]
D --> F["Block\ncollected!"]
E --> G["Block\nejected!"]
style D fill:#c8e6c9,stroke:#2e7d32
style E fill:#ffcdd2,stroke:#c62828
Block Sorting¶
def grab_our_blocks_only(our_color):
"""Only intake blocks of our alliance color."""
while True:
if optical_sensor.is_near_object():
detected_color = optical_sensor.color()
if detected_color == our_color:
intake_motor.spin(FORWARD)
wait(500, MSEC)
intake_motor.stop()
else:
# Wrong color - eject!
intake_motor.spin(REVERSE)
wait(300, MSEC)
intake_motor.stop()
wait(50, MSEC)
GPS Sensor¶
Knows exact field position! It's like Google Maps for your robot - it knows exactly where on the field it is!
How GPS Works in VEX¶
flowchart LR
subgraph "GPS System"
A["Field strips\n(bar code)"] --> B["Camera on\nrobot"]
B --> C["Reads position\nfrom strips"]
C --> D["Reports X, Y\ncoordinates"]
end
style D fill:#c8e6c9,stroke:#2e7d32
The GPS sensor uses special field strips around the arena. It's like reading a QR code to know exactly where you are!
The Phone GPS Analogy¶
| Phone GPS | VEX GPS |
|---|---|
| "You are on Main Street" | "You are at X=500, Y=-300" |
| "Turn left in 100 feet" | "Turn 45° and drive 200mm" |
| "You have arrived" | "X=0, Y=0 (center of field)" |
Field Coordinate System¶
flowchart TB
subgraph "VEX Field (1800mm × 1800mm)"
A["(-900, 900)\nTop Left"] --- B["(0, 900)\nTop Center"] --- C["(900, 900)\nTop Right"]
A --- D["(-900, 0)\nLeft"]
B --- E["(0, 0)\nCENTER"]
C --- F["(900, 0)\nRight"]
D --- G["(-900, -900)\nBottom Left"]
E --- H["(0, -900)\nBottom Center"]
F --- I["(900, -900)\nBottom Right"]
end
style E fill:#c8e6c9,stroke:#2e7d32
Setup¶
Reading Position¶
# X and Y position on field (in mm from center)
x = gps_sensor.x_position(MM)
y = gps_sensor.y_position(MM)
# Heading from GPS (also available!)
heading = gps_sensor.heading()
print(f"Robot at: ({x}, {y}) facing {heading}°")
Return to Position¶
def go_to_position(target_x, target_y):
"""Navigate to specific field coordinates."""
while True:
current_x = gps_sensor.x_position(MM)
current_y = gps_sensor.y_position(MM)
# Calculate distance to target
dx = target_x - current_x
dy = target_y - current_y
distance = (dx**2 + dy**2) ** 0.5
if distance < 50: # Within 50mm
drivetrain.stop()
return
# Calculate angle to target
import math
target_angle = math.degrees(math.atan2(dy, dx))
# Turn toward target
turn_to_heading(target_angle)
# Drive toward target
drivetrain.drive_for(FORWARD, min(distance, 300), MM)
Combining Sensors¶
The real power of sensors comes from using them together! Each sensor has strengths - combine them for a smarter robot.
Sensor Fusion Flowchart¶
flowchart TD
subgraph "Multi-Sensor Autonomous"
A["Start"] --> B["Calibrate\nInertial"]
B --> C["Drive forward"]
C --> D{"Distance sensor:\nobject < 200mm?"}
D -->|"NO"| C
D -->|"YES"| E["Optical:\ncheck color"]
E --> F{"Our alliance\ncolor?"}
F -->|"YES"| G["Intake: grab block"]
F -->|"NO"| H["Back away"]
G --> I["GPS: return\nto start"]
H --> I
I --> J["Done!"]
end
style G fill:#c8e6c9,stroke:#2e7d32
style H fill:#fff3e0,stroke:#ef6c00
Which Sensor for What?¶
flowchart TD
Q["What do you need?"] --> A{"Need to know\nHEADING?"}
A -->|"YES"| A1["Inertial Sensor"]
A -->|"NO"| B{"Need to know\nDISTANCE to object?"}
B -->|"YES"| B1["Distance Sensor"]
B -->|"NO"| C{"Need to know\nCOLOR?"}
C -->|"YES"| C1["Optical Sensor"]
C -->|"NO"| D{"Need to know\nPOSITION on field?"}
D -->|"YES"| D1["GPS Sensor"]
D -->|"NO"| E["Maybe you don't\nneed a sensor?"]
style A1 fill:#e3f2fd,stroke:#1565c0
style B1 fill:#fff3e0,stroke:#ef6c00
style C1 fill:#f8bbd9,stroke:#c2185b
style D1 fill:#c8e6c9,stroke:#2e7d32
Smart Autonomous Example¶
def smart_autonomous():
"""Autonomous using multiple sensors."""
# 1. Calibrate inertial (required first!)
inertial_sensor.calibrate()
wait(3, SECONDS)
# 2. Drive until we see a goal (Distance sensor)
while distance_sensor.object_distance(MM) > 200:
drive_straight(100) # Uses Inertial for correction
# 3. Check block color before scoring (Optical sensor)
if optical_sensor.is_near_object():
if optical_sensor.color() == our_alliance_color:
# Score it!
intake_motor.spin(FORWARD)
wait(500, MSEC)
else:
# Wrong color, back away
drivetrain.drive_for(REVERSE, 200, MM)
# 4. Return to starting position (GPS sensor)
go_to_position(0, -500)
Sensor Combination Examples¶
| Sensors Combined | Use Case |
|---|---|
| Inertial + Distance | Drive straight until reaching wall |
| Inertial + GPS | Navigate to coordinates accurately |
| Distance + Optical | Approach block and check color |
| All four | Full autonomous navigation |
Summary¶
| Sensor | Measures | Best For |
|---|---|---|
| Inertial | Heading, rotation | Accurate turns, straight driving |
| Distance | Distance to objects | Wall detection, stopping |
| Optical | Color, proximity | Block sorting, line following |
| GPS | Field position | Navigation, return-to-position |
Sensors in Push Back Competition¶
Each sensor helps you score more points in the Push Back game!
Inertial Sensor for Accurate Autonomous¶
def push_back_auto_with_inertial():
"""
Autonomous that uses inertial for precise movements.
Accurate turns = More blocks in goals!
"""
inertial_sensor.calibrate()
wait(3, SECONDS)
# Drive straight toward first block cluster
drive_straight_with_correction(600)
# Turn exactly 45° to face long goal
turn_to_heading(45)
# Push blocks into goal
drivetrain.drive_for(FORWARD, 400, MM)
# Back up and turn to face center
drivetrain.drive_for(REVERSE, 200, MM)
turn_to_heading(90)
# Continue to next goal...
Distance Sensor for Goal Approach¶
def approach_goal_safely():
"""
Use distance sensor to stop at perfect pushing distance.
Too close = can't push effectively
Too far = blocks don't reach goal
"""
OPTIMAL_PUSH_DISTANCE = 100 # mm
while distance_sensor.object_distance(MM) > OPTIMAL_PUSH_DISTANCE:
drivetrain.drive(FORWARD, 50, PERCENT)
wait(20, MSEC)
drivetrain.stop()
return True # Ready to push!
Optical Sensor for Block Selection¶
flowchart LR
A["Block\napproaches"] --> B{"Optical:\nWhat color?"}
B -->|"RED"| C{"Our alliance\n= RED?"}
B -->|"BLUE"| D{"Our alliance\n= BLUE?"}
C -->|"YES"| E["Grab it!"]
C -->|"NO"| F["Eject!"]
D -->|"YES"| E
D -->|"NO"| F
style E fill:#c8e6c9,stroke:#2e7d32
style F fill:#ffcdd2,stroke:#c62828
def smart_intake(our_alliance_color):
"""
Only intake blocks of our alliance color!
Scoring opponent's blocks helps THEM!
"""
if optical_sensor.is_near_object():
block_color = optical_sensor.color()
if block_color == our_alliance_color:
intake_motor.spin(FORWARD)
wait(500, MSEC)
intake_motor.stop()
return True # Got our block!
else:
intake_motor.spin(REVERSE)
wait(300, MSEC)
intake_motor.stop()
return False # Rejected opponent's block
GPS for Skills Autonomous¶
def skills_with_gps():
"""
Use GPS to navigate efficiently across entire field.
Skills = 60 seconds to cover whole field!
"""
# Phase 1: Score at long goal
go_to_position(-600, 200)
push_blocks_into_goal()
# Phase 2: Score at center
go_to_position(0, 0)
push_blocks_into_goal()
# Phase 3: Score at far goal
go_to_position(600, 200)
push_blocks_into_goal()
# Phase 4: PARK! (GPS ensures we hit the zone)
go_to_position(0, -700) # Park zone coordinates
drivetrain.stop()
Progressive Exercises¶
Beginner: Stop Before Wall¶
Goal: Use distance sensor to stop before hitting a wall.
def drive_until_wall():
"""Drive forward and stop 150mm from wall."""
while distance_sensor.object_distance(MM) > 150:
drivetrain.drive(FORWARD, 50, PERCENT)
wait(20, MSEC)
drivetrain.stop()
print("Stopped at:", distance_sensor.object_distance(MM), "mm")
Success criteria: Robot stops consistently at 150mm ±20mm
Intermediate: Drive Straight with Inertial Correction¶
Goal: Drive in a straight line using inertial sensor for heading correction.
def drive_straight_500mm():
"""Drive 500mm in a straight line using heading correction."""
Kp = 0.5
start_heading = inertial_sensor.heading()
# Calculate target based on wheel rotations
start_position = left_motor_front.position(DEGREES)
target_degrees = 500 / WHEEL_TRAVEL_MM * 360
while left_motor_front.position(DEGREES) - start_position < target_degrees:
current_heading = inertial_sensor.heading()
error = start_heading - current_heading
# YOUR CODE: Handle wraparound
# if error > 180: error -= 360
# if error < -180: error += 360
correction = Kp * error
# YOUR CODE: Apply correction to motors
# left_motors.set_velocity(50 + correction, PERCENT)
# right_motors.set_velocity(50 - correction, PERCENT)
left_motors.spin(FORWARD)
right_motors.spin(FORWARD)
wait(20, MSEC)
drivetrain.stop()
Test: Push the robot sideways while it's driving - it should correct itself!
Challenge: Block Color Sorting¶
Goal: Create a system that only picks up blocks of your alliance color.
def color_sorting_intake():
"""
Complete intake system that:
1. Detects when a block is near
2. Checks the color
3. Grabs our color, ejects opponent's color
"""
OUR_ALLIANCE = Color.RED # Change for your alliance
# YOUR CODE: Turn on optical sensor LED
# optical_sensor.set_light_power(100)
# optical_sensor.set_light(LedStateType.ON)
while True:
if optical_sensor.is_near_object():
detected_color = optical_sensor.color()
# YOUR CODE: Compare to alliance color
# If match: intake FORWARD
# If no match: intake REVERSE (eject)
# YOUR CODE: Run motor for appropriate time
# wait() and then stop motor
wait(50, MSEC)
Bonus: Display the color on the Brain screen when detected!
Common Mistakes with Sensors¶
Mistake 1: Forgetting to Calibrate Inertial¶
# WRONG: Use inertial without calibrating
def bad_autonomous():
heading = inertial_sensor.heading() # Random garbage value!
# RIGHT: Always calibrate first
def good_autonomous():
inertial_sensor.calibrate()
wait(3, SECONDS) # MUST wait!
heading = inertial_sensor.heading() # Accurate!
Mistake 2: Moving During Calibration¶
# WRONG: Robot moves during calibration
def bad_calibration():
inertial_sensor.calibrate()
drivetrain.drive(FORWARD) # Moving while calibrating!
wait(3, SECONDS)
# RIGHT: Stay perfectly still
def good_calibration():
drivetrain.stop() # Ensure stopped
inertial_sensor.calibrate()
wait(3, SECONDS) # Robot is still
# NOW safe to move
Mistake 3: Not Waiting Enough for Calibration¶
# WRONG: Not enough wait time
inertial_sensor.calibrate()
wait(1, SECONDS) # Too short!
# RIGHT: Full 3 seconds
inertial_sensor.calibrate()
wait(3, SECONDS) # Proper calibration time
Mistake 4: Forgetting to Turn On Optical LED¶
# WRONG: Optical sensor doesn't work reliably
color = optical_sensor.color() # Depends on room lighting!
# RIGHT: Turn on LED for consistent lighting
optical_sensor.set_light_power(100)
optical_sensor.set_light(LedStateType.ON)
color = optical_sensor.color() # Consistent!
Mistake 5: Wrong Units¶
# WRONG: Mixing up units
dist = distance_sensor.object_distance(INCHES) # Oops, wanted MM!
if dist > 150: # This is 150 inches, not 150mm!
# RIGHT: Be consistent with units
dist = distance_sensor.object_distance(MM)
if dist > 150: # 150mm as intended
How Sensors Connect to Push Back¶
| Sensor | Push Back Use | Points Impact |
|---|---|---|
| Inertial | Accurate turns to face goals | More blocks in goals (+3 each) |
| Inertial | Straight line block pushing | Blocks reach goal, not walls |
| Distance | Stop at optimal pushing distance | Efficient pushing |
| Distance | Detect goals and walls | Avoid collisions |
| Optical | Identify alliance blocks | Don't score opponent's blocks! |
| GPS | Navigate entire field in Skills | Cover all 3 goal areas |
| GPS | Return to park zone | Guarantee 8-30 points |
Sensors = Consistent Scores¶
flowchart LR
subgraph "Without Sensors"
A1["Match 1:\n45 points"] --> B1["Match 2:\n28 points"]
B1 --> C1["Match 3:\n52 points"]
C1 --> D1["INCONSISTENT"]
end
subgraph "With Sensors"
A2["Match 1:\n58 points"] --> B2["Match 2:\n55 points"]
B2 --> C2["Match 3:\n60 points"]
C2 --> D2["CONSISTENT!"]
end
style D1 fill:#ffcdd2,stroke:#c62828
style D2 fill:#c8e6c9,stroke:#2e7d32
Bottom line: Sensors make your robot reliable. Reliable robots win tournaments!
← Previous: PID Control | Next: Skills Autonomous → | Review Q&A