Project 3.7: "R32 Dual System"
ย
๐ Project 3.7 โ R32 Dual System
๐ฏ What youโll learn
- โ Goal 1: Connect two D1 R32 boards in a masterโslave relationship
- โ Goal 2: Combine IR and Bluetooth communication between them
- โ Goal 3: Distribute tasks across devices for efficiency
- โ Goal 4: Synchronize actions between boards
- โ Goal 5: Achieve cooperative control (two boards acting as one system)
Key ideas
- Short definition: Two boards can share work โ one leads, one follows.
- Realโworld link: Distributed systems power robotics swarms, smart homes, and multiplayer devices.
๐งฑ Blocks glossary (used in this project)
- IR send/receive: Transmit and decode infrared signals.
- Bluetooth peripheral/central: Advertise and connect for wireless communication.
- Callback: Function triggered when data arrives.
- Digital output: Control LEDs or motors.
- PWM output: Control speed or brightness.
- Variable: Store state (role, task, sync flag).
- def function: Encapsulate reusable actions (send_cmd(), sync(), etc.).
- Loop: Continuous cooperation cycle.
๐งฐ What you need
| Part | How many? | Pin connection (D1 R32) |
|---|---|---|
| D1 R32 | 2 | USB cables |
| IR modules | 2 | TX/RX on Pin 26 |
| Bluetooth | 2 | Builtโin |
| LEDs | 2 | Pin 13 each |
- Board 1 = Master, Board 2 = Slave.
- Both have IR TX/RX and Bluetooth active.
- LEDs show sync status.
โ Before you start
- Plug in both boards via USB.
- Open two serial monitors (one per board).
- Test print shows:
print("Ready!") # Confirm serial is working
๐ฎ Microprojects (5 mini missions)
๐ฎ Microproject 3.7.1 โ Basic MasterโSlave
Goal: Master sends a command, Slave executes it.
MicroPython code (Master):
# Microproject 3.7.1 โ Master sends command via IR
import irremote # Load IR library
import time # Load time library
ir_tx = irremote.NEC_TX(26, False, 100) # IR transmitter on Pin 26
print("[Master] IR TX ready") # Serial: confirm setup
def send_cmd(code): # Helper: send IR command
ir_tx.transmit(0x00, code, 0x00) # Send with address=0x00, control=0x00
print("[Master] Sent code:", hex(code)) # Serial: log send
while True:
send_cmd(0x18) # Send โforwardโ code
time.sleep_ms(2000) # Wait 2 seconds
MicroPython code (Slave):
# Microproject 3.7.1 โ Slave receives IR and acts
import irremote # Load IR library
import machine # Load hardware pin library
import time # Load time library
ir_rx = irremote.NEC_RX(26, 8) # IR receiver on Pin 26
pin13 = machine.Pin(13, machine.Pin.OUT) # LED on Pin 13
print("[Slave] IR RX ready") # Serial: confirm setup
while True:
if ir_rx.any(): # If a code arrived
code = ir_rx.code[0] # Read first code
print("[Slave] Got code:", hex(code)) # Serial: log receive
if code == 0x18: # If forward code
pin13.value(1) # LED ON
print("[Slave] LED ON") # Serial: action
time.sleep_ms(500) # Keep ON
pin13.value(0) # LED OFF
Reflection: One board commands, the other obeys โ simple teamwork.
Challenge: Add a second code for โLED OFFโ.
๐ฎ Microproject 3.7.2 โ IR + Bluetooth Communication
Goal: Master sends IR, Slave replies via Bluetooth.
Slave code (reply):
# Microproject 3.7.2 โ Slave replies via Bluetooth
import irremote
import ble_peripheral
import ble_handle
import time
ir_rx = irremote.NEC_RX(26, 8) # IR RX
ble_p = ble_peripheral.BLESimplePeripheral('Slave-R32') # Peripheral
handle = ble_handle.Handle() # Handle
print("[Slave] Ready IR+BT")
def send_bt(msg): # Helper: send Bluetooth
ble_p.send(msg)
print("[Slave] BT TX:", msg)
while True:
if ir_rx.any():
code = ir_rx.code[0]
print("[Slave] Got IR:", hex(code))
send_bt("ACK:" + hex(code)) # Reply via Bluetooth
time.sleep_ms(500)
Reflection: Two radios cooperate โ IR in, Bluetooth out.
Challenge: Add LED blink when ACK is sent.
๐ฎ Microproject 3.7.3 โ Distributed tasks
Goal: Master handles sensors, Slave handles actuators.
Master code (sensor):
# Microproject 3.7.3 โ Master reads sensor, sends IR
import irremote
import machine
import time
adc2 = machine.ADC(machine.Pin(2)) # Joystick X
adc2.atten(machine.ADC.ATTN_11DB)
adc2.width(machine.ADC.WIDTH_12BIT)
ir_tx = irremote.NEC_TX(26, False, 100)
print("[Master] Sensor+IR ready")
def send_angle(angle):
ir_tx.transmit(0x00, angle, 0x00)
print("[Master] Sent angle:", angle)
while True:
val = adc2.read()
angle = int((val*180)/4095)
send_angle(angle)
time.sleep_ms(500)
Slave code (actuator):
# Microproject 3.7.3 โ Slave moves servo
import irremote
import servo
import time
ir_rx = irremote.NEC_RX(26, 8)
print("[Slave] Servo ready")
while True:
if ir_rx.any():
angle = ir_rx.code[0]
servo.servo180_angle(13, angle)
print("[Slave] Servo angle:", angle)
time.sleep_ms(200)
Reflection: One board senses, the other acts โ division of labor.
Challenge: Add a second sensor for speed.
๐ฎ Microproject 3.7.4 โ Device synchronization
Goal: Both boards blink LEDs in sync.
Master code:
# Microproject 3.7.4 โ Master sync blink
import irremote
import time
ir_tx = irremote.NEC_TX(26, False, 100)
print("[Master] Sync ready")
def send_sync():
ir_tx.transmit(0x00, 0xAA, 0x00) # Sync code
print("[Master] Sent SYNC")
while True:
send_sync()
time.sleep_ms(1000)
Slave code:
# Microproject 3.7.4 โ Slave sync blink
import irremote
import machine
import time
ir_rx = irremote.NEC_RX(26, 8)
pin13 = machine.Pin(13, machine.Pin.OUT)
print("[Slave] Sync ready")
while True:
if ir_rx.any():
code = ir_rx.code[0]
if code == 0xAA:
pin13.value(1)
print("[Slave] LED ON")
time.sleep_ms(200)
pin13.value(0)
Reflection: Sync makes two devices act as one.
Challenge: Add Bluetooth confirmation after each sync.
Perfecto ๐, retomemos el Project 3.7: โR32 Dual Systemโ justo donde lo dejamos โ en el Microproject 3.7.5 โ Cooperative control.
๐ฎ Microproject 3.7.5 โ Cooperative control
Goal: Master sets a mode (e.g., TURBO, QUIET, STOP), Slave executes accordingly.
๐ MicroPython code (Master)
# Microproject 3.7.5 โ Master sets mode and sends via IR
import irremote # Load IR library
import time # Load time library
ir_tx = irremote.NEC_TX(26, False, 100) # IR transmitter on Pin 26
print("[Master] Mode control ready") # Serial: confirm setup
def send_mode(code): # Helper: send mode code
ir_tx.transmit(0x00, code, 0x00) # Send IR with address=0x00, control=0x00
print("[Master] Sent mode:", hex(code))# Serial: log send
while True: # Main loop
send_mode(0x55) # Example mode TURBO
time.sleep_ms(3000) # Wait 3 seconds
send_mode(0x16) # Example mode QUIET
time.sleep_ms(3000) # Wait 3 seconds
send_mode(0x19) # Example mode STOP
time.sleep_ms(3000) # Wait 3 seconds
๐ MicroPython code (Slave)
# Microproject 3.7.5 โ Slave executes modes received from Master
import irremote # Load IR library
import machine # Load hardware pin library
import time # Load time library
ir_rx = irremote.NEC_RX(26, 8) # IR receiver on Pin 26
pwm5 = machine.PWM(machine.Pin(5)) # PWM left motor enable
pwm18 = machine.PWM(machine.Pin(18))# PWM right motor enable
pwm5.freq(2000) # Set PWM frequency
pwm18.freq(2000) # Set PWM frequency
print("[Slave] Mode execution ready") # Serial: confirm setup
speed = 650 # Default medium duty
pwm5.duty(speed) # Apply left motor duty
pwm18.duty(speed) # Apply right motor duty
def stop_all(): # Helper: stop motors
pwm5.duty(0) # Duty 0 left
pwm18.duty(0) # Duty 0 right
print("[Slave] Motors STOP") # Serial: log stop
while True: # Main loop
if ir_rx.any(): # If a code arrived
code = ir_rx.code[0] # Read first code
print("[Slave] Got mode:", hex(code)) # Serial: log receive
if code == 0x55: # TURBO mode
speed = 1023 # Max duty
pwm5.duty(speed)
pwm18.duty(speed)
print("[Slave] TURBO speed =", speed)
elif code == 0x16: # QUIET mode
speed = 400 # Lower duty
pwm5.duty(speed)
pwm18.duty(speed)
print("[Slave] QUIET speed =", speed)
elif code == 0x19: # STOP mode
stop_all() # Call stop helper
else: # Unknown code
print("[Slave] Unknown mode") # Serial: warn
time.sleep_ms(250) # Small delay
Reflection: Master sets the mode, Slave adapts โ cooperative control achieved.
Challenge:
- Easy: Add a NORMAL mode (650 duty).
- Harder: Use Bluetooth to confirm mode changes back to the Master.
โจ Main project โ R32 dual system (masterโslave with IR, Bluetooth, sync, and cooperative control)
๐ง Blocks steps (with glossary)
- IR send/receive: Master transmits control/mode/sync codes; Slave decodes and acts.
- Bluetooth peripheral: Slave acknowledges important events back to a PC or to logs.
- PWM output: Slave adjusts motor speed according to mode (TURBO/QUIET/NORMAL/STOP).
- Digital output: LEDs on Pin 13 indicate sync and activity on both boards.
- def functions: Clear helpers to keep logic readable and reusable (send_cmd, send_sync, send_mode, stop_all, ack_bt).
- Loop: Continuous cycle to command, sync, and confirm cooperatively.
Block sequence:
- Master: Initialize IR TX (Pin 26), LED (Pin 13), and define helpers send_cmd(), send_sync(), send_mode().
- Slave: Initialize IR RX (Pin 26), LED (Pin 13), PWM on Pins 5 and 18, Bluetooth peripheral, and helpers ack_bt(), apply_mode(), stop_all().
- Master loop: Send SYNC each second, alternate commands (FORWARD/BACKWARD/TURNS), and set driving modes (TURBO/QUIET/NORMAL/STOP).
- Slave loop: Decode IR codes; blink LED on SYNC; execute direction pins; set PWM for modes; send Bluetooth ACK for received codes.
- Keep delays short and logs clear to maintain responsiveness.
๐ MicroPython code โ Master (R32 #1)
# Main Project 3.7 โ MASTER board: IR command + sync + mode transmitter
# This board is the "captain": it sends IR commands/modes and a periodic SYNC signal.
import irremote # Load IR communication library
import machine # Load hardware pin/LED library
import time # Load time library for delays
pin13 = machine.Pin(13, machine.Pin.OUT) # Prepare activity LED on Pin 13
print("[Master] LED on Pin 13 ready") # Serial: LED initialized
ir_tx = irremote.NEC_TX(26, False, 100) # Create IR transmitter on Pin 26 (power 100%)
print("[Master] IR TX ready on Pin 26") # Serial: IR transmitter ready
# Define canonical IR codes (matching earlier projects)
CODE_FORWARD = 0x18 # Forward direction code
CODE_BACKWARD = 0x52 # Backward direction code
CODE_LEFT = 0x08 # Turn left code
CODE_RIGHT = 0x5A # Turn right code
CODE_SYNC = 0xAA # Synchronization ping code
MODE_TURBO = 0x55 # Turbo mode code
MODE_QUIET = 0x16 # Quiet mode code
MODE_STOP = 0x19 # Stop mode code
MODE_NORMAL = 0x46 # Normal mode code (use '2' mapping)
def send_cmd(code): # Helper: send a directional command via IR
ir_tx.transmit(0x00, code, 0x00) # Transmit IR (address 0x00, control 0x00)
print("[Master] CMD sent:", hex(code)) # Serial: log command
def send_mode(code): # Helper: send a driving mode via IR
ir_tx.transmit(0x00, code, 0x00) # Transmit IR mode code
print("[Master] MODE sent:", hex(code)) # Serial: log mode
def send_sync(): # Helper: send a SYNC pulse
ir_tx.transmit(0x00, CODE_SYNC, 0x00) # Transmit SYNC code
print("[Master] SYNC sent") # Serial: log sync
def blink(t_ms): # Helper: blink activity LED for t_ms milliseconds
pin13.value(1) # LED ON
time.sleep_ms(t_ms) # Hold ON for t_ms
pin13.value(0) # LED OFF
print("[Master] Starting cooperative cycle") # Serial: start message
while True: # Main loop: alternate sync, commands, and modes
send_sync() # Send periodic SYNC pulse
blink(80) # Blink LED shortly to mark SYNC
time.sleep_ms(1000) # Wait ~1 second between syncs
send_cmd(CODE_FORWARD) # Send FORWARD command
blink(80) # Blink LED for action feedback
time.sleep_ms(600) # Short movement window
send_cmd(CODE_RIGHT) # Send RIGHT turn command
blink(80) # Blink LED feedback
time.sleep_ms(500) # Short movement window
send_mode(MODE_TURBO) # Switch to TURBO speed mode
blink(120) # Blink LED a bit longer for mode changes
time.sleep_ms(1000) # Hold TURBO for a moment
send_cmd(CODE_BACKWARD) # Send BACKWARD command
blink(80) # Blink LED feedback
time.sleep_ms(600) # Short movement window
send_mode(MODE_QUIET) # Switch to QUIET speed mode
blink(120) # Blink LED for mode indication
time.sleep_ms(1000) # Hold QUIET for a moment
send_cmd(CODE_LEFT) # Send LEFT turn command
blink(80) # Blink LED feedback
time.sleep_ms(500) # Short movement window
send_mode(MODE_NORMAL) # Switch to NORMAL speed mode
blink(120) # Blink LED for mode change
time.sleep_ms(1000) # Hold NORMAL briefly
send_mode(MODE_STOP) # Finally, send STOP mode
blink(150) # Blink LED slightly longer to mark STOP
time.sleep_ms(1200) # Pause before the next cycle
๐ MicroPython code โ Slave (R32 #2)
# Main Project 3.7 โ SLAVE board: IR receiver + Bluetooth ACK + motor control + sync LED
# This board is the "crew": it decodes IR commands/modes, drives motors, and sends ACK via Bluetooth.
import irremote # Load IR communication library
import ble_peripheral # Load Bluetooth peripheral helper
import ble_handle # Load Bluetooth callback handle
import machine # Load hardware (PWM/pin) library
import time # Load time library for delays
pin13 = machine.Pin(13, machine.Pin.OUT) # Activity/sync LED on Pin 13
print("[Slave] LED on Pin 13 ready") # Serial: LED initialized
ir_rx = irremote.NEC_RX(26, 8) # IR receiver on Pin 26 with buffer size 8
print("[Slave] IR RX ready on Pin 26") # Serial: IR receiver ready
ble_p = ble_peripheral.BLESimplePeripheral('Slave-R32') # Bluetooth peripheral named 'Slave-R32'
handle = ble_handle.Handle() # Bluetooth handle for callbacks
print("[Slave] Bluetooth peripheral 'Slave-R32' ready") # Serial: Bluetooth initialized
pwm5 = machine.PWM(machine.Pin(5)) # PWM enable A (left motor)
pwm18 = machine.PWM(machine.Pin(18)) # PWM enable B (right motor)
pwm5.freq(2000) # Set PWM frequency to 2000 Hz
pwm18.freq(2000) # Set PWM frequency to 2000 Hz
print("[Slave] PWM set at 2000 Hz (Pins 5, 18)") # Serial: PWM frequency set
in23 = machine.Pin(23, machine.Pin.OUT) # IN1 left direction pin
in19 = machine.Pin(19, machine.Pin.OUT) # IN2 left direction pin
in13 = machine.Pin(13, machine.Pin.OUT) # IN3 right direction pin
in21 = machine.Pin(21, machine.Pin.OUT) # IN4 right direction pin
print("[Slave] Direction pins ready: 23,19,13,21") # Serial: direction pins initialized
# Canonical IR codes (must match Master)
CODE_FORWARD = 0x18 # Forward code
CODE_BACKWARD = 0x52 # Backward code
CODE_LEFT = 0x08 # Left turn code
CODE_RIGHT = 0x5A # Right turn code
CODE_SYNC = 0xAA # Synchronization code
MODE_TURBO = 0x55 # Turbo mode code
MODE_QUIET = 0x16 # Quiet mode code
MODE_STOP = 0x19 # Stop mode code
MODE_NORMAL = 0x46 # Normal mode code
speed = 650 # Start with NORMAL duty (~medium)
pwm5.duty(speed) # Apply initial left duty
pwm18.duty(speed) # Apply initial right duty
print("[Slave] Start speed =", speed) # Serial: starting speed
def ack_bt(label): # Helper: send a Bluetooth acknowledgment
ble_p.send("ACK:" + str(label)) # Send acknowledgment string
print("[Slave] BT ACK:", label) # Serial: log ACK
def stop_all(): # Helper: stop all motor directions and duty
in23.value(0) # Left IN1 OFF
in19.value(0) # Left IN2 OFF
in13.value(0) # Right IN3 OFF
in21.value(0) # Right IN4 OFF
pwm5.duty(0) # Left duty to 0
pwm18.duty(0) # Right duty to 0
print("[Slave] Motors STOP") # Serial: stopped
def drive_forward(): # Helper: set pins to forward motion
in23.value(1) # Left forward ON
in19.value(0) # Left backward OFF
in13.value(1) # Right forward ON
in21.value(0) # Right backward OFF
print("[Slave] Move FORWARD") # Serial: log direction
def drive_backward(): # Helper: set pins to backward motion
in23.value(0) # Left forward OFF
in19.value(1) # Left backward ON
in13.value(0) # Right forward OFF
in21.value(1) # Right backward ON
print("[Slave] Move BACKWARD") # Serial: log direction
def turn_left(): # Helper: set pins to spin left
in23.value(0) # Left forward OFF
in19.value(1) # Left backward ON
in13.value(1) # Right forward ON
in21.value(0) # Right backward OFF
print("[Slave] Turn LEFT") # Serial: log turn
def turn_right(): # Helper: set pins to spin right
in23.value(1) # Left forward ON
in19.value(0) # Left backward OFF
in13.value(0) # Right forward OFF
in21.value(1) # Right backward ON
print("[Slave] Turn RIGHT") # Serial: log turn
def apply_mode(code): # Helper: set speed/duty based on mode code
global speed # Use global to update speed variable
if code == MODE_TURBO: # If mode TURBO
speed = 1023 # Max duty
print("[Slave] Mode TURBO โ duty", speed) # Serial: log mode
elif code == MODE_QUIET: # If mode QUIET
speed = 400 # Lower duty
print("[Slave] Mode QUIET โ duty", speed) # Serial: log mode
elif code == MODE_NORMAL: # If mode NORMAL
speed = 650 # Medium duty
print("[Slave] Mode NORMAL โ duty", speed) # Serial: log mode
elif code == MODE_STOP: # If mode STOP
stop_all() # Stop motors immediately
ack_bt("STOP") # ACK STOP mode
return # End early (duty already zero)
pwm5.duty(speed) # Apply left duty to new speed
pwm18.duty(speed) # Apply right duty to new speed
ack_bt("MODE:" + hex(code)) # Send ACK about new mode
def blink(t_ms): # Helper: blink LED for t_ms milliseconds
pin13.value(1) # LED ON
time.sleep_ms(t_ms) # Hold ON
pin13.value(0) # LED OFF
print("[Slave] Cooperative receiver loop starting") # Serial: start message
while True: # Main cooperative loop
if ir_rx.any(): # If any IR code is available
code = ir_rx.code[0] # Read first buffered code
print("[Slave] IR code:", hex(code)) # Serial: show code
if code == CODE_SYNC: # If this is a SYNC code
blink(80) # Short blink to mark sync reception
ack_bt("SYNC") # Send Bluetooth ACK for sync
elif code == CODE_FORWARD: # If FORWARD command
drive_forward() # Apply forward pins
ack_bt("CMD:FORWARD") # ACK forward action
elif code == CODE_BACKWARD: # If BACKWARD command
drive_backward() # Apply backward pins
ack_bt("CMD:BACKWARD") # ACK backward action
elif code == CODE_LEFT: # If LEFT turn command
turn_left() # Apply left turn pins
ack_bt("CMD:LEFT") # ACK left turn action
elif code == CODE_RIGHT: # If RIGHT turn command
turn_right() # Apply right turn pins
ack_bt("CMD:RIGHT") # ACK right turn action
elif (code == MODE_TURBO or # If any known mode code
code == MODE_QUIET or
code == MODE_NORMAL or
code == MODE_STOP):
apply_mode(code) # Apply mode and ACK
else: # Otherwise unknown code
print("[Slave] Unknown code:", hex(code)) # Serial: warn unknown
ack_bt("UNKNOWN:" + hex(code)) # Send unknown ACK
time.sleep_ms(150) # Short delay for responsiveness
๐ External explanation
- What it teaches: How to architect two boards so they act as one system: one sends commands and timing (IR), the other executes, controls motors, and reports status (Bluetooth).
- Why it works: IR is simple, fast, and one-way โ great for commands and sync pings. Bluetooth is robust and two-way โ perfect for acknowledgments and logging. PWM drives speed, while digital pins set directions.
- Key concept: Define clear roles (Master โ โcaptainโ, Slave โ โcrewโ) and use small, readable helper functions to make cooperation reliable and easy to understand.
โจ Story time
Youโve built a tiny two-robot team. The captain flashes SYNC and calls the plays; the crew follows, drives, and replies โACKโ over the radio. Two boards, one mind โ thatโs distributed robotics.
๐ต๏ธ Debugging (2)
๐ Debugging 3.7.A โ Device desynchronization
- Problem: Slave blinks late or misses SYNC; motions feel out of step.
- Clues: Sparse โ[Slave] IR code: 0xAAโ logs; inconsistent LED timing.
- Fix: Shorten loop delays (150โ250 ms), space SYNC to ~1000 ms, and reduce print verbosity.
# In Slave loop: keep delays short and predictable
time.sleep_ms(150) # Maintain tight loop timing
# In Master loop: keep SYNC cadence stable
time.sleep_ms(1000) # SYNC every ~1s
๐ Debugging 3.7.B โ Control conflicts
- Problem: Master sends TURBO but Slave stays QUIET or UNKNOWN.
- Clues: Hex codes donโt match across boards or were edited in one place only.
- Fix: Centralize canonical codes and reuse the same constants on both boards.
# Define shared codes identically on Master and Slave
MODE_TURBO = 0x55
MODE_QUIET = 0x16
MODE_NORMAL = 0x46
MODE_STOP = 0x19
# Use these constants everywhere to avoid mismatches
โ Final checklist
- Master sends SYNC regularly and blinks LED for feedback.
- Slave blinks on SYNC and ACKs via Bluetooth.
- Direction commands (forward/back/left/right) execute correctly.
- Modes (TURBO/QUIET/NORMAL/STOP) change PWM speed as expected.
- Logs are short, readable, and help you confirm behavior.
๐ Extras
- ๐ง Student tip: Test each part alone first: (1) Master IR send, (2) Slave IR receive, (3) Slave PWM/motors, (4) Bluetooth ACKs. Then merge.
- ๐งโ๐ซ Instructor tip: Emphasize code constants and function reuse; they prevent mismatches and simplify debugging for teams.
- ๐ Glossary:
- MasterโSlave: One device coordinates, the other executes.
- SYNC pulse: A simple, repeated signal to align timing across devices.
- ACK: A short โI heard youโ message confirming receipt or action.
- ๐ก Mini tips:
- Keep delays modest (80โ120 ms blinks, 150โ250 ms loop sleeps).
- Place code constants at the top and donโt duplicate them inconsistently.
- Use helper functions for every repeated action (send_cmd, apply_mode, stop_all).