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
| Part | How many? | Pin connection (R32) |
|---|---|---|
| D1 R32 (Robot A) | 1 | USB cable |
| D1 R32 (Robot B, optional) | 1 | USB cable |
| L298N motor driver | 1 | IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26 |
| IR receiver (NEC) | 1 | Signal → Pin 26 |
| Bluetooth (built‑in) | 1–2 | Internal |
| DHT11 | 1 | Signal → Pin 26 |
| PIR | 1 | Signal → Pin 23 |
| Ultrasonic HC‑SR04 | 1 | TRIG → 26, ECHO → 5 |
| OLED 128×64 | 1 | SCL → 22, SDA → 21 |
| Gripper (optional) | 1 | Signal → Pin 14 |
| Buzzer (MIDI) | 1 | Pin 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.