🤖 Level 4 – Mobile Robotics

Project 4.12: "Integration N1+N2+N3+N4"

 

What you’ll learn

  • Goal 1: Combine remote control (IR and Bluetooth) with crawler motor control.
  • Goal 2: Integrate environmental monitoring (DHT11, PIR) and safety alerts with OLED.
  • Goal 3: Add autonomous navigation using ultrasonic and a simple rescue pick/drop.
  • Goal 4: Coordinate simple strategy interactions between multiple robots via BLE.
  • Goal 5: Design and test a free creative integration with clear logs and safe pacing.

Blocks glossary

  • irremote.NEC_RX(pin, buffer_len).any(), .code[0]: IR receiver for remote commands.
  • ble_peripheral.BLESimplePeripheral(name).send(text): Bluetooth messages (status/commands).
  • ble_handle.Handle().recv(callback): Receive BLE commands.
  • dhtx.DHT11(pin).temperature() / .humidity(): Read environment values.
  • machine.Pin(pin, machine.Pin.IN/OUT).value(v): PIR input and motor/gripper outputs.
  • sonar.Sonar(TRIG, ECHO).checkdist(): Ultrasonic distance for obstacle avoidance.
  • oled128x64.OLED(i2c,…).shows(…), rect(…), show(): OLED display of status data.
  • music.MIDI(pin).pitch_time(freq, ms) / play(…): Sound feedback for events.
  • print(…), time.sleep_ms(ms): Serial logs and calm pacing in loops.

What you need

PartHow many?Pin connection (R32)
D1 R32 (Robot A)1USB cable
D1 R32 (Robot B, optional)1USB cable
L298N motor driver1IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26
IR receiver (NEC)1Signal → Pin 26
Bluetooth (built‑in)1–2Internal
DHT111Signal → Pin 26
PIR1Signal → Pin 23
Ultrasonic HC‑SR041TRIG → 26, ECHO → 5
OLED 128×641SCL → 22, SDA → 21
Gripper (optional)1Signal → Pin 14
Buzzer (MIDI)1Pin 26
  • Share GND across all components.
  • Keep ultrasonic centered forward; PIR facing monitored area; microphone/sound optional.

Before you start

  • Wire motors (21,13,27,26), IR receiver (26), DHT11 (26), PIR (23), ultrasonic (26/5), OLED (22/21), gripper (14), buzzer (26).
  • Open Serial monitor for live logs on each device.
  • Quick test:
print("Ready!")  # Serial: confirm that the board is alive and connected

Microprojects 1–5

Microproject 4.12.1 – Remote‑controlled (IR/Bluetooth) crawler robot

import irremote                                     # Load IR remote library (NEC)
import ble_peripheral                              # Load Bluetooth peripheral
import ble_handle                                  # Load Bluetooth RX handle
import machine                                      # Load pins for motors
import time                                         # Load time for pacing

# Motors for crawler (L298N style)
in1 = machine.Pin(21, machine.Pin.OUT)              # Left IN1 output pin
in2 = machine.Pin(13, machine.Pin.OUT)              # Left IN2 output pin
in3 = machine.Pin(27, machine.Pin.OUT)              # Right IN3 output pin
in4 = machine.Pin(26, machine.Pin.OUT)              # Right IN4 output pin
print("[RC] Motors ready 21/13/27/26")              # Serial: confirm motor pins

# IR receiver setup
ir_rx = irremote.NEC_RX(26, 8)                      # IR receiver on pin 26, buffer length 8
print("[RC] IR RX ready pin=26")                    # Serial: confirm IR

# Bluetooth setup
ble_p = ble_peripheral.BLESimplePeripheral("Crawler-R32")  # BLE peripheral name
h = ble_handle.Handle()                             # RX handle for commands
print("[RC] BLE 'Crawler-R32' ready")               # Serial: confirm BLE

def forward():                                      # Helper: move forward
    in1.value(1); in2.value(0)                      # Left forward
    in3.value(1); in4.value(0)                      # Right forward
    print("[RC] FORWARD")                           # Serial: action log

def stop():                                         # Helper: stop movement
    in1.value(0); in2.value(0)                      # Left stop
    in3.value(0); in4.value(0)                      # Right stop
    print("[RC] STOP")                              # Serial: action log

def right():                                        # Helper: turn right
    in1.value(1); in2.value(0)                      # Left forward
    in3.value(0); in4.value(1)                      # Right backward
    print("[RC] RIGHT")                             # Serial: action log

def rx_ble(msg):                                    # RX BLE: remote commands
    s = str(msg)                                    # Ensure string format
    print("[RC] BLE RX:", s)                        # Serial: show received cmd
    if s == "CMD:FORWARD":                          # Command: forward
        forward()                                   # Execute forward
        ble_p.send("ACK:FORWARD")                   # Send ack
    elif s == "CMD:STOP":                           # Command: stop
        stop()                                      # Execute stop
        ble_p.send("ACK:STOP")                      # Send ack
    elif s == "CMD:RIGHT":                          # Command: right
        right()                                     # Execute right
        ble_p.send("ACK:RIGHT")                     # Send ack
    else:                                           # Unknown command
        ble_p.send("ERR:UNKNOWN")                   # Send error

h.recv(rx_ble)                                      # Register BLE RX callback
print("[RC] BLE RX registered")                     # Serial: confirm

# IR control (example mapping, replace with your remote codes)
CODE_FWD  = 0x18                                    # Example forward IR code
CODE_STOP = 0x1C                                    # Example stop IR code
CODE_RIGHT= 0x5A                                    # Example right IR code

if ir_rx.any():                                     # If any IR code is available
    c = ir_rx.code[0]                               # Read the first IR code
    print("[RC] IR code0:", c)                      # Serial: show code
    if c == CODE_FWD:                               # If forward code
        forward()                                   # Execute forward
    elif c == CODE_STOP:                            # If stop code
        stop()                                      # Execute stop
    elif c == CODE_RIGHT:                           # If right code
        right()                                     # Execute right
    else:                                           # Otherwise
        print("[RC] Unknown IR code")               # Serial: report unknown
time.sleep_ms(250)                                  # Short delay for calm control

Reflection: You combined IR and BLE remote commands with clear motor actions.
Challenge: Add “CMD:LEFT” and a corresponding IR code to complete basic steering.


Microproject 4.12.2 – Environmental and safety monitoring station (DHT11 + PIR + OLED)

import dhtx                                        # Load DHT11 sensor library
import machine                                     # Load hardware for PIR and I2C
import oled128x64                                  # Load OLED library
import time                                        # Load time for pacing

# PIR input
pir = machine.Pin(23, machine.Pin.IN)              # PIR motion sensor on pin 23
print("[Env] PIR=23 ready")                        # Serial: confirm PIR

# OLED setup (I2C)
i2c_extend = machine.SoftI2C(                      # Create I2C bus for OLED
    scl = machine.Pin(22),                         # SCL pin 22
    sda = machine.Pin(21),                         # SDA pin 21
    freq = 100000                                  # I2C frequency 100 kHz
)                                                  # End I2C setup

oled = oled128x64.OLED(                            # Create OLED object
    i2c_extend,                                    # Pass I2C bus
    address = 0x3c,                                # OLED I2C address
    font_address = 0x3A0000,                       # Font resource address
    types = 0                                      # SSD1306 type
)                                                  # End OLED creation
print("[Env] OLED ready 128x64")                   # Serial: confirm OLED

# Read environment
t = dhtx.DHT11(26).temperature()                   # Read temperature in °C from DHT11 on 26
h = dhtx.DHT11(26).humidity()                      # Read humidity in % from DHT11 on 26
m = pir.value()                                    # Read motion 0/1 from PIR
print("[Env] T=", t, "C H=", h, "% M=", m)         # Serial: show values

# Draw status on OLED
oled.rect(0,0,128,64,1)                            # Draw border frame
oled.shows("ENV",4,4,1,0,False)                    # Title text
oled.shows("T:"+str(t)+"C",4,24,1,0,False)         # Temperature line
oled.shows("H:"+str(h)+"%",4,40,1,0,False)         # Humidity line
oled.shows("M:"+str(m),84,4,1,0,False)             # Motion flag at top-right
oled.show()                                        # Update OLED content
time.sleep_ms(800)                                 # Short pause to observe

Reflection: You integrated environment and motion data into a clean OLED page.
Challenge: Print “ALERT:ENV” if T≥30 or H≥80, and show “ALERT!” at top-right on OLED.


Microproject 4.12.3 – Autonomous navigation and rescue (ultrasonic + gripper + buzzer)

import machine                                     # Load pins for motors and gripper
import sonar                                       # Load ultrasonic module
import music                                       # Load MIDI buzzer
import time                                        # Load time for pacing

# Motors
in1 = machine.Pin(21, machine.Pin.OUT)             # Left IN1
in2 = machine.Pin(13, machine.Pin.OUT)             # Left IN2
in3 = machine.Pin(27, machine.Pin.OUT)             # Right IN3
in4 = machine.Pin(26, machine.Pin.OUT)             # Right IN4
print("[Auto] Motors ready")                       # Serial: confirm motors

# Sonar
us = sonar.Sonar(26, 5)                            # Ultrasonic TRIG=26 ECHO=5
print("[Auto] Sonar ready 26/5")                   # Serial: confirm sonar

# Gripper and buzzer
grip = machine.Pin(14, machine.Pin.OUT)            # Gripper control ON/OFF on pin 14
midi = music.MIDI(26)                              # Buzzer via MIDI on pin 26
print("[Auto] Gripper=14 MIDI=26 ready")           # Serial: confirm actuators

SAFE_CM = 25                                       # Safe distance threshold for navigation

def forward():                                     # Helper: forward
    in1.value(1); in2.value(0)                     # Left forward
    in3.value(1); in4.value(0)                     # Right forward
    print("[Auto] FORWARD")                        # Serial: action

def right():                                       # Helper: turn right
    in1.value(1); in2.value(0)                     # Left forward
    in3.value(0); in4.value(1)                     # Right backward
    print("[Auto] RIGHT")                          # Serial: action

def stop():                                        # Helper: stop
    in1.value(0); in2.value(0)                     # Left stop
    in3.value(0); in4.value(0)                     # Right stop
    print("[Auto] STOP")                           # Serial: action

def pickup_drop():                                 # Helper: simulate rescue pickup and drop
    grip.value(1)                                  # Activate gripper (pickup)
    print("[Auto] PICKUP")                         # Serial: pickup event
    midi.pitch_time(660, 300)                      # Confirmation tone for pickup
    time.sleep_ms(600)                             # Hold briefly
    grip.value(0)                                  # Release gripper (drop)
    print("[Auto] DROP")                           # Serial: drop event
    midi.pitch_time(523, 300)                      # Confirmation tone for drop

d = us.checkdist()                                 # Read distance in cm
print("[Auto] d=", d)                              # Serial: show distance

if (d is None) or (d <= 0):                        # If distance invalid
    stop()                                         # Stop for safety
elif d < SAFE_CM:                                  # If obstacle near
    right()                                        # Turn to avoid
    time.sleep_ms(700)                             # Turn for a moment
    stop()                                         # Stop to recheck
else:                                              # If path clear
    forward()                                      # Advance
    time.sleep_ms(700)                             # Move briefly
    stop()                                         # Stop to simulate mission step
    pickup_drop()                                  # Simulate rescue pick/drop
time.sleep_ms(300)                                 # Calm cadence after action

Reflection: Your robot navigates safely and simulates rescue with clear tones.
Challenge: Increase SAFE_CM to 35 for cautious navigation and compare behavior.


Microproject 4.12.4 – Strategy game with multiple robots (BLE messages)

import ble_peripheral                              # Load Bluetooth peripheral (Robot A)
import ble_handle                                  # Load RX handle
import time                                        # Load time for pacing

ble_p = ble_peripheral.BLESimplePeripheral("Strat-A")  # Create BLE device for Robot A
h = ble_handle.Handle()                            # RX handle for messages
print("[Strat] BLE 'Strat-A' ready")               # Serial: confirm BLE

def rx_method(msg):                                # Receive strategy messages from Robot B
    s = str(msg)                                   # Ensure string
    print("[Strat] RX:", s)                        # Serial: show incoming message
    if s == "ALLY:NEED_HELP":                      # If ally requests help
        ble_p.send("ACK:ON_MY_WAY")                # Send acknowledgment
        print("[Strat] TX: ACK:ON_MY_WAY")         # Serial: mirror ack
    elif s == "ALLY:SAFE":                         # If ally says safe
        ble_p.send("ACK:GOOD")                     # Send positive ack
        print("[Strat] TX: ACK:GOOD")              # Serial: mirror ack
    else:                                          # Unknown message
        ble_p.send("ERR:UNKNOWN")                  # Send error
        print("[Strat] TX: ERR:UNKNOWN")           # Serial: mirror error

h.recv(rx_method)                                  # Register RX callback
print("[Strat] RX registered")                     # Serial: confirm registration

ble_p.send("ALLY:READY")                           # Send initial ready message to allies
print("[Strat] TX: ALLY:READY")                    # Serial: mirror message
time.sleep_ms(500)                                 # Short delay

Reflection: Short, clear messages let robots coordinate simple strategies.
Challenge: Add “ALLY:OBSTACLE” and respond with “ACK:AVOID” for teamwork.


Microproject 4.12.5 – Free creative project (your idea + logs)

import machine                                     # Load pins for your creative actuator
import music                                       # Load MIDI for custom sound
import time                                        # Load time for pacing

led = machine.Pin(13, machine.Pin.OUT)             # LED on pin 13 for creative feedback
midi = music.MIDI(26)                              # MIDI buzzer on pin 26
print("[Free] LED=13 MIDI=26 ready")               # Serial: confirm creative actuators

led.value(1)                                       # Turn LED ON to start your idea
print("[Free] LED ON")                             # Serial: log LED ON
midi.pitch_time(990, 250)                          # Play a custom "start" tone
print("[Free] Tone 990 Hz 250 ms")                 # Serial: log tone
time.sleep_ms(500)                                 # Short pause

led.value(0)                                       # Turn LED OFF to end the idea
print("[Free] LED OFF")                            # Serial: log LED OFF
midi.pitch_time(440, 250)                          # Play a custom "end" tone
print("[Free] Tone 440 Hz 250 ms")                 # Serial: log tone
time.sleep_ms(300)                                 # Small delay at the end

Reflection: You built a tiny creative action with sound and light feedback.
Challenge: Add an input (PIR or IR) to trigger your creative action automatically.


Main project

Full integration: IR/BLE remote, environment and safety OLED, autonomous navigation with rescue, multi‑robot strategy, and creative action

import irremote                                     # Load IR remote library
import ble_peripheral                              # Load Bluetooth peripheral
import ble_handle                                  # Load Bluetooth RX handle
import dhtx                                        # Load DHT11 library
import machine                                     # Load pins and ADC/I2C
import oled128x64                                  # Load OLED library
import sonar                                       # Load ultrasonic module
import music                                       # Load MIDI buzzer
import time                                        # Load time library

# --- Motors (crawler) ---
in1 = machine.Pin(21, machine.Pin.OUT)              # Left IN1
in2 = machine.Pin(13, machine.Pin.OUT)              # Left IN2
in3 = machine.Pin(27, machine.Pin.OUT)              # Right IN3
in4 = machine.Pin(26, machine.Pin.OUT)              # Right IN4
print("[Main] Motors ready 21/13/27/26")            # Serial: confirm motors

# --- Sensors (IR, DHT11, PIR, ultrasonic) ---
ir_rx = irremote.NEC_RX(26, 8)                      # IR RX on pin 26 with buffer 8
pir   = machine.Pin(23, machine.Pin.IN)             # PIR input on pin 23
us    = sonar.Sonar(26, 5)                          # Ultrasonic TRIG=26 ECHO=5
print("[Main] IR/PIR/SONAR ready")                  # Serial: confirm sensors

# --- OLED (I2C) ---
i2c_extend = machine.SoftI2C(                       # Create I2C bus
    scl = machine.Pin(22),                          # SCL pin 22
    sda = machine.Pin(21),                          # SDA pin 21
    freq = 100000                                   # 100 kHz bus
)                                                   # End I2C setup
oled = oled128x64.OLED(                             # Create OLED object
    i2c_extend, address=0x3c, font_address=0x3A0000, types=0  # OLED params
)                                                   # End OLED creation
print("[Main] OLED ready 128x64")                   # Serial: confirm OLED

# --- Gripper + buzzer ---
grip = machine.Pin(14, machine.Pin.OUT)             # Gripper on pin 14
midi = music.MIDI(26)                               # Buzzer on pin 26
print("[Main] Gripper=14 MIDI=26 ready")            # Serial: confirm actuators

# --- Bluetooth (strategy + remote) ---
ble_p = ble_peripheral.BLESimplePeripheral("Integrator-R32")  # BLE peripheral
h = ble_handle.Handle()                             # RX handle
print("[Main] BLE 'Integrator-R32' ready")          # Serial: confirm BLE

# --- Thresholds and codes ---
SAFE_CM = 25                                        # Safe distance threshold
TH_T_MAX = 30                                       # Temperature alert threshold
TH_H_MAX = 80                                       # Humidity alert threshold
CODE_FWD  = 0x18                                    # Example IR forward
CODE_STOP = 0x1C                                    # Example IR stop
CODE_RIGHT= 0x5A                                    # Example IR right
print("[Main] SAFE_CM=", SAFE_CM, " TH_T_MAX=", TH_T_MAX, " TH_H_MAX=", TH_H_MAX)  # Serial: show params

def forward():                                      # Helper: forward movement
    in1.value(1); in2.value(0)                      # Left forward
    in3.value(1); in4.value(0)                      # Right forward
    print("[Main] FORWARD")                         # Serial: action

def stop():                                         # Helper: stop
    in1.value(0); in2.value(0)                      # Left stop
    in3.value(0); in4.value(0)                      # Right stop
    print("[Main] STOP")                            # Serial: action

def right():                                        # Helper: right turn
    in1.value(1); in2.value(0)                      # Left forward
    in3.value(0); in4.value(1)                      # Right backward
    print("[Main] RIGHT")                           # Serial: action

def pickup_drop():                                  # Helper: simple rescue pick/drop
    grip.value(1)                                   # Activate gripper (pickup)
    print("[Main] PICKUP")                          # Serial: pickup
    midi.pitch_time(660, 250)                       # Tone for pickup
    time.sleep_ms(600)                              # Hold briefly
    grip.value(0)                                   # Release gripper (drop)
    print("[Main] DROP")                            # Serial: drop
    midi.pitch_time(523, 250)                       # Tone for drop

def rx_ble(msg):                                    # BLE RX: remote + strategy
    s = str(msg)                                    # Ensure string
    print("[Main] BLE RX:", s)                      # Serial: show command
    if s == "CMD:FORWARD":                          # Remote forward
        forward()                                   # Execute forward
        ble_p.send("ACK:FORWARD")                   # Ack
    elif s == "CMD:STOP":                           # Remote stop
        stop()                                      # Execute stop
        ble_p.send("ACK:STOP")                      # Ack
    elif s == "CMD:RIGHT":                          # Remote right
        right()                                     # Execute right
        ble_p.send("ACK:RIGHT")                     # Ack
    elif s == "ALLY:NEED_HELP":                     # Strategy message from ally
        ble_p.send("ACK:ON_MY_WAY")                 # Respond help
        print("[Main] TX: ACK:ON_MY_WAY")           # Serial: mirror
    elif s == "ALLY:OBSTACLE":                      # Ally obstacle
        ble_p.send("ACK:AVOID")                     # Suggest avoid
        print("[Main] TX: ACK:AVOID")               # Serial: mirror
    else:                                           # Unknown
        ble_p.send("ERR:UNKNOWN")                   # Error
        print("[Main] TX: ERR:UNKNOWN")             # Serial: mirror

h.recv(rx_ble)                                      # Register BLE RX callback
print("[Main] BLE RX registered")                   # Serial: confirm

# --- Main integration loop (few cycles demo) ---
for _ in range(0, 3, 1):                            # Run 3 integration cycles (demo)
    # Remote control via IR
    if ir_rx.any():                                 # If IR code available
        c = ir_rx.code[0]                           # Read first IR code
        print("[Main] IR code0:", c)                # Serial: show code
        if c == CODE_FWD:                           # If forward
            forward()                                # Move forward
        elif c == CODE_STOP:                        # If stop
            stop()                                   # Stop movement
        elif c == CODE_RIGHT:                       # If right
            right()                                  # Turn right

    # Environment monitoring
    t = dhtx.DHT11(26).temperature()                # Read temperature (°C)
    hval = dhtx.DHT11(26).humidity()                # Read humidity (%)
    m = pir.value()                                 # Read motion state (0/1)
    print("[Main] T=", t, "C H=", hval, "% M=", m)  # Serial: show environment status

    # OLED page
    oled.rect(0,0,128,64,1)                         # Draw frame
    oled.shows("INTG",4,4,1,0,False)                # Title
    oled.shows("T:"+str(t)+"C",4,24,1,0,False)      # Temperature
    oled.shows("H:"+str(hval)+"%",4,40,1,0,False)   # Humidity
    oled.shows("M:"+str(m),84,4,1,0,False)          # Motion flag
    oled.show()                                     # Update OLED

    # Autonomous step using ultrasonic
    d = us.checkdist()                              # Read distance cm
    print("[Main] d=", d)                           # Serial: show distance
    if (d is not None) and (d > 0) and (d < SAFE_CM):  # If obstacle near
        right()                                     # Turn to avoid
        time.sleep_ms(600)                          # Brief turn
        stop()                                      # Stop after turn
    else:                                           # Path clear
        forward()                                   # Move forward
        time.sleep_ms(600)                          # Brief forward
        stop()                                      # Stop to recheck

    # Simple rescue action if motion detected
    if m == 1:                                      # If PIR detects motion
        pickup_drop()                               # Simulate rescue
        ble_p.send("STATUS:RESCUE")                 # Notify status
        print("[Main] TX STATUS:RESCUE")            # Serial: mirror

    time.sleep_ms(500)                              # Calm cadence between cycles

print("[Main] Integration demo complete")           # Serial: end of demo

External explanation

This integration ties together remote control (IR/BLE), environmental monitoring (DHT11 + PIR), OLED status, autonomous navigation (ultrasonic), and a simple rescue (gripper + tones). Short messages and clear logs keep behavior understandable, with safe pacing to avoid floods and erratic motion.


Story time

Your integrated robot listens, watches, and moves. It follows your clicks, reads the room, avoids bumps, and even “rescues” with a gentle grip. When an ally calls, it answers—and it shows everything on its small screen like a tiny mission control.


Debugging (2)

Debugging 4.12.A – Interference between sensors

# Spread reads apart and reduce rapid actions to avoid conflicts
print("[Debug] Pace: read sensors, then act, then pause")  # Serial: guidance
time.sleep_ms(200)                                         # Short pause between reads
# Keep wires short and avoid shared noisy power lines for ultrasonic and motors

Debugging 4.12.B – Processing overload

# Reduce prints and keep 400–800 ms cadence between actions
print("[Debug] Trim logs and increase sleep_ms for stability")  # Serial: guidance
# Consider skipping OLED updates if values haven't changed

Final checklist

  • IR and BLE remote commands control the crawler reliably.
  • DHT11 + PIR readings show on the OLED with a clear frame.
  • Ultrasonic avoids obstacles with simple forward/right/stop logic.
  • Gripper + buzzer simulate rescue and provide feedback.
  • Bluetooth sends concise strategy/status messages.
  • Serial logs remain readable with steady pacing.

Extras

  • Student tip: Design a mission script—“search, rescue, return”—and log each step.
  • Instructor tip: Assign pairs of robots to cooperate via BLE (ALLY:NEED_HELP / ACK:ON_MY_WAY).
  • Glossary:
    • Remote control: Commands via IR or BLE to direct actions.
    • Autonomous: Robot decides based on sensor inputs and thresholds.
    • Integration: Combining multiple modules into one coherent system.
  • Mini tips:
    • Keep OLED text short to avoid clipping.
    • Confirm PIR before rescue to reduce false triggers.
    • Tune SAFE_CM and alert thresholds to match your environment.
On this page