📡 Level 3 – Advanced Communication

Project 3.5: "IR Controlled Car 2.0"

 

🚀 Project 3.5 – IR Car Controlled by Joystick Buttons

🎯 What you’ll learn

  • ✅ Goal 1: Use joystick buttons A–F to send IR commands from a controller board
  • ✅ Goal 2: Receive IR commands on the car board and run direction/speed actions
  • ✅ Goal 3: Build simple modes and a macro using only A–F (no joystick axes)

Key ideas

  • Short definition: Buttons on the joystick send IR codes; the car decodes them and moves.
  • Real‑world link: Remote controllers use buttons to command robots and RC cars.

🧱 Blocks glossary (used in this project)

  • Digital input (pull‑up): Reads button state (pressed = 0).
  • IR send: Transmits an infrared code from the controller board.
  • IR receive: Listens for infrared codes on the car board.
  • Digital output: Sets motor direction pins (ON/OFF).
  • PWM output: Controls motor speed by duty cycle.
  • Variable: Stores mode, speed, and the last code.
  • if / else: Maps a code to an action (direction, speed, macro).
  • Loop: Repeats reading/sending/acting continuously.

🧰 What you need

PartHow many?Pin connection (D1 R32)
D1 R32 (Controller with joystick)1Joystick Shield on top; IR Transmitter → Pin 26
D1 R32 (Car)1IR Receiver → Pin 26
L298N Motor Driver1ENA → Pin 5 (PWM), IN1 → Pin 23, IN2 → Pin 19
  ENB → Pin 18 (PWM), IN3 → Pin 13, IN4 → Pin 21
TT Motors2Wired to L298N outputs
  • Joystick buttons: A(26), B(25), C(17), D(16), E(27), F(14) on the controller board.
  • We’ll map A–F to 6 IR codes (e.g., forward, backward, left, right, speed up, stop).

🔌 Wiring tip: Share GND between boards and L298N. Aim the IR transmitter at the receiver (20–50 cm).
📍 Pin map snapshot: Controller uses joystick buttons (26,25,17,16,27,14) and IR TX on 26 (module). Car uses IR RX on 26, motors on 5,18,23,19,13,21.


✅ Before you start

  • Open two serial monitors: one for the controller board, one for the car board.
  • Test print shows:
print("Ready!")  # Confirm serial is working

🎮 Microprojects (5 mini missions)

🎮 Microproject 3.5.1 – Controller: send IR codes with buttons A–F

Goal: Press A–F to send six distinct IR codes.

Blocks used:

  • Digital input (pull‑up): Read A–F
  • IR send: Transmit code per button
  • Serial print: Log which code was sent

Block sequence:

  1. Setup A–F pins with pull‑up
  2. Setup IR transmitter on Pin 26
  3. On each press, send a unique code
  4. Print action and delay to debounce

MicroPython code (Controller):

# Microproject 3.5.1 – Controller: Send IR codes with joystick buttons A–F

import machine                                # Load hardware/pin library
import irremote                               # Load IR communication library
import time                                   # Load time library for delays

pin26 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # Button A (active LOW)
pin25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP)  # Button B (active LOW)
pin17 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP)  # Button C (active LOW)
pin16 = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)  # Button D (active LOW)
pin27 = machine.Pin(27, machine.Pin.IN, machine.Pin.PULL_UP)  # Button E (active LOW)
pin14 = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_UP)  # Button F (active LOW)

ir_tx = irremote.NEC_TX(26, False, 100)                       # IR transmitter on Pin 26, power 100%
print("[Controller] IR TX ready on 26 | Buttons A–F active")  # Confirm setup

addr = 0x00                                                   # Use simple address 0x00
ctrl = 0x00                                                   # Use simple control 0x00

while True:                                                   # Main sending loop
    if pin26.value() == 0:                                    # If A pressed
        ir_tx.transmit(addr, 0x18, ctrl)                      # Send code 0x18 (forward)
        print("[Send] A → CMD=0x18 (FORWARD)")                # Log action
        time.sleep_ms(250)                                    # Debounce delay
    if pin25.value() == 0:                                    # If B pressed
        ir_tx.transmit(addr, 0x52, ctrl)                      # Send code 0x52 (backward)
        print("[Send] B → CMD=0x52 (BACKWARD)")               # Log action
        time.sleep_ms(250)                                    # Debounce delay
    if pin17.value() == 0:                                    # If C pressed
        ir_tx.transmit(addr, 0x08, ctrl)                      # Send code 0x08 (left)
        print("[Send] C → CMD=0x08 (LEFT)")                   # Log action
        time.sleep_ms(250)                                    # Debounce delay
    if pin16.value() == 0:                                    # If D pressed
        ir_tx.transmit(addr, 0x5A, ctrl)                      # Send code 0x5A (right)
        print("[Send] D → CMD=0x5A (RIGHT)")                  # Log action
        time.sleep_ms(250)                                    # Debounce delay
    if pin27.value() == 0:                                    # If E pressed
        ir_tx.transmit(addr, 0x47, ctrl)                      # Send code 0x47 (speed up)
        print("[Send] E → CMD=0x47 (SPEED +)")                # Log action
        time.sleep_ms(250)                                    # Debounce delay
    if pin14.value() == 0:                                    # If F pressed
        ir_tx.transmit(addr, 0x19, ctrl)                      # Send code 0x19 (stop)
        print("[Send] F → CMD=0x19 (STOP)")                   # Log action
        time.sleep_ms(250)                                    # Debounce delay

Reflection: Your joystick buttons now act like a remote—each press beams a command.
Challenge:

  • Easy: Swap E/F meanings to match your preference.
  • Harder: Add a long‑press (hold > 1 s) to send a different code (e.g., turbo).

🎮 Microproject 3.5.2 – Car: decode A–D as directions

Goal: Map received codes to forward/back/left/right motor actions.

Blocks used:

  • IR receive: Listen for codes
  • Digital output: Set IN1..IN4
  • Serial print: Log direction

Block sequence:

  1. Setup IR receiver on Pin 26
  2. Setup direction pins (23,19,13,21)
  3. Map 0x18/0x52/0x08/0x5A to moves
  4. Stop on unknown

MicroPython code (Car):

# Microproject 3.5.2 – Car: Decode directions from IR

import irremote                               # Load IR communication library
import machine                                # Load hardware pin library
import time                                   # Load time library for delays

ir_rx = irremote.NEC_RX(26, 8)                # IR receiver on Pin 26 with buffer 8
print("[Car] IR RX ready on 26")              # Confirm IR setup

pin23 = machine.Pin(23, machine.Pin.OUT)      # IN1 left motor
pin19 = machine.Pin(19, machine.Pin.OUT)      # IN2 left motor
pin13 = machine.Pin(13, machine.Pin.OUT)      # IN3 right motor
pin21 = machine.Pin(21, machine.Pin.OUT)      # IN4 right motor
print("[Car] Direction pins ready: 23,19,13,21")  # Confirm pins

def stop_all():                               # Helper: stop motors
    pin23.value(0)                             # Left IN1 OFF
    pin19.value(0)                             # Left IN2 OFF
    pin13.value(0)                             # Right IN3 OFF
    pin21.value(0)                             # Right IN4 OFF

while True:                                   # Continuous control loop
    if ir_rx.any():                            # If a code arrived
        code = ir_rx.code[0]                   # Read the first buffered code
        print("[IR] Code:", hex(code))         # Show code in hex
        if code == 0x18:                       # Forward code
            pin23.value(1)                     # Left forward ON
            pin19.value(0)                     # Left backward OFF
            pin13.value(1)                     # Right forward ON
            pin21.value(0)                     # Right backward OFF
            print("[Move] FORWARD")            # Log move
        elif code == 0x52:                     # Backward code
            pin23.value(0)                     # Left forward OFF
            pin19.value(1)                     # Left backward ON
            pin13.value(0)                     # Right forward OFF
            pin21.value(1)                     # Right backward ON
            print("[Move] BACKWARD")           # Log move
        elif code == 0x08:                     # Left code
            pin23.value(0)                     # Left backward ON (spin)
            pin19.value(1)                     # Left backward ON
            pin13.value(1)                     # Right forward ON
            pin21.value(0)                     # Right backward OFF
            print("[Turn] LEFT")               # Log turn
        elif code == 0x5A:                     # Right code
            pin23.value(1)                     # Left forward ON
            pin19.value(0)                     # Left backward OFF
            pin13.value(0)                     # Right backward ON
            pin21.value(1)                     # Right backward ON
            print("[Turn] RIGHT")              # Log turn
        else:                                  # Unknown code
            stop_all()                          # Stop motors
            print("[Move] STOP (unknown)")      # Log stop
    else:                                      # No code this cycle
        print("[Wait] No IR code")              # Waiting message
        stop_all()                               # Keep safe stop
    time.sleep_ms(250)                          # Readability delay

Reflection: The car reacts to your button beams—simple, clean control.
Challenge:

  • Easy: Add “centered stop” after 2 seconds without commands.
  • Harder: Make left/right turns shorter pulses (0.3 s), else stop.

🎮 Microproject 3.5.3 – Car: speed control with E/F

Goal: Use E (speed up) and F (stop) codes to control PWM speed.

Blocks used:

  • IR receive: Read E/F codes
  • PWM output: Adjust duty on ENA/ENB
  • Serial print: Show speed duty

Block sequence:

  1. Setup PWM on Pin 5 and Pin 18
  2. On 0x47 (E) increase duty by step
  3. On 0x19 (F) set duty to 0 and stop
  4. Print speed

MicroPython code (Car):

# Microproject 3.5.3 – Car: Speed control using E (up) and F (stop)

import irremote                               # Load IR communication library
import machine                                # Load hardware/PWM library
import time                                   # Load time library

ir_rx = irremote.NEC_RX(26, 8)                # IR receiver on Pin 26
print("[Car] IR RX ready for speed E/F")      # Confirm IR setup

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)                               # PWM frequency 2000 Hz
pwm18.freq(2000)                              # PWM frequency 2000 Hz
print("[Car] PWM set at 2000 Hz (5,18)")      # Confirm PWM setup

speed = 500                                   # Start duty ~50%
pwm5.duty(speed)                              # Apply left speed
pwm18.duty(speed)                             # Apply right speed
print("[Speed] Start =", speed)               # Log start speed

while True:                                   # Speed control loop
    if ir_rx.any():                            # If a code arrived
        code = ir_rx.code[0]                   # Read first code
        print("[IR] Code:", hex(code))         # Show code
        if code == 0x47:                       # If E (speed up)
            speed = speed + 100                # Increase duty by 100
            if speed > 1023:                   # If above max
                speed = 1023                   # Clamp to max
            print("[Speed] UP =", speed)       # Log new speed
        elif code == 0x19:                     # If F (stop)
            speed = 0                          # Duty to 0
            print("[Speed] STOP =", speed)     # Log stop
        pwm5.duty(speed)                       # Update left speed
        pwm18.duty(speed)                      # Update right speed
    else:                                      # No code now
        print("[Wait] No IR code (speed)")     # Waiting message
    time.sleep_ms(250)                         # Small delay

Reflection: Two buttons can manage the car’s energy—fast or full stop.
Challenge:

  • Easy: Add a minimum speed (don’t go below 300).
  • Harder: Use C/D to fine‑tune speed up/down by 50.

🎮 Microproject 3.5.4 – Car: pre‑programmed movement (macro from A)

Goal: When 0x18 (A) arrives, run a short routine: forward→right→stop.

Blocks used:

  • IR receive: Detect trigger code
  • Digital/PWM: Execute timed sequence
  • Serial print: Narrate steps

Block sequence:

  1. On 0x18, forward for 0.8 s
  2. Turn right for 0.5 s
  3. Stop and print “Macro done”

MicroPython code (Car):

# Microproject 3.5.4 – Car: Macro triggered by A (0x18)

import irremote                               # Load IR communication library
import machine                                # Load hardware pin/PWM library
import time                                   # Load time library

ir_rx = irremote.NEC_RX(26, 8)                # IR receiver on Pin 26
print("[Car] Macro trigger on A(0x18)")       # Confirm macro mode

pin23 = machine.Pin(23, machine.Pin.OUT)      # IN1 left
pin19 = machine.Pin(19, machine.Pin.OUT)      # IN2 left
pin13 = machine.Pin(13, machine.Pin.OUT)      # IN3 right
pin21 = machine.Pin(21, machine.Pin.OUT)      # IN4 right

def forward():                                 # Forward helper
    pin23.value(1)                              # Left forward ON
    pin19.value(0)                              # Left backward OFF
    pin13.value(1)                              # Right forward ON
    pin21.value(0)                              # Right backward OFF

def right():                                   # Right helper
    pin23.value(1)                              # Left forward ON
    pin19.value(0)                              # Left backward OFF
    pin13.value(0)                              # Right backward ON
    pin21.value(1)                              # Right backward ON

def stop_all():                                # Stop helper
    pin23.value(0)                              # Left forward OFF
    pin19.value(0)                              # Left backward OFF
    pin13.value(0)                              # Right forward OFF
    pin21.value(0)                              # Right backward OFF

while True:                                    # Macro loop
    if ir_rx.any():                             # If a code arrived
        code = ir_rx.code[0]                    # Read the code
        print("[IR] Code:", hex(code))          # Show hex
        if code == 0x18:                        # If A (forward trigger)
            print("[Macro] Start")              # Begin macro
            forward()                           # Step 1 forward
            time.sleep_ms(800)                  # Run 0.8 s
            right()                             # Step 2 right
            time.sleep_ms(500)                  # Run 0.5 s
            stop_all()                          # Step 3 stop
            print("[Macro] Done")               # End macro
    else:                                       # If no code
        print("[Wait] No IR code (macro)")      # Waiting message
    time.sleep_ms(250)                          # Small delay

Reflection: One press runs a move combo—handy for teaching patterns.
Challenge:

  • Easy: Trigger another macro with B (0x52).
  • Harder: Chain two macros: A then C within 2 seconds.

🎮 Microproject 3.5.5 – Car: driver modes with B/E/F

Goal: Switch modes: Normal (B), Boost (E), Stop mode (F).

Blocks used:

  • IR receive: Detect mode codes
  • PWM output: Set duty caps per mode
  • Serial print: Announce mode

Block sequence:

  1. On B (0x52) → Normal speed (650)
  2. On E (0x47) → Boost speed (900)
  3. On F (0x19) → Stop (0)

MicroPython code (Car):

# Microproject 3.5.5 – Car: Driver modes with B/E/F

import irremote                               # Load IR communication library
import machine                                # Load hardware/PWM library
import time                                   # Load time library

ir_rx = irremote.NEC_RX(26, 8)                # IR receiver on Pin 26
print("[Car] Modes: B=Normal, E=Boost, F=Stop")  # Mode info

pwm5 = machine.PWM(machine.Pin(5))            # PWM enable A
pwm18 = machine.PWM(machine.Pin(18))          # PWM enable B
pwm5.freq(2000)                               # 2000 Hz PWM
pwm18.freq(2000)                              # 2000 Hz PWM

mode = "NORMAL"                               # Start mode
speed = 650                                   # Start speed
pwm5.duty(speed)                              # Apply left duty
pwm18.duty(speed)                             # Apply right duty
print("[Mode] NORMAL | speed =", speed)       # Log start state

while True:                                   # Mode control loop
    if ir_rx.any():                            # If a code arrived
        code = ir_rx.code[0]                   # Read code
        print("[IR] Code:", hex(code))         # Show hex code
        if code == 0x52:                       # B → Normal
            mode = "NORMAL"                    # Set Normal
            speed = 650                        # Duty medium
            print("[Mode] NORMAL | speed =", speed)  # Log
        elif code == 0x47:                     # E → Boost
            mode = "BOOST"                     # Set Boost
            speed = 900                        # Duty high
            print("[Mode] BOOST | speed =", speed)   # Log
        elif code == 0x19:                     # F → Stop
            mode = "STOP"                      # Set Stop
            speed = 0                          # Duty 0
            print("[Mode] STOP | speed =", speed)    # Log
        pwm5.duty(speed)                       # Update left duty
        pwm18.duty(speed)                      # Update right duty
    else:                                      # No code this cycle
        print("[Wait] No IR code (mode)")      # Waiting message
    time.sleep_ms(300)                         # Small delay

Reflection: Modes change the car’s personality—calm, fast, or halted.
Challenge:

  • Easy: Add C (0x08) for “Slow” mode (400).
  • Harder: Display mode on LCD if attached (Project 3.3 blocks).

✨ Main project – Joystick buttons controlling an IR car

🔧 Blocks steps (with glossary)

  • Digital input (pull‑up): Read A–F on the controller
  • IR send: Transmit code per button
  • IR receive: Decode codes on the car
  • Digital output: Apply direction pins
  • PWM output: Adjust speed and modes
  • Serial print: Log sends and actions

Block sequence:

  1. Controller: Setup A–F (pull‑up) and IR TX on 26 → send codes 0x18,0x52,0x08,0x5A,0x47,0x19.
  2. Car: Setup IR RX on 26, direction pins (23,19,13,21), PWM (5,18).
  3. Car: Map codes to actions (forward/back/left/right, speed up, stop).
  4. Car: Provide modes and macro based on codes.
  5. Keep clear serial messages at every step.

🐍 MicroPython code (mirroring blocks)

# Main Project 3.5 – Joystick Buttons → IR → Car Control

# --- Controller Board (send A–F as IR) ---
import machine                                # Load hardware/pin library
import irremote                               # Load IR communication library
import time                                   # Load time library

pin26 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # A button input
pin25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP)  # B button input
pin17 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP)  # C button input
pin16 = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)  # D button input
pin27 = machine.Pin(27, machine.Pin.IN, machine.Pin.PULL_UP)  # E button input
pin14 = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_UP)  # F button input

ir_tx = irremote.NEC_TX(26, False, 100)                       # IR TX on pin 26
print("[Controller] Ready A–F → IR")                          # Controller init log

addr = 0x00                                                   # Address code
ctrl = 0x00                                                   # Control code

# Send once example (press to transmit)
if pin26.value() == 0:                                        # If A pressed
    ir_tx.transmit(addr, 0x18, ctrl)                          # Send forward code
    print("[Controller] A → 0x18")                            # Log send

# --- Car Board (decode IR and drive) ---
ir_rx = irremote.NEC_RX(26, 8)                                # IR RX on pin 26
print("[Car] IR RX ready")                                    # Car init log

pin23 = machine.Pin(23, machine.Pin.OUT)                      # IN1 left
pin19 = machine.Pin(19, machine.Pin.OUT)                      # IN2 left
pin13 = machine.Pin(13, machine.Pin.OUT)                      # IN3 right
pin21 = machine.Pin(21, machine.Pin.OUT)                      # IN4 right
print("[Car] Direction pins set")                             # Pins ready log

pwm5 = machine.PWM(machine.Pin(5))                            # ENA left PWM
pwm18 = machine.PWM(machine.Pin(18))                          # ENB right PWM
pwm5.freq(2000)                                               # PWM freq set
pwm18.freq(2000)                                              # PWM freq set
print("[Car] PWM 2000 Hz set")                                # PWM init log

speed = 650                                                   # Start speed
pwm5.duty(speed)                                              # Apply left speed
pwm18.duty(speed)                                             # Apply right speed
print("[Car] Speed =", speed)                                 # Speed log

def stop_all():                                               # Helper stop
    pin23.value(0)                                            # Left IN1 OFF
    pin19.value(0)                                            # Left IN2 OFF
    pin13.value(0)                                            # Right IN3 OFF
    pin21.value(0)                                            # Right IN4 OFF

if ir_rx.any():                                               # If any IR code
    code = ir_rx.code[0]                                      # Read first code
    print("[Car] Code:", hex(code))                           # Show code
    if code == 0x18:                                          # Forward
        pin23.value(1)                                        # Left forward ON
        pin19.value(0)                                        # Left backward OFF
        pin13.value(1)                                        # Right forward ON
        pin21.value(0)                                        # Right backward OFF
        print("[Car] FORWARD")                                # Log move
    elif code == 0x52:                                        # Backward
        pin23.value(0)                                        # Left forward OFF
        pin19.value(1)                                        # Left backward ON
        pin13.value(0)                                        # Right forward OFF
        pin21.value(1)                                        # Right backward ON
        print("[Car] BACKWARD")                               # Log move
    elif code == 0x08:                                        # Left
        pin23.value(0)                                        # Left backward ON
        pin19.value(1)                                        # Left backward ON
        pin13.value(1)                                        # Right forward ON
        pin21.value(0)                                        # Right backward OFF
        print("[Car] LEFT")                                   # Log turn
    elif code == 0x5A:                                        # Right
        pin23.value(1)                                        # Left forward ON
        pin19.value(0)                                        # Left backward OFF
        pin13.value(0)                                        # Right backward ON
        pin21.value(1)                                        # Right backward ON
        print("[Car] RIGHT")                                  # Log turn
    elif code == 0x47:                                        # Speed up
        speed = speed + 100                                   # Increase duty
        if speed > 1023:                                      # Clamp max
            speed = 1023                                      # Max duty
        pwm5.duty(speed)                                      # Update left
        pwm18.duty(speed)                                     # Update right
        print("[Car] SPEED UP →", speed)                      # Log speed
    elif code == 0x19:                                        # Stop
        speed = 0                                             # Duty zero
        pwm5.duty(speed)                                      # Update left
        pwm18.duty(speed)                                     # Update right
        stop_all()                                            # Stop motors
        print("[Car] STOP")                                   # Log stop
    else:                                                     # Unknown
        stop_all()                                            # Safe stop
        print("[Car] UNKNOWN")                                # Log unknown

📖 External explanation

  • What it teaches: Turning button presses into wireless commands, and decoding them for motion and speed.
  • Why it works: Buttons (pull‑up) give clean digital events; IR carries small hex codes; the car maps codes to actions.
  • Key concept: Clear code‑to‑action mapping makes reliable remote control.

✨ Story time

Your joystick becomes a beacon: A–F are signals, the car interprets and moves. It’s like piloting a rover with a six‑button mission pad.


🕵️ Debugging (2)

🐞 Debugging 3.5.A – Lost commands between boards

Problem: Some presses don’t trigger the car.
Clues: Controller logs “Send” but car shows “No IR code”.

Broken code:

ir_tx.transmit(addr, 0x18, ctrl)  # Send without debounce or spacing

Fixed code:

ir_tx.transmit(addr, 0x18, ctrl)  # Send command
time.sleep_ms(250)                # Add spacing to avoid overlaps

Why it works: Short spacing gives the receiver time to catch the signal.
Avoid next time: Debounce button presses and keep delays small but present.


🐞 Debugging 3.5.B – Slow response on the car

Problem: The car reacts late or stutters.
Clues: Very long delays or excessive printing.

Broken code:

time.sleep_ms(1200)      # Too long loop delay
print("Spam spam spam")  # Too many logs per cycle

Fixed code:

time.sleep_ms(250)       # Keep delays short (150–300 ms)
print("[Move] FORWARD")  # One concise status line

Why it works: Short delays and focused logs keep the control loop responsive.
Avoid next time: Avoid long sleeps and repeated verbose prints.


✅ Final checklist

  • Controller sends IR on A–F with clear logs
  • Car decodes directions (A–D) correctly
  • Car changes speed with E and stops with F
  • Macro runs when A triggers it (if implemented)
  • Serial messages are readable and helpful

📚 Extras

  • 🧠 Student tip: Point the controller directly at the car; keep a steady arm for reliable sends.
  • 🧑‍🏫 Instructor tip: Test each A–F mapping in place before driving; verify GND is shared.
  • 📖 Glossary:
    • Pull‑up: Keeps an input HIGH until a button press pulls it LOW.
    • IR code: A small hex value sent by light to communicate commands.
    • Duty (PWM): Percentage of time power is ON—controls motor speed.
  • 💡 Mini tips:
    • Use short, distinct presses; avoid holding multiple buttons.
    • Keep IR modules aligned and at a stable distance.
    • Start at medium speed; increase once direction works.
On this page