📡 Level 3 – Advanced Communication

Project 3.9: "Complete Display Station"

 

What you’ll learn

  • Goal 1: Display multiple data sources (sensor, system, Bluetooth messages) on a 16×2 LCD.
  • Goal 2: Select the active data source via Bluetooth commands and confirm selection visibly.
  • Goal 3: Format values neatly (units, padding, truncation) for readable 16‑character lines.
  • Goal 4: Add visual alerts (blink, inverted text) when thresholds or errors occur.
  • Goal 5: Build a complete, responsive UI: status line, active source, and periodic updates.

Key ideas

  • Modular display: Use helper functions to render lines, pages, and status.
  • Source control: Bluetooth commands switch what the LCD shows.
  • Friendly formatting: Trim, pad, and label values to fit the LCD.
  • Visual cues: Blink or mark alerts without overwhelming the display.

Blocks glossary

  • Bluetooth peripheral: Advertises a name and receives/sends text.
  • Callback (receive): Function that runs automatically when a Bluetooth message arrives.
  • I2C setup: Initializes the LCD bus (SCL/SDA).
  • LCD print: Writes text at a given row and column.
  • Analog input (ADC): Reads sensor values (e.g., joystick X on Pin 2) for telemetry.
  • Serial print: Logs internal events for debugging.
  • def function: Encapsulates reusable actions (formatting, show lines, handle messages).
  • Loop: Repeats UI updates and checks for alerts at a steady cadence.

What you need

PartHow many?Pin connection (R32)
D1 R321USB cable
1602 LCD (I2C)1SCL → Pin 26, SDA → Pin 5, VCC 5V, GND
Built‑in Bluetooth1Internal (no pins)
Optional sensor (ADC)1Joystick X → Pin 2
  • Share GND with the LCD.
  • Keep the board 1–3 m from the PC for reliable Bluetooth.

Before you start

  • Plug in USB and open the serial monitor.
  • Quick test:
print("Ready!")  # Confirm serial is working

🎮 Microprojects (5 mini missions)

🎮 Microproject 3.9.1 – Multiple data displays

Goal: Show sensor, system, and last Bluetooth message on different LCD pages.

# Microproject 3.9.1 – Multiple data displays (pages: SENSOR, SYSTEM, BT)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware libraries
import i2clcd                                        # Load LCD library (1602 I2C)
import time                                          # Load time for delays

ble_p = ble_peripheral.BLESimplePeripheral('Display-R32')  # Create Bluetooth peripheral named 'Display-R32'
print("[Init] BLE 'Display-R32'")                           # Serial: confirm Bluetooth init

handle = ble_handle.Handle()                               # Create callback handle for RX
print("[Init] 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 frequency at 100 kHz
)                                                           # End I2C creation

lcd = i2clcd.LCD(                                          # Create LCD controller
    i2c,                                                    # Pass I2C bus
    lcd_width = 16,                                         # LCD width is 16 characters
    i2c_addr = 0x27                                         # Typical I2C backpack address
)                                                           # End LCD creation

adc2 = machine.ADC(machine.Pin(2))                          # Initialize ADC on Pin 2 (sensor demo)
adc2.atten(machine.ADC.ATTN_11DB)                           # Full voltage range 0–3.3 V
adc2.width(machine.ADC.WIDTH_12BIT)                         # 12-bit resolution (0–4095)
print("[Init] ADC Pin 2 ready")                             # Serial: ADC ready

last_bt = ""                                                # Store last received Bluetooth text
page = "SENSOR"                                             # Start on SENSOR page
print("[State] Page =", page)                               # Serial: show active page

def show(row, text):                                        # Helper: print centered on LCD row
    lcd.shows(str(text),                                    # Render given text
              column = 0,                                   # Start at column 0
              line = row,                                   # Target row (0 or 1)
              center = True)                                # Center for readability
    print("[LCD]", row, ":", text)                          # Serial: mirror output

def handle_method(msg):                                     # Callback: store last BT message
    global last_bt                                          # Use global to retain message
    last_bt = str(msg)                                      # Save as string
    print("[BT] RX:", last_bt)                              # Serial: show received text

handle.recv(handle_method)                                  # Register BT receive callback
print("[BT] Callback registered")                           # Serial: callback active

while True:                                                 # UI loop: cycle pages
    if page == "SENSOR":                                    # If SENSOR page active
        val = adc2.read()                                   # Read sensor value (0–4095)
        show(0, "Sensor ADC2")                              # Title line
        show(1, "Val=" + str(val))                          # Value line
        page = "SYSTEM"                                     # Next page to show
    elif page == "SYSTEM":                                  # If SYSTEM page active
        up = time.ticks_ms()                                # Read uptime in ms
        show(0, "System Status")                            # Title line
        show(1, "Up " + str(up) + "ms")                     # Show uptime
        page = "BT"                                         # Next page to show
    else:                                                   # Else BT page
        show(0, "Last BT Msg")                              # Title line
        show(1, last_bt if last_bt else "(none)")           # Show last message or placeholder
        page = "SENSOR"                                     # Next page cycles back
    time.sleep_ms(900)                                      # Delay to pace updates

Reflection: You created a simple multi‑page display that cycles through source views.
Challenge: Increase cycle rate for SENSOR and decrease for BT to prioritize live data.


🎮 Microproject 3.9.2 – Data source selection

Goal: Change the active page via Bluetooth (SRC:SENSOR, SRC:SYSTEM, SRC:BT).

# Microproject 3.9.2 – Select data source (page) via Bluetooth commands

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware libraries
import i2clcd                                        # Load LCD library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Display-R32')  # Bluetooth peripheral with name
handle = ble_handle.Handle()                                 # Callback handle
print("[Init] BLE + handle ready")                           # Serial: confirm init

i2c = machine.SoftI2C(                                     # Create I2C bus
    scl = machine.Pin(26),                                  # SCL pin
    sda = machine.Pin(5),                                   # SDA pin
    freq = 100000                                           # I2C speed
)                                                           # End bus creation

lcd = i2clcd.LCD(i2c, 16, 0x27)                             # LCD controller (width=16, addr=0x27)
print("[Init] LCD ready")                                    # Serial: confirm LCD

adc2 = machine.ADC(machine.Pin(2))                          # ADC sensor on Pin 2
adc2.atten(machine.ADC.ATTN_11DB)                           # Voltage range
adc2.width(machine.ADC.WIDTH_12BIT)                         # Resolution
print("[Init] ADC ready")                                    # Serial: confirm ADC

page = "SENSOR"                                             # Default page
print("[State] Page =", page)                                # Serial: show default

def show(row, text):                                        # Helper: display a row centered
    lcd.shows(str(text),                                    # Text to show
              column = 0,                                   # Column 0
              line = row,                                   # Target row
              center = True)                                # Center horizontally
    print("[LCD]", row, ":", text)                          # Serial: mirror

def handle_method(msg):                                     # Callback: process SRC commands
    global page                                             # Use global page state
    s = str(msg)                                            # Ensure string
    print("[BT] RX:", s)                                    # Serial: log received text
    if s == "SRC:SENSOR":                                   # If request SENSOR page
        page = "SENSOR"                                     # Set page to SENSOR
        ble_p.send("ACK:SRC=SENSOR")                        # Send ACK
    elif s == "SRC:SYSTEM":                                 # If request SYSTEM page
        page = "SYSTEM"                                     # Set page to SYSTEM
        ble_p.send("ACK:SRC=SYSTEM")                        # Send ACK
    elif s == "SRC:BT":                                     # If request BT page
        page = "BT"                                         # Set page to BT
        ble_p.send("ACK:SRC=BT")                            # Send ACK
    else:                                                   # Unknown command
        ble_p.send("ERR:UNKNOWN_SRC")                       # Send error feedback

handle.recv(handle_method)                                  # Register callback
print("[BT] Callback registered")                           # Serial: active

while True:                                                 # UI loop (selected page only)
    if page == "SENSOR":                                    # If SENSOR page
        val = adc2.read()                                   # Read sensor value
        show(0, "Sensor ADC2")                              # Title
        show(1, "Val=" + str(val))                          # Value
    elif page == "SYSTEM":                                  # If SYSTEM page
        up = time.ticks_ms()                                # Uptime ms
        show(0, "System Status")                            # Title
        show(1, "Up " + str(up) + "ms")                     # Value
    else:                                                   # Else BT page
        show(0, "Last BT Msg")                              # Title (value updated elsewhere)
        show(1, "(Waiting...)")                             # Placeholder
    time.sleep_ms(800)                                      # UI cadence

Reflection: A simple Bluetooth message now controls the UI — students feel in charge.
Challenge: Add “SRC:CYCLE” to auto‑rotate pages every 1 s.


🎮 Microproject 3.9.3 – Advanced data formatting

Goal: Neatly format 16‑char lines with units, padding, truncation, and short timestamps.

# Microproject 3.9.3 – Advanced formatting (16-char friendly strings)

import machine                                       # Load hardware library
import i2clcd                                        # Load LCD library
import time                                          # Load time for timestamps

i2c = machine.SoftI2C(                              # Create I2C bus
    scl = machine.Pin(26),                           # SCL pin
    sda = machine.Pin(5),                            # SDA pin
    freq = 100000                                    # I2C frequency
)                                                    # End I2C

lcd = i2clcd.LCD(i2c, 16, 0x27)                      # LCD controller
print("[Init] LCD ready")                             # Serial: LCD init

def fit16(text):                                     # Helper: ensure <=16 chars
    s = str(text)                                    # Cast to string
    return s[:16] if len(s) > 16 else s.ljust(16)    # Truncate or pad to 16

def fmt_adc(raw):                                    # Helper: ADC to 'ADC=xxxx (V)'
    volts = (raw * 3.3) / 4095                       # Convert to volts
    s = "ADC=" + str(raw) + " " + "{:.2f}V".format(volts)  # Compose label + units
    return fit16(s)                                   # Fit to 16 chars

def fmt_up(ms):                                      # Helper: uptime mm:ss
    secs = ms // 1000                                 # Convert ms to seconds
    mm = secs // 60                                   # Minutes
    ss = secs % 60                                    # Seconds remainder
    s = "Up " + "{:02d}:{:02d}".format(mm, ss)        # Compose mm:ss string
    return fit16(s)                                   # Fit to 16 chars

def show(row, text):                                  # Helper: show formatted row
    lcd.shows(fit16(text),                            # Show 16-char text
              column = 0,                              # Column 0
              line = row,                              # Target row
              center = False)                          # No center to keep alignment
    print("[LCD]", row, ":", fit16(text))             # Serial: log display

adc2 = machine.ADC(machine.Pin(2))                    # ADC sensor demo
adc2.atten(machine.ADC.ATTN_11DB)                     # Full range
adc2.width(machine.ADC.WIDTH_12BIT)                   # Resolution
print("[Init] ADC ready")                              # Serial: ADC init

while True:                                           # Demo loop
    raw = adc2.read()                                  # Read ADC raw value
    show(0, fmt_adc(raw))                              # Line 0: formatted ADC
    show(1, fmt_up(time.ticks_ms()))                   # Line 1: mm:ss uptime
    time.sleep_ms(900)                                 # Cadence

Reflection: Clean formatting makes every line readable and trustworthy.
Challenge: Create fmt_bt(msg) that strips “CMD:” or “ACK:” prefixes and keeps the core.


🎮 Microproject 3.9.4 – Visual alerts

Goal: Blink or mark “ALERT” when thresholds are crossed or errors are received.

# Microproject 3.9.4 – Visual alerts (threshold + error)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware libraries
import i2clcd                                        # Load LCD library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Display-R32')  # BLE peripheral name
handle = ble_handle.Handle()                                 # Callback handle
print("[Init] BLE ready")                                     # Serial: Bluetooth init

i2c = machine.SoftI2C(                                     # Create I2C bus
    scl = machine.Pin(26),                                  # SCL pin
    sda = machine.Pin(5),                                   # SDA pin
    freq = 100000                                           # I2C speed
)                                                           # End I2C

lcd = i2clcd.LCD(i2c, 16, 0x27)                             # LCD controller
print("[Init] LCD ready")                                    # Serial: LCD init

adc2 = machine.ADC(machine.Pin(2))                          # ADC sensor
adc2.atten(machine.ADC.ATTN_11DB)                           # Range
adc2.width(machine.ADC.WIDTH_12BIT)                         # Resolution
print("[Init] ADC ready")                                    # Serial: ADC init

alert = False                                               # Alert state flag
last_err = ""                                               # Store last error text

def show(row, text):                                        # Helper: display text
    lcd.shows(str(text),                                    # Text
              column = 0,                                   # Column 0
              line = row,                                   # Row selection
              center = True)                                # Center for visibility
    print("[LCD]", row, ":", text)                          # Serial: mirror

def blink_alert(times):                                     # Helper: blink ALERT
    for _ in range(times):                                  # Repeat blink cycle
        show(0, "ALERT!")                                   # Show alert title
        show(1, last_err if last_err else "Threshold")      # Show cause
        time.sleep_ms(180)                                  # Hold ON
        lcd.clear()                                         # Clear quickly
        time.sleep_ms(140)                                  # OFF period

def handle_method(msg):                                     # Callback: detect errors
    global alert, last_err                                  # Use global alert state
    s = str(msg)                                            # Ensure string
    print("[BT] RX:", s)                                    # Serial: log RX
    if s.startswith("ERR:"):                                # If error prefix
        last_err = s                                        # Save last error
        alert = True                                        # Raise alert
        ble_p.send("ACK:ERR")                               # Confirm receipt
    else:                                                   # Non-error feedback
        show(1, s)                                          # Show message on row 1

handle.recv(handle_method)                                  # Register callback
print("[BT] Callback registered")                           # Serial: active

while True:                                                 # Monitoring loop
    val = adc2.read()                                       # Read sensor
    if val > 3500:                                          # If above threshold
        alert = True                                        # Raise alert
        last_err = "ADC>3500"                               # Context for alert
    if alert:                                               # If alert active
        blink_alert(2)                                      # Blink alert twice
        alert = False                                       # Reset alert flag
        lcd.clear()                                         # Clear LCD after blinking
    else:                                                   # If no alert
        show(0, "ADC2=" + str(val))                         # Show current value
        time.sleep_ms(800)                                  # Cadence

Reflection: Visual alerts communicate important states without overwhelming the screen.
Challenge: Add a “quiet alert” mode that marks alerts with a “!” instead of blinking.


🎮 Microproject 3.9.5 – Complete user interface

Goal: Combine status, active page, formatted data, alerts, and Bluetooth‑controlled source switching.

# Microproject 3.9.5 – Complete UI (status + page + format + alerts + BT source)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware libraries
import i2clcd                                        # Load LCD library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Display-R32')  # BLE peripheral name
handle = ble_handle.Handle()                                 # Callback handle
print("[Init] BLE ready")                                     # Serial: Bluetooth init

i2c = machine.SoftI2C(                                     # Create I2C bus
    scl = machine.Pin(26),                                  # SCL pin
    sda = machine.Pin(5),                                   # SDA pin
    freq = 100000                                           # I2C speed
)                                                           # End I2C

lcd = i2clcd.LCD(i2c, 16, 0x27)                             # LCD controller
print("[Init] LCD ready")                                    # Serial: LCD init

adc2 = machine.ADC(machine.Pin(2))                          # ADC sensor
adc2.atten(machine.ADC.ATTN_11DB)                           # Range
adc2.width(machine.ADC.WIDTH_12BIT)                         # Resolution
print("[Init] ADC ready")                                    # Serial: ADC init

page = "SENSOR"                                             # Active page state
alert = False                                               # Alert flag
last_rx = ""                                                # Last BT RX text
cycle = False                                               # Auto-cycle toggle
print("[State] page=SENSOR cycle=False")                    # Serial: 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/pad

def show(row, text):                                        # Helper: display aligned text
    lcd.shows(fit16(text),                                  # Fit to 16 chars
              column = 0,                                   # Column 0
              line = row,                                   # Target row
              center = False)                               # Keep left alignment
    print("[LCD]", row, ":", fit16(text))                   # Serial: mirror

def fmt_adc(raw):                                           # Helper: ADC label + volts
    v = (raw * 3.3) / 4095                                  # Convert raw to volts
    return fit16("ADC=" + str(raw) + " " + "{:.2f}V".format(v))  # Compose string

def fmt_up(ms):                                             # Helper: mm:ss uptime
    secs = ms // 1000                                       # Convert ms to seconds
    return fit16("Up {:02d}:{:02d}".format(secs // 60, secs % 60))  # mm:ss

def blink_alert_once(reason):                               # Helper: single blink alert
    show(0, "ALERT!")                                       # Show alert header
    show(1, reason)                                         # Show alert reason
    time.sleep_ms(160)                                      # Blink ON duration
    lcd.clear()                                             # Clear LCD
    time.sleep_ms(140)                                      # Blink OFF duration

def handle_method(msg):                                     # Callback: BT command parsing
    global page, cycle, alert, last_rx                      # Use global state
    s = str(msg)                                            # Ensure string
    print("[BT] RX:", s)                                    # Serial: show RX
    last_rx = s                                             # Save last RX
    if s == "SRC:SENSOR":                                   # If source SENSOR
        page = "SENSOR"                                     # Set page
        cycle = False                                       # Disable auto cycle
        ble_p.send("ACK:SRC=SENSOR")                        # Send ACK
    elif s == "SRC:SYSTEM":                                 # If source SYSTEM
        page = "SYSTEM"                                     # Set page
        cycle = False                                       # Disable auto cycle
        ble_p.send("ACK:SRC=SYSTEM")                        # Send ACK
    elif s == "SRC:BT":                                     # If source BT
        page = "BT"                                         # Set page
        cycle = False                                       # Disable auto cycle
        ble_p.send("ACK:SRC=BT")                            # Send ACK
    elif s == "SRC:CYCLE":                                  # If request auto cycle
        cycle = True                                        # Enable auto cycle
        ble_p.send("ACK:SRC=CYCLE")                         # Send ACK
    elif s.startswith("ERR:"):                              # If an error message
        alert = True                                        # Raise alert
        ble_p.send("ACK:ERR")                               # Confirm receipt
    else:                                                   # Unknown command
        ble_p.send("ERR:UNKNOWN")                           # Send error feedback

handle.recv(handle_method)                                  # Register RX callback
print("[BT] Callback registered")                           # Serial: active

while True:                                                 # Main UI loop
    if alert:                                               # If alert pending
        blink_alert_once("BT/Error")                        # Blink once with reason
        alert = False                                       # Reset alert
    if cycle:                                               # If auto cycle is ON
        page = "SENSOR" if page == "BT" else ("SYSTEM" if page == "SENSOR" else "BT")  # Rotate pages
    if page == "SENSOR":                                    # SENSOR page view
        raw = adc2.read()                                   # Read sensor value
        show(0, "Sensor ADC2")                              # Header
        show(1, fmt_adc(raw))                               # Formatted value
    elif page == "SYSTEM":                                  # SYSTEM page view
        show(0, "System Status")                            # Header
        show(1, fmt_up(time.ticks_ms()))                    # Uptime mm:ss
    else:                                                   # BT page view
        show(0, "Last BT Msg")                              # Header
        show(1, last_rx if last_rx else "(none)")           # Last message or placeholder
    time.sleep_ms(850)                                      # Update cadence

Reflection: Your station now feels complete: selectable sources, neat formatting, and alerts.
Challenge: Add a small heartbeat “•” at the end of line 1 that toggles each update.


✨ Main project – Complete display station (LCD + Serial + Bluetooth)

  • Bluetooth peripheral: Accept SRC commands, send ACK/ERR.
  • I2C LCD: Render formatted lines for SENSOR, SYSTEM, and BT pages.
  • Serial logs: Mirror actions for easy debugging.
  • UI helpers: Fit to 16 chars, format ADC and uptime, blink alert.
  • Loop: Auto‑cycle or fixed page with steady cadence and low overhead.
# Project 3.9 – Complete Display Station (LCD + Serial + Bluetooth)
# A single R32 board that displays SENSOR, SYSTEM, and BT views with formatting and alerts.

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware libraries (I2C/ADC)
import i2clcd                                        # Load LCD library (1602 I2C)
import time                                          # Load time for delays

ble_p = ble_peripheral.BLESimplePeripheral('Display-R32')  # Create BLE peripheral named 'Display-R32'
print("[Init] BLE 'Display-R32'")                             # Serial: Bluetooth init

handle = ble_handle.Handle()                                 # Create RX callback handle
print("[Init] Handle ready")                                  # Serial: handle init

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 frequency
)                                                             # End I2C creation

lcd = i2clcd.LCD(i2c, 16, 0x27)                               # Create LCD controller (width=16, addr=0x27)
print("[Init] LCD ready")                                      # Serial: LCD init

adc2 = machine.ADC(machine.Pin(2))                            # ADC sensor demo (Pin 2)
adc2.atten(machine.ADC.ATTN_11DB)                             # Voltage range 0–3.3 V
adc2.width(machine.ADC.WIDTH_12BIT)                           # 12-bit resolution
print("[Init] ADC ready")                                      # Serial: ADC init

page = "SENSOR"                                               # Active page state
cycle = False                                                 # Auto-cycle flag
alert = False                                                 # Alert flag
last_rx = ""                                                  # Last Bluetooth RX text
print("[State] page=SENSOR, cycle=False")                      # Serial: initial state

def fit16(text):                                              # Helper: fit text to 16 characters
    s = str(text)                                             # Cast to string type
    return s[:16] if len(s) > 16 else s.ljust(16)             # Truncate or pad to exact length

def show(row, text, center=False):                            # Helper: render one LCD row
    lcd.shows(fit16(text),                                    # Render fitted text
              column = 0,                                     # Start at column 0
              line = row,                                     # Choose row (0/1)
              center = center)                                # Center if requested
    print("[LCD]", row, ":", fit16(text))                     # Serial: mirror display

def fmt_adc(raw):                                             # Helper: format ADC with volts
    v = (raw * 3.3) / 4095                                    # Convert raw ADC to volts
    return fit16("ADC=" + str(raw) + " " + "{:.2f}V".format(v))  # Compose compact label

def fmt_up(ms):                                               # Helper: format uptime mm:ss
    secs = ms // 1000                                         # Convert ms to seconds
    return fit16("Up {:02d}:{:02d}".format(secs // 60, secs % 60))  # Human-readable uptime

def blink_alert_once(reason):                                 # Helper: single blink alert
    show(0, "ALERT!", center=True)                            # Show alert header centered
    show(1, reason, center=True)                              # Show reason centered
    time.sleep_ms(160)                                        # Hold ON briefly
    lcd.clear()                                               # Clear LCD for blink effect
    time.sleep_ms(140)                                        # Short OFF period

def handle_method(msg):                                       # Callback: process Bluetooth commands
    global page, cycle, alert, last_rx                        # Use global UI state
    s = str(msg)                                              # Ensure string
    print("[BT] RX:", s)                                      # Serial: log received text
    last_rx = s                                               # Update last seen RX
    if s == "SRC:SENSOR":                                     # If request SENSOR page
        page = "SENSOR"                                       # Set active page
        cycle = False                                         # Disable auto-cycle
        ble_p.send("ACK:SRC=SENSOR")                          # Send acknowledgment
    elif s == "SRC:SYSTEM":                                   # If request SYSTEM page
        page = "SYSTEM"                                       # Set active page
        cycle = False                                         # Disable auto-cycle
        ble_p.send("ACK:SRC=SYSTEM")                          # Send acknowledgment
    elif s == "SRC:BT":                                       # If request BT page
        page = "BT"                                           # Set active page
        cycle = False                                         # Disable auto-cycle
        ble_p.send("ACK:SRC=BT")                              # Send acknowledgment
    elif s == "SRC:CYCLE":                                    # If request auto cycle
        cycle = True                                          # Enable auto-cycle
        ble_p.send("ACK:SRC=CYCLE")                           # Send acknowledgment
    elif s.startswith("ERR:"):                                # If an error arrives
        alert = True                                          # Raise visual alert
        ble_p.send("ACK:ERR")                                 # Confirm error receipt
    else:                                                     # Otherwise unknown
        ble_p.send("ERR:UNKNOWN")                             # Error feedback

handle.recv(handle_method)                                    # Register receive callback
print("[BT] Callback registered")                             # Serial: callback active

show(0, "Display Station", center=True)                       # Initial title screen
show(1, "SRC=" + page, center=True)                           # Show current source
time.sleep_ms(900)                                            # Brief welcome delay

while True:                                                   # Main UI loop
    if alert:                                                 # If alert is active
        blink_alert_once("BT/Error")                          # Blink a single alert
        alert = False                                         # Reset alert flag
    if cycle:                                                 # If auto-cycle enabled
        page = "SENSOR" if page == "BT" else ("SYSTEM" if page == "SENSOR" else "BT")  # Rotate pages
    if page == "SENSOR":                                      # SENSOR page view
        raw = adc2.read()                                     # Read ADC raw value
        show(0, "Sensor ADC2")                                # Header
        show(1, fmt_adc(raw))                                 # Formatted value line
    elif page == "SYSTEM":                                    # SYSTEM page view
        show(0, "System Status")                              # Header
        show(1, fmt_up(time.ticks_ms()))                      # Uptime mm:ss
    else:                                                     # BT page view
        show(0, "Last BT Msg")                                # Header
        show(1, last_rx if last_rx else "(none)")             # Message or placeholder
    time.sleep_ms(850)                                        # Cadence to keep updates smooth

External explanation

  • What it teaches: A practical way to unify Bluetooth control, formatted data display, and serial logging in a student‑friendly UI.
  • Why it works: The callback updates state on incoming messages; helpers keep lines neat; the loop refreshes at a steady pace without overwhelming the LCD.
  • Key concept: Separate data formatting from rendering; keep updates short and consistent.

Story time

Your robot has a voice and a screen now. It hears “SRC:SENSOR,” switches instantly, and shows clean numbers with units. If something goes wrong, it flashes “ALERT!”—clear and calm.


Debugging (2)

Debugging 3.9.A – Overloaded display

  • Symptom: Text overlaps or looks messy after multiple updates.
  • Fix: Fit to 16 characters and avoid noisy rapid updates.
# Always fit to 16 chars to prevent spillover
show(1, fit16("ADC=" + str(val)))  # Keep under 16
# Keep cadence ~800–900 ms to avoid flicker
time.sleep_ms(850)                 # Smooth update rate

Debugging 3.9.B – Slow updates

  • Symptom: UI lags after Bluetooth messages or sensor reads.
  • Fix: Minimize heavy prints and keep loop delays predictable.
# Reduce verbose serial prints inside fast loops
print("[BT] RX:", s)               # One concise log per event
# Keep display loop delays short and consistent
time.sleep_ms(850)                 # Avoid long sleeps or jitter

Final checklist

  • Source pages (SENSOR, SYSTEM, BT) render clearly on the LCD.
  • Bluetooth commands switch sources and send concise ACK/ERR.
  • ADC values and uptime are formatted neatly for 16 characters.
  • Visual alerts blink once and then return to normal view.
  • Serial logs mirror UI actions without spam.

Extras

  • Student tip: Try “SRC:CYCLE” and watch the station rotate pages; then send “SRC:BT” to take control.
  • Instructor tip: Demonstrate how helpers (fit16, fmt_adc, fmt_up) keep code maintainable and displays readable.
  • Glossary:
    • Peripheral: Bluetooth device that advertises and receives data (your station).
    • Callback: Function invoked automatically on incoming Bluetooth messages.
    • I2C: Two‑wire bus (SCL, SDA) for the LCD.
  • Mini tips:
    • Keep messages short (SRC:…, ERR:…).
    • Prefer left‑aligned data lines for readability, centered headers for clarity.
    • Use consistent update intervals to reduce flicker and lag.
On this page