📡 Level 3 – Advanced Communication

Project 3.12: "N1+N2+N3 Integration"

 

What you’ll learn

  • Integration: Combine IR, Bluetooth, Serial, LCD, sensors, and motors across multiple boards.
  • Coordination: Assign clear roles (Controller, Robot, Station, Game) and keep messages concise.
  • Reliability: Use helper functions and pacing to avoid overload and keep feedback immediate.
  • Usability: Format displays for clarity, add alerts, and confirm actions with ACKs.
  • Creativity: Leave hooks for students to expand modes, views, and mini‑games safely.

Blocks glossary

  • IR send/receive: Quick one‑way command path (Controller → Robot).
  • Bluetooth central/peripheral: Two‑way text for modes, ACKs, telemetry, and game events.
  • Serial print: Minimal logs to track actions and keep debugging simple.
  • LCD (I2C): Render formatted sensor values, status, and messages.
  • Digital input/output: Buttons, LEDs for control and feedback.
  • PWM output: Motor speed control on ENA/ENB.
  • ADC input: Sensor readings (e.g., light sensor on Pin 2).
  • def function: Small reusable helpers (send_cmd, apply_mode, fit16, ack).
  • Loop: Steady cadence across boards; don’t spam.

What you need

Role Board Connections
Controller D1 R32 Buttons A–F, IR TX → 26, Bluetooth central
Robot D1 R32 IR RX → 26; L298N: ENA → 5 (PWM), ENB → 18 (PWM), IN1 → 23, IN2 → 19, IN3 → 13, IN4 → 21; Bluetooth peripheral
Station D1 R32 LCD 1602 I2C (SCL → 26, SDA → 5), ADC2 sensor, Bluetooth peripheral
Game D1 R32 Buttons, LEDs, Bluetooth peripheral
  • Share GND across boards and drivers.
  • Keep Bluetooth devices within 1–3 m; align IR 20–50 cm.

Before you start

  • Open serial monitors for Controller, Robot, Station, and Game.
  • Quick test:
print("Ready!")  # Confirm serial is working

🎮 Microprojects (5 mini missions)

🎮 Microproject 3.12.1 – Complete remote control system

Goal: Controller sends dual‑medium commands (IR + Bluetooth), Robot decodes both.

# Microproject 3.12.1 – Controller: IR + Bluetooth commands
import irremote                                   # Load IR library
import ble_central                                # Load Bluetooth central helper
import time                                       # Load time library
ir_tx = irremote.NEC_TX(26, False, 100)           # IR transmitter on Pin 26
print("[Controller] IR TX ready on 26")           # Serial: confirm IR init
central = ble_central.BLESimpleCentral()          # Bluetooth central object
print("[Controller] BT central ready")            # Serial: confirm BT init
def connect_robot():                              # Helper: connect to Robot-R32
    central.scan()                                # Scan peripherals nearby
    time.sleep_ms(600)                            # Short scanning delay
    central.connect('Robot-R32')                  # Connect by name
    print("[Controller] BT connected to Robot-R32")  # Serial: connection OK
connect_robot()                                   # Establish BT link
CODE_FORWARD = 0x18                               # IR forward command code
def send_ir(code):                                # Helper: send IR command
    ir_tx.transmit(0x00, code, 0x00)              # Transmit IR packet (addr/control 0x00)
    print("[Controller] IR TX:", hex(code))       # Serial: log IR send
def send_bt(text):                                # Helper: send Bluetooth text
    central.send(text)                            # Transmit text to robot
    print("[Controller] BT TX:", text)            # Serial: log BT send
while True:                                       # Demo loop
    send_ir(CODE_FORWARD)                         # Send IR forward command
    send_bt("CMD:FORWARD")                        # Send BT forward command
    time.sleep_ms(1000)                           # Pace loop at ~1 second

Reflection: Redundant commands make control resilient.
Challenge: Add STOP on button F and send both “CMD:STOP” and the IR STOP code.


🎮 Microproject 3.12.2 – Advanced remote‑controlled robot

Goal: Robot decodes IR + BT, applies assisted modes, and is ready for pulse precision.

# Microproject 3.12.2 – Robot: decode IR + BT and apply modes
import ble_peripheral                             # Load Bluetooth peripheral helper
import ble_handle                                 # Load Bluetooth callback helper
import irremote                                   # Load IR library
import machine                                     # Load hardware pin/PWM library
import time                                        # Load time library
ble_p = ble_peripheral.BLESimplePeripheral('Robot-R32')  # Peripheral named 'Robot-R32'
print("[Robot] BLE 'Robot-R32' ready")                   # Serial: confirm BT init
handle = ble_handle.Handle()                              # Callback handle for RX
print("[Robot] BT handle ready")                          # Serial: confirm handle
ir_rx = irremote.NEC_RX(26, 8)                    # IR receiver on Pin 26 with buffer 8
print("[Robot] IR RX ready on 26")                # Serial: confirm IR init
pwmA = machine.PWM(machine.Pin(5))                # ENA PWM (left)
pwmB = machine.PWM(machine.Pin(18))               # ENB PWM (right)
pwmA.freq(2000)                                   # Set PWM frequency 2kHz
pwmB.freq(2000)                                   # Set PWM frequency 2kHz
speed = 650                                       # Default duty (NORMAL)
pwmA.duty(speed)                                  # Apply left duty
pwmB.duty(speed)                                  # Apply right duty
print("[Robot] Start duty =", speed)              # Serial: initial speed
def apply_mode(text):                             # Helper: set speed by mode text
    global speed                                  # Use global speed variable
    if text == "MODE:TURBO":                      # Turbo mode
        speed = 1023                              # Max duty
    elif text == "MODE:QUIET":                    # Quiet mode
        speed = 400                               # Low duty
    elif text == "MODE:NORMAL":                   # Normal mode
        speed = 650                               # Medium duty
    elif text == "MODE:STOP":                     # Stop mode
        speed = 0                                 # Duty zero
    pwmA.duty(speed)                              # Apply left duty
    pwmB.duty(speed)                              # Apply right duty
    ble_p.send("ACK:" + text)                     # ACK mode change
    print("[Robot] Mode applied:", text)          # Serial: log applied mode
def handle_method(msg):                           # Callback: process BT messages
    s = str(msg)                                  # Ensure string type
    print("[Robot] BT RX:", s)                    # Serial: show incoming text
    if s.startswith("MODE:"):                     # If mode command
        apply_mode(s)                             # Apply the mode
    elif s == "CMD:FORWARD":                      # Forward command (BT)
        print("[Robot] BT CMD FORWARD")           # Serial: placeholder forward action
        ble_p.send("ACK:FORWARD")                 # ACK forward command
    elif s == "CMD:STOP":                         # Stop command (BT)
        apply_mode("MODE:STOP")                   # Apply stop via mode helper
    else:                                         # Unknown BT command
        ble_p.send("ERR:UNKNOWN")                 # Error feedback
handle.recv(handle_method)                        # Register BT callback
print("[Robot] Callback registered")              # Serial: callback active
while True:                                       # IR decode loop
    if ir_rx.any():                               # If any IR code available
        code = ir_rx.code[0]                      # Read first code from buffer
        print("[Robot] IR RX:", hex(code))        # Serial: log IR code (hook for actions)
    time.sleep_ms(150)                            # Loop pacing for responsiveness

Reflection: Dual‑path control with modes keeps actions clear.
Challenge: Add pulse stepping for “STEP:FWD/STEP:BACK” with timed motor pulses.


🎮 Microproject 3.12.3 – Total monitoring station

Goal: Station shows SENSOR, SYSTEM, and BT messages neatly on LCD.

# Microproject 3.12.3 – Station: LCD monitoring (SENSOR + SYSTEM + BT)
import ble_peripheral                             # Load Bluetooth peripheral helper
import ble_handle                                 # Load Bluetooth callback helper
import machine                                     # Load hardware (I2C/ADC) library
import i2clcd                                      # Load LCD library (1602 I2C)
import time                                        # Load time library
ble_p = ble_peripheral.BLESimplePeripheral('Station-R32')  # Peripheral named 'Station-R32'
print("[Station] BLE 'Station-R32' ready")                 # Serial: confirm BT init
handle = ble_handle.Handle()                               # Callback handle for RX
print("[Station] Handle ready")                            # Serial: confirm handle
i2c = machine.SoftI2C(                                     # Create software I2C bus
    scl = machine.Pin(26),                                  # SCL on Pin 26
    sda = machine.Pin(5),                                   # SDA on Pin 5
    freq = 100000                                           # I2C at 100 kHz
)                                                           # End I2C creation
lcd = i2clcd.LCD(i2c, 16, 0x27)                             # LCD controller (16 cols, addr 0x27)
print("[Station] LCD ready")                                # Serial: confirm LCD
adc2 = machine.ADC(machine.Pin(2))                          # ADC sensor on Pin 2
adc2.atten(machine.ADC.ATTN_11DB)                           # Full voltage range 0–3.3V
adc2.width(machine.ADC.WIDTH_12BIT)                         # 12-bit resolution
print("[Station] ADC2 ready")                               # Serial: confirm ADC
page = "SENSOR"                                             # Active page state
last_rx = ""                                                # Last BT message shown
print("[State] page=SENSOR")                                # Serial: initial page
def fit16(text):                                            # Helper: fit/pad to 16 chars
    s = str(text)                                           # Cast to string
    return s[:16] if len(s) > 16 else s.ljust(16)           # Truncate or pad
def show(row, text):                                        # Helper: render one row
    lcd.shows(fit16(text),                                  # Fit the text to 16 chars
              column = 0,                                   # Start at column 0
              line = row,                                   # Row 0 or 1
              center = False)                               # Left align for data lines
    print("[LCD]", row, ":", fit16(text))                   # Serial: mirror display
def handle_method(msg):                                     # Callback: receive BT
    global last_rx, page                                    # Use global state
    s = str(msg)                                            # Ensure string
    print("[Station] BT RX:", s)                            # Serial: log incoming
    last_rx = s                                             # Save last message
    if s.startswith("SRC:"):                                # If source selection
        page = s.split(":")[1]                              # Set page (SENSOR/SYSTEM/BT)
        ble_p.send("ACK:SRC=" + page)                       # Acknowledge change
handle.recv(handle_method)                                  # Register BT callback
print("[Station] Callback registered")                      # Serial: callback active
while True:                                                 # UI loop
    if page == "SENSOR":                                    # SENSOR view
        raw = adc2.read()                                   # Read ADC raw
        volts = (raw * 3.3) / 4095                          # Convert to volts
        show(0, "Sensor ADC2")                              # Header
        show(1, "Raw " + str(raw))                          # Raw value line
    elif page == "SYSTEM":                                  # SYSTEM view
        ms = time.ticks_ms()                                # Uptime ms
        secs = ms // 1000                                   # Seconds
        show(0, "System Status")                            # Header
        show(1, "Up {:02d}:{:02d}".format(secs // 60, secs % 60))  # mm:ss uptime
    else:                                                   # BT view
        show(0, "Last BT Msg")                              # Header
        show(1, last_rx if last_rx else "(none)")           # Last message or placeholder
    time.sleep_ms(850)                                      # Smooth update cadence

Reflection: Clean display makes monitoring calm and informative.
Challenge: Add “SRC:CYCLE” to rotate pages automatically.


🎮 Microproject 3.12.4 – Multi‑device gaming

Goal: A simple “serve/hit/miss” mini‑game using Bluetooth across two boards.

# Microproject 3.12.4 – Game: serve/hit/miss (Peripheral side)
import ble_peripheral                             # Load Bluetooth peripheral helper
import ble_handle                                 # Load Bluetooth callback helper
import machine                                     # Load hardware pin library
import time                                        # Load time library
ble_p = ble_peripheral.BLESimplePeripheral('Game-R32')  # Peripheral named 'Game-R32'
print("[Game-B] BLE 'Game-R32' ready")                  # Serial: confirm BT init
handle = ble_handle.Handle()                              # Callback handle
print("[Game-B] Handle ready")                            # Serial: confirm handle
led = machine.Pin(13, machine.Pin.OUT)                   # LED for feedback
print("[Game-B] LED=13 ready")                            # Serial: confirm LED
turn = "A"                                               # Track turn (starts at A)
round_active = False                                     # Rally state flag
def set_led(on):                                         # Helper: LED on/off
    led.value(1 if on else 0)                            # Set LED state
    print("[Game-B] LED:", "ON" if on else "OFF")        # Serial: LED state
def send(text):                                          # Helper: send message to central
    ble_p.send(text)                                     # Transmit text
    print("[Game-B] TX:", text)                          # Serial: log send
def handle_method(msg):                                  # Callback: process game messages
    global turn, round_active                            # Use global rally state
    s = str(msg)                                         # Ensure string type
    print("[Game-B] RX:", s)                             # Serial: log incoming
    if s == "SERVE?":                                    # Start rally request
        round_active = True                              # Mark rally active
        turn = "A"                                       # Serve to A
        set_led(True)                                    # LED ON to mark rally
        send("SERVE:A")                                  # Notify serve side
    elif s == "HIT:A" and round_active and turn == "A":  # A hits correctly
        turn = "B"                                       # Ball goes to B
        send("BALL:B")                                   # Notify next turn
    elif s == "HIT:B" and round_active and turn == "B":  # B hits correctly
        turn = "A"                                       # Ball goes to A
        send("BALL:A")                                   # Notify next turn
    elif s == "MISS:A" and round_active and turn == "A": # A misses
        round_active = False                             # End rally
        set_led(False)                                   # LED OFF
        send("MISS:A")                                   # Announce miss
    elif s == "MISS:B" and round_active and turn == "B": # B misses
        round_active = False                             # End rally
        set_led(False)                                   # LED OFF
        send("MISS:B")                                   # Announce miss
    else:                                                # Unknown or out-of-turn
        send("ERR:STATE")                                # Error feedback
handle.recv(handle_method)                                # Register callback
print("[Game-B] Callback registered")                     # Serial: callback active
while True:                                               # Idle; actions via callback
    time.sleep_ms(150)                                    # Keep CPU cool
# Microproject 3.12.4 – Game: serve/hit/miss (Central side)
import ble_central                                # Load Bluetooth central helper
import ble_handle                                 # Load Bluetooth handle helper
import machine                                     # Load hardware pin library
import time                                        # Load time library
btn_hit  = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # HIT button
btn_miss = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP)  # MISS button
print("[Game-A] Buttons HIT=26 MISS=25 ready")                 # Serial: confirm inputs
central = ble_central.BLESimpleCentral()           # Central object
print("[Game-A] Central ready")                    # Serial: confirm BT init
handle  = ble_handle.Handle()                      # Callback handle
print("[Game-A] Handle ready")                     # Serial: confirm handle
def connect_peer():                                # Helper: connect to Game-R32
    central.scan()                                 # Scan for peripherals
    time.sleep_ms(600)                             # Short scan wait
    central.connect('Game-R32')                    # Connect by name
    print("[Game-A] Connected to Game-R32")        # Serial: connection OK
def send(text):                                    # Helper: transmit text
    central.send(text)                             # Send message
    print("[Game-A] TX:", text)                    # Serial: log send
def handle_method(msg):                            # Callback: print feedback
    print("[Game-A] RX:", msg)                     # Serial: log incoming
handle.recv(handle_method)                         # Register callback
print("[Game-A] Callback registered")              # Serial: callback active
connect_peer()                                     # Connect to peripheral
send("SERVE?")                                     # Ask to start rally
turn = "A"                                         # Local expected turn state
while True:                                        # Input loop for hits/misses
    if btn_hit.value() == 0:                       # If HIT pressed
        send("HIT:" + turn)                        # Send HIT with current turn
        turn = "B" if turn == "A" else "A"         # Flip expected turn
        time.sleep_ms(250)                         # Debounce
    if btn_miss.value() == 0:                      # If MISS pressed
        send("MISS:" + turn)                       # Send MISS with current turn
        turn = "B" if turn == "A" else "A"         # Flip state
        time.sleep_ms(250)                         # Debounce
    time.sleep_ms(40)                              # Poll delay

Reflection: Quick game messages keep play smooth without overwhelming Bluetooth.
Challenge: Add score tracking and “Best of 7” win logic.


🎮 Microproject 3.12.5 – Creative integrative project

Goal: A flexible template students can expand (control, monitor, play in one).

# Microproject 3.12.5 – Integrative template (Peripheral role)
import ble_peripheral                             # Load Bluetooth peripheral helper
import ble_handle                                 # Load Bluetooth callback helper
import machine                                     # Load hardware pin/ADC library
import i2clcd                                      # Load LCD library (optional)
import time                                        # Load time library
ble_p = ble_peripheral.BLESimplePeripheral('Integrate-R32')  # Peripheral named 'Integrate-R32'
print("[Integrate] BLE 'Integrate-R32' ready")               # Serial: confirm BT init
handle = ble_handle.Handle()                               # Callback handle
print("[Integrate] Handle ready")                          # Serial: confirm handle
# Optional LCD setup (comment out if not used)
i2c = machine.SoftI2C(                                     # Create I2C bus for LCD
    scl = machine.Pin(26),                                  # SCL on Pin 26
    sda = machine.Pin(5),                                   # SDA on Pin 5
    freq = 100000                                           # 100 kHz bus
)                                                           # End I2C creation
lcd = i2clcd.LCD(i2c, 16, 0x27)                             # LCD controller
print("[Integrate] LCD ready")                              # Serial: confirm LCD
adc2 = machine.ADC(machine.Pin(2))                          # Sensor on ADC2 (demo)
adc2.atten(machine.ADC.ATTN_11DB)                           # Full voltage range
adc2.width(machine.ADC.WIDTH_12BIT)                         # 12-bit resolution
print("[Integrate] ADC2 ready")                             # Serial: confirm ADC
led = machine.Pin(13, machine.Pin.OUT)                      # LED feedback on Pin 13
print("[Integrate] LED=13 ready")                           # Serial: confirm LED
state = {"mode": "IDLE", "view": "SENSOR"}                  # Minimal state dict
print("[Integrate] State:", state)                          # Serial: show initial state
def fit16(text):                                            # Helper: fit text to 16 chars
    s = str(text)                                           # Cast to string
    return s[:16] if len(s) > 16 else s.ljust(16)           # Truncate or pad
def show(row, text):                                        # Helper: LCD row render
    lcd.shows(fit16(text),                                  # Fit to 16 characters
              column = 0,                                   # Start at column 0
              line = row,                                   # Target row 0/1
              center = False)                               # Left align for readability
    print("[LCD]", row, ":", fit16(text))                   # Serial: mirror
def send(text):                                             # Helper: Bluetooth send
    ble_p.send(text)                                        # Transmit text message
    print("[Integrate] TX:", text)                          # Serial: log send
def set_mode(m):                                            # Helper: update mode
    state["mode"] = m                                       # Change mode
    send("ACK:MODE=" + m)                                   # Confirm mode change
def set_view(v):                                            # Helper: update view
    state["view"] = v                                       # Change view
    send("ACK:VIEW=" + v)                                   # Confirm view change
def handle_method(msg):                                     # Callback: main router
    s = str(msg)                                            # Ensure string
    print("[Integrate] RX:", s)                             # Serial: log incoming
    if s.startswith("MODE:"):                               # Mode change request
        set_mode(s.split(":")[1])                           # Apply mode
    elif s.startswith("VIEW:"):                             # View change request
        set_view(s.split(":")[1])                           # Apply view
    elif s == "LED:ON":                                     # LED ON command
        led.value(1)                                        # Turn LED ON
        send("ACK:LED=ON")                                  # Confirm
    elif s == "LED:OFF":                                    # LED OFF command
        led.value(0)                                        # Turn LED OFF
        send("ACK:LED=OFF")                                 # Confirm
    else:                                                   # Unknown command
        send("ERR:UNKNOWN")                                 # Error feedback
handle.recv(handle_method)                                  # Register callback
print("[Integrate] Callback registered")                    # Serial: callback active
while True:                                                 # Main loop: render chosen view
    if state["view"] == "SENSOR":                           # If SENSOR view selected
        raw = adc2.read()                                   # Read ADC raw value
        show(0, "ADC2 Sensor")                              # Header
        show(1, "Raw " + str(raw))                          # Raw value
    elif state["view"] == "SYSTEM":                         # If SYSTEM view selected
        ms = time.ticks_ms()                                # Read uptime ms
        show(0, "System Status")                            # Header
        show(1, "Up {:02d}:{:02d}".format((ms // 1000) // 60, (ms // 1000) % 60))  # mm:ss uptime
    else:                                                   # Else CUSTOM/BT view
        show(0, "Integrate-R32")                            # Header
        show(1, "Mode " + state["mode"])                    # Mode line
    time.sleep_ms(850)                                      # Smooth update cadence

Reflection: A safe template invites creativity while keeping structure intact.
Challenge: Add a “COMPOSE” page that shows two short values (e.g., ADC and mode) on one line.


✨ Main project – N1+N2+N3 integration (multi‑board ecosystem)

System outline

  • Controller: Sends IR and BT commands; keeps concise logs.
  • Robot: Decodes IR and BT; applies modes; ready for precise pulses.
  • Station: Displays SENSOR/SYSTEM/BT views; handles source commands.
  • Game: Runs a mini rally; confirms turns and misses.
  • Integration hooks: Shared message patterns (CMD, MODE, SRC, ACK/ERR) keep everything consistent.
# Project 3.12 – Integrated ecosystem (Controller + Robot + Station + Game)
# This outline shows how each board cooperates using small, readable helpers.
# Controller: already implemented in Microproject 3.12.1
# Robot: already implemented in Microproject 3.12.2
# Station: already implemented in Microproject 3.12.3
# Game: already implemented in Microproject 3.12.4
# Tip: Keep all messages short (CMD:..., MODE:..., SRC:...) and confirm with ACK:...
# Avoid spamming: use ~800–1000 ms cadence and minimal serial logs per event.

External explanation

  • What it teaches: How to stitch together multiple Level‑3 skills into a coherent, fault‑tolerant, student‑friendly system.
  • Why it works: Each device has a clear role; helpers prevent repetition; consistent message names avoid confusion; displays keep users informed.
  • Key concept: Integration succeeds when each part is simple and the interfaces between parts are crystal‑clear.

Story time

Four tiny teammates: the Controller calls the plays, the Robot moves with style, the Station narrates the world, and the Game keeps spirits high. Together, they feel like a little campus of robots.


Debugging (2)

Debugging 3.12.A – Memory saturation

  • Symptom: Random slowdowns or resets when many features run.
  • Fix: Reduce print frequency, reuse helpers, and avoid long strings.
# Keep one concise log per event and avoid giant strings
print("[Robot] IR RX:", hex(code))  # Short, informative
# Reuse shared helpers (fit16, apply_mode) to prevent duplication

Debugging 3.12.B – Interference with multiple communications

  • Symptom: Bluetooth feels laggy when IR floods, or LCD flickers during fast updates.
  • Fix: Add small delays, avoid simultaneous sends, and batch UI updates.
# Pace each loop separately (~850–1000 ms for displays, ~800–1000 ms for commands)
time.sleep_ms(850)  # Smooth LCD refresh
# Pick one route per cycle and avoid sending IR+BT at the exact same instant

Final checklist

  • Controller sends IR and BT commands with clean spacing.
  • Robot decodes commands, applies modes, and acknowledges actions.
  • Station displays SENSOR/SYSTEM/BT clearly with 16‑char formatting.
  • Game runs a rally with serve/hit/miss and immediate feedback.
  • Serial logs remain concise; loops use steady cadences.

Extras

  • Student tip: Rename devices (“Robot‑R32”, “Station‑R32”, “Game‑R32”) for easy pairing.
  • Instructor tip: Let teams decide roles; rotate responsibilities to explore each device.
  • Glossary:
    • ACK: A short confirmation message sent back to the sender.
    • SRC: A source selection keyword for display stations.
    • Cadence: The rate of updates — steady is better than fast-and-noisy.
  • Mini tips:
    • Fit LCD text to 16 characters and avoid overlap.
    • Keep message labels consistent across all boards.
    • Celebrate small wins — a clean ACK and a smooth move mean your system is healthy.
On this page