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
| Part | How many? | Pin connection (R32) |
|---|---|---|
| D1 R32 | 1 | USB cable |
| 1602 LCD (I2C) | 1 | SCL → Pin 26, SDA → Pin 5, VCC 5V, GND |
| Built‑in Bluetooth | 1 | Internal (no pins) |
| Optional sensor (ADC) | 1 | Joystick 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.