Project 6.1: "Omnidirectional Robot"
Â
What youâll learn
- â Omnidirectional basics: Understand how 4 omni/mecanum wheels enable forward, backward, lateral, and rotational movement.
- â Direction maps: Set clear HIGH/LOW patterns for each motor to achieve the desired motion.
- â Anyâdirection control: Combine lateral, forward/backward, and rotation.
- â Pattern design: Create reusable movement sequences (Lâshapes, spirals, squares).
- â Reliability: Use prints and safeguards so the robot moves predictably and stops cleanly.
Blocks glossary (used in this project)
- Digital output (motor pins): Control L298N inputs for each wheelâs direction.
- Helper functions: Group motor actions (forward, back, left, right, rotate, stop).
- Serial println: Print âMOVE:âŠâ and âDEBUG:âŠâ messages so actions are visible.
- Variables: Store timing (ms) and patterns.
- Loops: Run sequences step by step with pauses.
What you need
| Part | How many? | Pin connection (suggested) |
|---|---|---|
| D1 R32 | 1 | USB cable (30 cm) |
| Omni/mecanum wheels | 4 | Mounted on 4 TT gear motors |
| TT gear motors | 4 | 1 per wheel |
| L298N motor driver modules | 2 | Driver A: IN1â18, IN2â19, IN3â5, IN4â23; Driver B: IN1â16, IN2â17, IN3â4, IN4â15 |
| Power for motors (external) | 1 | Motor supply per L298N; logic 5V to R32 shared ground |
Notes
- Use two L298N drivers to control 4 motors (frontâleft/frontâright on Driver A, rearâleft/rearâright on Driver B).
- Share ground between motor power and the D1 R32. Keep wiring tidy and labels on each wheel.
Before you start
- USB cable connected and stable
- Shared ground verified between R32 and L298N motor power
- Serial monitor open and showing:
print("Ready!") # Confirm serial is working so you can see messages
Microprojects 1â5
Microproject 6.1.1 â Forward/backward movement
Goal: Drive all wheels forward together, then backward, using clear pin patterns.
Blocks used:
- Digital output: Set IN pins for each wheelâs direction.
- Serial println: Print âMOVE:FWD/BACKâ and âMOTORS:STOPâ.
MicroPython code:
import machine # Import machine to control pins
import time # Import time for delays
# Driver A (front motors)
FL_IN1 = machine.Pin(18, machine.Pin.OUT) # Front-Left IN1
FL_IN2 = machine.Pin(19, machine.Pin.OUT) # Front-Left IN2
FR_IN1 = machine.Pin(5, machine.Pin.OUT) # Front-Right IN1
FR_IN2 = machine.Pin(23, machine.Pin.OUT) # Front-Right IN2
# Driver B (rear motors)
RL_IN1 = machine.Pin(16, machine.Pin.OUT) # Rear-Left IN1
RL_IN2 = machine.Pin(17, machine.Pin.OUT) # Rear-Left IN2
RR_IN1 = machine.Pin(4, machine.Pin.OUT) # Rear-Right IN1
RR_IN2 = machine.Pin(15, machine.Pin.OUT) # Rear-Right IN2
def all_stop(): # Define a helper to stop all motors
FL_IN1.value(0) # Set Front-Left IN1 LOW
FL_IN2.value(0) # Set Front-Left IN2 LOW
FR_IN1.value(0) # Set Front-Right IN1 LOW
FR_IN2.value(0) # Set Front-Right IN2 LOW
RL_IN1.value(0) # Set Rear-Left IN1 LOW
RL_IN2.value(0) # Set Rear-Left IN2 LOW
RR_IN1.value(0) # Set Rear-Right IN1 LOW
RR_IN2.value(0) # Set Rear-Right IN2 LOW
print("MOTORS:STOP") # Print stop status
def forward(): # Define forward movement
print("MOVE:FWD") # Print action
FL_IN1.value(1) # Front-Left forward HIGH
FL_IN2.value(0) # Front-Left forward LOW
FR_IN1.value(1) # Front-Right forward HIGH
FR_IN2.value(0) # Front-Right forward LOW
RL_IN1.value(1) # Rear-Left forward HIGH
RL_IN2.value(0) # Rear-Left forward LOW
RR_IN1.value(1) # Rear-Right forward HIGH
RR_IN2.value(0) # Rear-Right forward LOW
def backward(): # Define backward movement
print("MOVE:BACK") # Print action
FL_IN1.value(0) # Front-Left backward LOW
FL_IN2.value(1) # Front-Left backward HIGH
FR_IN1.value(0) # Front-Right backward LOW
FR_IN2.value(1) # Front-Right backward HIGH
RL_IN1.value(0) # Rear-Left backward LOW
RL_IN2.value(1) # Rear-Left backward HIGH
RR_IN1.value(0) # Rear-Right backward LOW
RR_IN2.value(1) # Rear-Right backward HIGH
forward() # Run forward
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
time.sleep(0.5) # Pause briefly
backward() # Run backward
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
Reflection: Forward/backward uses the same pattern on all four wheelsâgreat for straight motion tests.
Challenge:
- Easy: Change each move to 2 seconds.
- Harder: Add a safety âSTOPâ between moves for 1 full second.
Microproject 6.1.2 â Lateral movement (strafe left/right)
Goal: Move sideways using opposite wheel directions (mecanum/omni principle).
Blocks used:
- Digital output: Set pin patterns for strafe.
- Serial println: Print âMOVE:LEFT/RIGHTâ.
Tip: Depending on wheel orientation, you might need to swap left/right patterns.
MicroPython code:
import time # Import time for delays
def strafe_left(): # Define lateral left movement
print("MOVE:LEFT") # Print action
# Front-Left backward, Rear-Left forward
FL_IN1.value(0) # Front-Left backward LOW
FL_IN2.value(1) # Front-Left backward HIGH
RL_IN1.value(1) # Rear-Left forward HIGH
RL_IN2.value(0) # Rear-Left forward LOW
# Front-Right forward, Rear-Right backward
FR_IN1.value(1) # Front-Right forward HIGH
FR_IN2.value(0) # Front-Right forward LOW
RR_IN1.value(0) # Rear-Right backward LOW
RR_IN2.value(1) # Rear-Right backward HIGH
def strafe_right(): # Define lateral right movement
print("MOVE:RIGHT") # Print action
# Front-Left forward, Rear-Left backward
FL_IN1.value(1) # Front-Left forward HIGH
FL_IN2.value(0) # Front-Left forward LOW
RL_IN1.value(0) # Rear-Left backward LOW
RL_IN2.value(1) # Rear-Left backward HIGH
# Front-Right backward, Rear-Right forward
FR_IN1.value(0) # Front-Right backward LOW
FR_IN2.value(1) # Front-Right backward HIGH
RR_IN1.value(1) # Rear-Right forward HIGH
RR_IN2.value(0) # Rear-Right forward LOW
strafe_left() # Run strafe left
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
time.sleep(0.5) # Pause briefly
strafe_right() # Run strafe right
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
Reflection: True omni movement feels like âslidingââpin patterns must mirror left vs. right.
Challenge:
- Easy: Try 1.5 seconds per strafe.
- Harder: If drifting, add a short forward pulse to straighten after each strafe.
Microproject 6.1.3 â Rotation on its own axis
Goal: Spin in place by driving left and right sides in opposite directions.
Blocks used:
- Digital output: Opposite directions on left vs. right sides.
- Serial println: Print âMOVE:ROTATE_LEFT/ROTATE_RIGHTâ.
MicroPython code:
import time # Import time for delays
def rotate_left(): # Define rotation to the left
print("MOVE:ROTATE_LEFT") # Print action
# Left side backward
FL_IN1.value(0) # Front-Left backward LOW
FL_IN2.value(1) # Front-Left backward HIGH
RL_IN1.value(0) # Rear-Left backward LOW
RL_IN2.value(1) # Rear-Left backward HIGH
# Right side forward
FR_IN1.value(1) # Front-Right forward HIGH
FR_IN2.value(0) # Front-Right forward LOW
RR_IN1.value(1) # Rear-Right forward HIGH
RR_IN2.value(0) # Rear-Right forward LOW
def rotate_right(): # Define rotation to the right
print("MOVE:ROTATE_RIGHT") # Print action
# Left side forward
FL_IN1.value(1) # Front-Left forward HIGH
FL_IN2.value(0) # Front-Left forward LOW
RL_IN1.value(1) # Rear-Left forward HIGH
RL_IN2.value(0) # Rear-Left forward LOW
# Right side backward
FR_IN1.value(0) # Front-Right backward LOW
FR_IN2.value(1) # Front-Right backward HIGH
RR_IN1.value(0) # Rear-Right backward LOW
RR_IN2.value(1) # Rear-Right backward HIGH
rotate_left() # Run rotation left
time.sleep(1.0) # Spin for 1 second
all_stop() # Stop all motors
time.sleep(0.5) # Pause briefly
rotate_right() # Run rotation right
time.sleep(1.0) # Spin for 1 second
all_stop() # Stop all motors
Reflection: Inâplace rotation is a hallmark of omni robotsâmirror left and right sides.
Challenge:
- Easy: Reduce each spin to 0.6 s for tighter angle control.
- Harder: Build a ârotate 90°â helper using timing you calibrate.
Microproject 6.1.4 â Movement in any direction (combine strafe + forward/back)
Goal: Diagonal or angled motion by mixing forward/back with lateral patterns.
Blocks used:
- Digital output: Set combined patterns.
- Serial println: Print âMOVE:DIAG_*â.
Example shown: diagonal forwardâleft and forwardâright.
MicroPython code:
import time # Import time for delays
def diag_forward_left(): # Define diagonal forward-left motion
print("MOVE:DIAG_FWD_LEFT") # Print action
# Mix forward + left strafe: bias wheels so net vector is forward-left
FL_IN1.value(0) # Front-Left slightly backward to bias left
FL_IN2.value(1) # Front-Left backward HIGH
FR_IN1.value(1) # Front-Right forward
FR_IN2.value(0) # Front-Right forward
RL_IN1.value(1) # Rear-Left forward
RL_IN2.value(0) # Rear-Left forward
RR_IN1.value(0) # Rear-Right slightly backward to bias left
RR_IN2.value(1) # Rear-Right backward HIGH
def diag_forward_right(): # Define diagonal forward-right motion
print("MOVE:DIAG_FWD_RIGHT") # Print action
# Mix forward + right strafe: bias wheels so net vector is forward-right
FL_IN1.value(1) # Front-Left forward
FL_IN2.value(0) # Front-Left forward
FR_IN1.value(0) # Front-Right slightly backward to bias right
FR_IN2.value(1) # Front-Right backward HIGH
RL_IN1.value(0) # Rear-Left slightly backward to bias right
RL_IN2.value(1) # Rear-Left backward HIGH
RR_IN1.value(1) # Rear-Right forward
RR_IN2.value(0) # Rear-Right forward
diag_forward_left() # Run diagonal forward-left
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
time.sleep(0.5) # Pause briefly
diag_forward_right() # Run diagonal forward-right
time.sleep(1.0) # Move for 1 second
all_stop() # Stop all motors
Reflection: Anyâdirection motion comes from combining straight and sideways patternsâbalance the bias.
Challenge:
- Easy: Create diag_back_left and diag_back_right using the same idea.
- Harder: Make a âvector tableâ comment block that lists pin patterns for 8 compass directions.
Microproject 6.1.5 â Complex movement patterns
Goal: Execute repeatable sequences (square, Lâshape, spiral feel) with pauses and prints.
Blocks used:
- Functions: Reuse forward/strafe/rotate helpers.
- Loops: Step through patterns in order.
- Serial println: Print âPATTERN:*â.
MicroPython code:
import time # Import time for delays
def pattern_square(): # Define a square path pattern
print("PATTERN:SQUARE_START") # Announce pattern start
forward() # Move forward edge
time.sleep(1.0) # Travel duration
all_stop() # Stop before turning
rotate_right() # Rotate right at corner
time.sleep(0.6) # Corner rotation (approx 90° with calibration)
all_stop() # Stop after rotation
forward() # Next edge forward
time.sleep(1.0) # Travel duration
all_stop() # Stop before turning
rotate_right() # Rotate right at corner
time.sleep(0.6) # Corner rotation
all_stop() # Stop after rotation
forward() # Third edge
time.sleep(1.0) # Travel duration
all_stop() # Stop before turning
rotate_right() # Rotate right at corner
time.sleep(0.6) # Corner rotation
all_stop() # Stop after rotation
forward() # Final edge
time.sleep(1.0) # Travel duration
all_stop() # Stop at end
print("PATTERN:SQUARE_DONE") # Announce pattern completion
def pattern_l_shape(): # Define an L-shape pattern
print("PATTERN:L_START") # Announce pattern start
forward() # Long forward segment
time.sleep(1.5) # Travel duration
all_stop() # Stop before turn
rotate_right() # Rotate to face sideways
time.sleep(0.6) # Corner rotation
all_stop() # Stop after rotation
strafe_right() # Short right segment
time.sleep(0.8) # Travel duration
all_stop() # Stop at end
print("PATTERN:L_DONE") # Announce pattern completion
pattern_square() # Run square pattern
time.sleep(1.0) # Pause between patterns
pattern_l_shape() # Run L-shape pattern
Reflection: Patterns make the robot feel choreographedâclean stops keep shapes sharp.
Challenge:
- Easy: Add âPATTERN:LINE_SLIDEâ (forward then strafe).
- Harder: Create âPATTERN:SPIRAL_FEELâ by mixing shorter forward + brief strafes and turns.
Main project â Omnidirectional robot control
Blocks steps (with glossary)
- Pin setup: Define 8 motor inputs for 4 wheels across 2 L298N drivers.
- Motion helpers: forward, backward, strafe left/right, rotate left/right, stop.
- Anyâdirection: Diagonals by mixing forward/back and strafe.
- Patterns: Square and Lâshape sequences with rotation timing.
- Serial prints: Clear âMOVE:â and âPATTERN:â messages for visibility.
MicroPython code (mirroring blocks)
# Project 6.1 â Omnidirectional Robot
import machine # Import machine to control GPIO pins
import time # Import time for delays and sequence timing
# Driver A pins (front motors)
FL_IN1 = machine.Pin(18, machine.Pin.OUT) # Front-Left IN1
FL_IN2 = machine.Pin(19, machine.Pin.OUT) # Front-Left IN2
FR_IN1 = machine.Pin(5, machine.Pin.OUT) # Front-Right IN1
FR_IN2 = machine.Pin(23, machine.Pin.OUT) # Front-Right IN2
# Driver B pins (rear motors)
RL_IN1 = machine.Pin(16, machine.Pin.OUT) # Rear-Left IN1
RL_IN2 = machine.Pin(17, machine.Pin.OUT) # Rear-Left IN2
RR_IN1 = machine.Pin(4, machine.Pin.OUT) # Rear-Right IN1
RR_IN2 = machine.Pin(15, machine.Pin.OUT) # Rear-Right IN2
print("PINS: FL(18,19) FR(5,23) RL(16,17) RR(4,15)") # Print pin map
def all_stop(): # Stop all 4 motors
FL_IN1.value(0) # Front-Left IN1 LOW
FL_IN2.value(0) # Front-Left IN2 LOW
FR_IN1.value(0) # Front-Right IN1 LOW
FR_IN2.value(0) # Front-Right IN2 LOW
RL_IN1.value(0) # Rear-Left IN1 LOW
RL_IN2.value(0) # Rear-Left IN2 LOW
RR_IN1.value(0) # Rear-Right IN1 LOW
RR_IN2.value(0) # Rear-Right IN2 LOW
print("MOTORS:STOP") # Confirm stop
def forward(): # Drive forward (all wheels forward)
print("MOVE:FWD") # Print action
FL_IN1.value(1) # FL forward
FL_IN2.value(0) # FL forward
FR_IN1.value(1) # FR forward
FR_IN2.value(0) # FR forward
RL_IN1.value(1) # RL forward
RL_IN2.value(0) # RL forward
RR_IN1.value(1) # RR forward
RR_IN2.value(0) # RR forward
def backward(): # Drive backward (all wheels backward)
print("MOVE:BACK") # Print action
FL_IN1.value(0) # FL backward
FL_IN2.value(1) # FL backward
FR_IN1.value(0) # FR backward
FR_IN2.value(1) # FR backward
RL_IN1.value(0) # RL backward
RL_IN2.value(1) # RL backward
RR_IN1.value(0) # RR backward
RR_IN2.value(1) # RR backward
def strafe_left(): # Lateral left (combination of directions)
print("MOVE:LEFT") # Print action
FL_IN1.value(0) # FL backward
FL_IN2.value(1) # FL backward
FR_IN1.value(1) # FR forward
FR_IN2.value(0) # FR forward
RL_IN1.value(1) # RL forward
RL_IN2.value(0) # RL forward
RR_IN1.value(0) # RR backward
RR_IN2.value(1) # RR backward
def strafe_right(): # Lateral right (inverse of left)
print("MOVE:RIGHT") # Print action
FL_IN1.value(1) # FL forward
FL_IN2.value(0) # FL forward
FR_IN1.value(0) # FR backward
FR_IN2.value(1) # FR backward
RL_IN1.value(0) # RL backward
RL_IN2.value(1) # RL backward
RR_IN1.value(1) # RR forward
RR_IN2.value(0) # RR forward
def rotate_left(): # Spin left in place
print("MOVE:ROTATE_LEFT") # Print action
FL_IN1.value(0) # FL backward
FL_IN2.value(1) # FL backward
RL_IN1.value(0) # RL backward
RL_IN2.value(1) # RL backward
FR_IN1.value(1) # FR forward
FR_IN2.value(0) # FR forward
RR_IN1.value(1) # RR forward
RR_IN2.value(0) # RR forward
def rotate_right(): # Spin right in place
print("MOVE:ROTATE_RIGHT") # Print action
FL_IN1.value(1) # FL forward
FL_IN2.value(0) # FL forward
RL_IN1.value(1) # RL forward
RL_IN2.value(0) # RL forward
FR_IN1.value(0) # FR backward
FR_IN2.value(1) # FR backward
RR_IN1.value(0) # RR backward
RR_IN2.value(1) # RR backward
def diag_forward_left(): # Diagonal forward-left motion
print("MOVE:DIAG_FWD_LEFT") # Print action
FL_IN1.value(0) # FL backward
FL_IN2.value(1) # FL backward
FR_IN1.value(1) # FR forward
FR_IN2.value(0) # FR forward
RL_IN1.value(1) # RL forward
RL_IN2.value(0) # RL forward
RR_IN1.value(0) # RR backward
RR_IN2.value(1) # RR backward
def diag_forward_right(): # Diagonal forward-right motion
print("MOVE:DIAG_FWD_RIGHT") # Print action
FL_IN1.value(1) # FL forward
FL_IN2.value(0) # FL forward
FR_IN1.value(0) # FR backward
FR_IN2.value(1) # FR backward
RL_IN1.value(0) # RL backward
RL_IN2.value(1) # RL backward
RR_IN1.value(1) # RR forward
RR_IN2.value(0) # RR forward
def pattern_square(): # Square path using forward and rotate
print("PATTERN:SQUARE_START") # Announce start
for i in range(4): # Loop four edges
forward() # Move forward along edge
time.sleep(1.0) # Edge travel time
all_stop() # Stop before turning
rotate_right() # Turn right at corner
time.sleep(0.6) # Approx 90° turn (calibrate)
all_stop() # Stop after turn
print("PATTERN:SQUARE_DONE") # Announce completion
def pattern_l_shape(): # L-shape path using forward and strafe
print("PATTERN:L_START") # Announce start
forward() # Long forward leg
time.sleep(1.5) # Travel duration
all_stop() # Stop before next leg
strafe_right() # Short right leg
time.sleep(0.8) # Travel duration
all_stop() # Stop at end
print("PATTERN:L_DONE") # Announce completion
print("TEST:BASE MOTIONS") # Print section header
forward() # Test forward
time.sleep(0.8) # Move briefly
all_stop() # Stop
backward() # Test backward
time.sleep(0.8) # Move briefly
all_stop() # Stop
strafe_left() # Test left strafe
time.sleep(0.8) # Move briefly
all_stop() # Stop
strafe_right() # Test right strafe
time.sleep(0.8) # Move briefly
all_stop() # Stop
rotate_left() # Test rotate left
time.sleep(0.6) # Spin briefly
all_stop() # Stop
rotate_right() # Test rotate right
time.sleep(0.6) # Spin briefly
all_stop() # Stop
print("TEST:DIAGONALS") # Print section header
diag_forward_left() # Test diagonal forward-left
time.sleep(0.8) # Move briefly
all_stop() # Stop
diag_forward_right() # Test diagonal forward-right
time.sleep(0.8) # Move briefly
all_stop() # Stop
print("TEST:PATTERNS") # Print section header
pattern_square() # Run square pattern
time.sleep(1.0) # Pause
pattern_l_shape() # Run L-shape pattern
all_stop() # Final stop
print("DONE") # End of main project
External explanation
- What it teaches: How to map motor pin directions to omni behaviors: straight, lateral, rotation, diagonals, and full patterns.
- Why it works: Each wheelâs direction contributes to the net motion; mirroring sides or corners creates sideways and rotational movement; clean stops reduce drift and keep shapes neat.
- Key concept: âPin pattern â net motion.â
Story time
You press run and the robot glides left like itâs on ice, spins in place like a dancer, then draws a perfect square. It feels alive because your patterns are clear and confident.
Debugging (2)
Debugging 6.1.1 â Nonâomnidirectional movement
Problem: Robot only goes forward/back; strafe or rotate doesnât work.
Clues: Sideways commands feel like forward; prints show âMOVE:LEFT/RIGHTâ but motion is wrong.
Broken code:
# All wheels set to forward for strafe (incorrect)
FL_IN1.value(1); FL_IN2.value(0) # FL forward only
FR_IN1.value(1); FR_IN2.value(0) # FR forward only
Fixed code:
# Mixed directions for strafe (correct principle)
FL_IN1.value(0); FL_IN2.value(1) # FL backward
FR_IN1.value(1); FR_IN2.value(0) # FR forward
RL_IN1.value(1); RL_IN2.value(0) # RL forward
RR_IN1.value(0); RR_IN2.value(1) # RR backward
Why it works: Omni/mecanum needs opposing directions across corners to create lateral force.
Avoid next time: Donât reuse forward patterns for sideways motionâmirror corners instead.
Debugging 6.1.2 â Wheels get stuck
Problem: Some wheels donât turn or stutter under load.
Clues: One corner doesnât move; robot drifts and vibrates.
Broken code:
# Forget to stop before switching direction (causes stall)
forward() # Forward
strafe_left() # Immediately change to strafe
Fixed code:
all_stop() # Stop before changing direction
time.sleep(0.2) # Small pause to unload motors
strafe_left() # Now switch pattern safely
Why it works: A brief stop prevents fighting forces and lets motors reset.
Avoid next time: Insert short pauses when changing patterns or directions.
Final checklist
- Forward and backward move straight and stop cleanly
- Strafe left/right slides sideways without rotating
- Rotate left/right spins in place without drifting
- Diagonals combine forward/back + strafe correctly
- Patterns (square, Lâshape) run with clear prints and pauses
Extras
- đ§ Student tip: Label each wheel on the chassis (FL/FR/RL/RR) so debugging is fast.
- đ§âđ« Instructor tip: Have students draw a wheel direction table for each motion before wiring.
- đ Glossary:
- Strafe: Pure sideways motion without changing heading.
- Pattern: A sequence of moves forming a shape or path.
- Opposing corners: Diagonal wheels set to opposite directions to create lateral force.
- đĄ Mini tips:
- Calibrate rotation timing per battery level; note best values.
- Add a global âSAFE_DELAY = 0.2 sâ between moves to protect motors.
- Keep wires short and balanced to reduce voltage drop across drivers.

