Driving the LEGO® Technic Cat D11 Bulldozer (42131) with the Xbox Controller
In this project, we will show you how to control the LEGO® Technic Cat D11 Bulldozer (42131) with the Xbox Controller.
This is a fun way to drive the bulldozer around without having to look at your phone screen all time time. You’ll also use the controller to switch between the functions of the blade, ripper, and ladder.
Requirements
To follow this project, you will need the following:
See this overview for all compatible models.
Understanding the code
The program consists of two main tasks that run in parallel. The first task handles the driving and steering of the bulldozer, as well as powering the function motor.
The second task controls the position of the motor that selects which function is being operated. The details are included as comments in the program below.
When the program begins, it resets the function selector to the zero position, corresponding to the blade up and down movement. You can select any function by pressing the A/B/X/Y buttons. This will switch the function selector as follows:
- X (blue): Moves the blade up and down.
- B (red): Tilts the blade back and forth.
- Y (yellow): Moves the ladder up and down.
- A (green): Moves the ripper up and down.
Once the function is selected, use the bumper buttons to control the motor that powers the selected function.
Use the direction pad to control the tracks. You can change
the drive acceleration in the main program to a lower value to make the
vehicle start and stop driving more gradually. You can also change the speed
values corresponding the left
and right
motor blocks.
Running the Pybricks program
This project uses Pybricks on your LEGO hub. Pybricks makes your creations come alive and helps you unlock the full potential of your LEGO Technic, City, MINDSTORMS, BOOST, or Spike sets.
If you haven’t already, install Pybricks on your hub as shown below, or check out our getting started guide for more details. You can go back to the LEGO firmware and apps at any time.
Now import the program you downloaded earlier, as shown below. Click to connect your hub and ▶ to start!
You can run imported block programs even if you’re not signed up. This is a great way to try out Pybricks and see how it works.
Pairing with the Xbox Controller
The very first time, you’ll need to pair the hub with your controller:
- Turn the controller on.
- Press and hold the pairing button on the back.
- Release after a few seconds. The controller light will start flashing more rapidly. This is pairing mode.
- Then start your program. Either using the Pybricks app, or using the green button on the hub if you’ve already loaded the program earlier.
The hub will start looking for your controller when the program starts. When the connection is successful, the controller light will stop flashing and stay on for as long as the program is running.
You can see this in the video below.
Connecting without pairing
The next time you use it, pairing is not required. Just turn the controller on. The hub will connect to it automatically when your program runs.
The Xbox controller remembers only one device. If you use the controller with another LEGO hub or your Xbox console, and then with this hub again, you’ll need to pair them again as above.
Updating the Xbox Controller
If you often use the Xbox Controller with your console, it is probably already up to date. If you have not used it for a while or if you bought one recently, you may need to update it.
To update the controller without a console, you can use the Xbox Accessories app on a Windows computer. Connect the controller via USB to the computer and follow the instructions in the app to click on “Update now”. If it is already at firmware version 5.17 or later, you don’t need to do anything.
Technic Hub limitations
Once you start your program, the Technic Hub will disconnect from your computer to free up the connection for the Xbox Controller. This means you can’t use the Pybricks app to stop the program or change the code while the Xbox Controller is connected.
To change your program, stop the program by pressing the green button on the hub. Then you can connect to the hub with the Pybricks app again, and change your program as needed.
Running it as a Python program
You can also run this project as a Python (MicroPython) program. The following code was generated from the block program above. To run it, create a new empty Python program in Pybricks and copy the code into it.
from pybricks.iodevices import XboxController
from pybricks.parameters import Button, Direction, Port, Stop
from pybricks.pupdevices import Motor
from pybricks.tools import multitask, run_task, wait
# Set up all devices.
left = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right = Motor(Port.B, Direction.CLOCKWISE)
function = Motor(Port.C, Direction.CLOCKWISE)
switch = Motor(Port.D, Direction.CLOCKWISE)
controller = XboxController()
# Initialize variables.
left_end = 0
right_end = 0
busy_switching = 1
async def switch_function(number):
global busy_switching
await wait(1)
# This function takes care of moving the motor to positions 0, 1, 2, or 3:
# 0: Blade up/down.
# 1: Ripper.
# 2: Ladder.
# 3: Blade tilt.
# First, indicate that we are going to be busy switching.
busy_switching = 1
for count in range(5):
await wait(1)
# Go to the target angle. But give up if it takes more
# than 2 seconds, which means it's stuck for now.
await multitask(switch.run_target(750, number * 90, Stop.COAST), wait(2000), race=True)
# Let's check if we're on target.
if abs(switch.angle() - number * 90) <= 10:
# We are close to the target angle.
# so we can exit this repeating loop.
break
# Otherwise, we're not done yet, so we must be stuck.
# Let's wiggle the motor around to try to get it unstuck.
switch.track_target(0)
await wait(1000)
switch.track_target(270)
await wait(1000)
switch.stop()
# We're no longer busy switching, so we can drive again.
busy_switching = 0
async def main1():
# This main task will handle driving and the motor
# that powers (not switches) the function gearbox.
left.control.limits(acceleration=2500)
right.control.limits(acceleration=2500)
while True:
await wait(1)
if busy_switching:
# If we are currently busy switching the function, we stop
# driving and powering the function motor to be safe.
left.stop()
right.stop()
function.stop()
else:
# Otherwise drive the motors based on the buttons that are pressed.
# Use the bumpers for the function motor.
if Button.RB in controller.buttons.pressed():
function.dc(100)
elif Button.LB in controller.buttons.pressed():
function.dc(-100)
else:
function.stop()
# Use the direction pad for driving.
if controller.dpad() == 1:
# Forward
left.run(1000)
right.run(1000)
elif controller.dpad() == 2:
# Forward / Right
right.stop()
left.run(1000)
elif controller.dpad() == 3:
# Right
left.run(1000)
right.run(-1000)
elif controller.dpad() == 4:
# Reverse / Right
right.run(-1000)
left.stop()
elif controller.dpad() == 5:
# Reverse
left.run(-1000)
right.run(-1000)
elif controller.dpad() == 6:
# Reverse / Left
left.run(-1000)
right.stop()
elif controller.dpad() == 7:
# Left
left.run(-1000)
right.run(1000)
elif controller.dpad() == 8:
# Forward / Left
right.run(1000)
left.stop()
else:
# Nothing, so stop.
left.stop()
right.stop()
async def main2():
global right_end, left_end
# This task will handle the motor that switches the function gearbox.
# First it resets the gearbox by finding the start and end stops.
await switch.run_until_stalled(500, Stop.COAST, 50)
right_end = switch.angle()
await switch.run_until_stalled(-500, Stop.COAST, 50)
left_end = switch.angle()
switch.reset_angle((left_end + 270 - right_end) / 2)
await switch_function(0)
# Now we run the main loop of operating the switch.
while True:
await wait(1)
# Wait for the center switch to be pressed.
while not any(controller.buttons.pressed()):
await wait(1)
# Then select the function based on which other button is pressed.
if Button.X in controller.buttons.pressed():
# Blade up/down function.
await switch_function(0)
while Button.X in controller.buttons.pressed():
await wait(1)
elif Button.A in controller.buttons.pressed():
# Ripper function.
await switch_function(1)
while Button.A in controller.buttons.pressed():
await wait(1)
elif Button.Y in controller.buttons.pressed():
# Ladder function.
await switch_function(2)
while Button.Y in controller.buttons.pressed():
await wait(1)
elif Button.B in controller.buttons.pressed():
# Blade tilt function.
await switch_function(3)
while Button.B in controller.buttons.pressed():
await wait(1)
else:
# No other button pressed, so don't switch.
pass
async def main():
await multitask(main1(), main2())
run_task(main())