Skip to content

Common Mistakes

A collection of frequently made errors in each lab and how to avoid them.


Lab 1: Digital I/O & Timing

Mistake: Ignoring Switch Bounce

# WRONG - counts multiple presses for one physical press
if button.value() == 0:
    count += 1
# RIGHT - debounce with timing
if button.value() == 0:
    if time.ticks_diff(now, last_press) > 50:  # 50ms debounce
        count += 1
        last_press = now

Why it matters: Switch bounce causes 5-50 spurious transitions per press.


Mistake: Using time.sleep() for Timing Measurement

# WRONG - sleep itself has jitter, you're measuring sleep, not your code
start = time.ticks_ms()
time.sleep_ms(100)
end = time.ticks_ms()
print(end - start)  # Will vary!
# RIGHT - measure actual operations
start = time.ticks_ms()
do_actual_work()
end = time.ticks_ms()
print(end - start)

Mistake: Drift from Cumulative Timing

# WRONG - error accumulates
while True:
    led.toggle()
    time.sleep_ms(500)  # Each sleep adds error
# RIGHT - anchor to start time
start = time.ticks_ms()
next_toggle = start
while True:
    if time.ticks_diff(time.ticks_ms(), next_toggle) >= 0:
        led.toggle()
        next_toggle += 500  # Absolute time, no drift

Lab 2: PWM & Sensors

Mistake: Assuming Linear PWM-to-Brightness

# WRONG - 50% duty doesn't look like 50% brightness
pwm.duty_u16(32768)  # "Should be half bright"
# RIGHT - apply gamma correction for perceived linearity
def gamma_correct(percent, gamma=2.2):
    return int((percent / 100) ** gamma * 65535)

pwm.duty_u16(gamma_correct(50))  # Actually looks like 50%

Why: Human eyes perceive light logarithmically, not linearly.


Mistake: Wrong PWM Frequency for Application

# WRONG for LED - frequency too low, causes visible flicker
pwm.freq(50)  # 50Hz - you'll see blinking

# WRONG for servo - frequency too high
pwm.freq(1000)  # Servos expect 50Hz!
# RIGHT - choose frequency for application
pwm.freq(1000)   # LEDs: 1kHz+ (no visible flicker)
pwm.freq(50)     # Servos: exactly 50Hz (20ms period)
pwm.freq(20000)  # Motors: 20kHz (above audible range)

Mistake: Not Handling ADC Noise

# WRONG - single reading is noisy
value = adc.read_u16()  # Could be ±500 from true value!
# RIGHT - average multiple readings
def read_adc_averaged(adc, samples=10):
    total = sum(adc.read_u16() for _ in range(samples))
    return total // samples

Lab 3: Calibration & Uncertainty

Mistake: Single-Point Calibration

# WRONG - assumes linear and zero-offset
# "At 20cm, sensor reads 3000, so: distance = reading / 150"
distance = reading / 150
# RIGHT - multi-point calibration with linear fit
# Measured: 10cm=1500, 20cm=3000, 30cm=4400 (not perfectly linear!)
def calibrated_distance(reading):
    # Linear fit from measurements: y = 0.00685x + 0.23
    return 0.00685 * reading + 0.23

Mistake: Reporting Values Without Uncertainty

# WRONG - implies false precision
print(f"Distance: 25.3847 cm")  # Really? To 0.0001 cm?
# RIGHT - include uncertainty from your measurements
print(f"Distance: 25.4 ± 1.5 cm")  # Honest about precision

Mistake: Confusing Precision and Accuracy

  • Precision: How repeatable are readings? (Low spread)
  • Accuracy: How close to true value? (Low offset)
High precision, low accuracy:    High accuracy, low precision:
      ●●●                              ●
       ●●        ⊕ (target)       ●   ⊕   ●
      ●●●                              ●

You can have one without the other!

Lab 4: Multi-Sensor Integration

Mistake: Not Initializing I2C Correctly

# WRONG - wrong pins or frequency
i2c = I2C(0)  # Uses default pins, which may not match wiring

# WRONG - frequency too high for device
i2c = I2C(0, freq=1000000)  # 1MHz - many devices can't handle this
# RIGHT - explicit pins and safe frequency
i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000)  # 400kHz standard

Mistake: Drawing to OLED Too Frequently

# WRONG - floods I2C bus, slows everything
while True:
    oled.fill(0)
    oled.text(f"Value: {read_sensor()}", 0, 0)
    oled.show()  # Takes ~30ms over I2C!
# RIGHT - update display at reasonable rate
last_display = 0
DISPLAY_INTERVAL = 200  # 5 Hz is plenty for human reading

while True:
    now = time.ticks_ms()
    # Fast sensor reading here...

    if time.ticks_diff(now, last_display) >= DISPLAY_INTERVAL:
        oled.fill(0)
        oled.text(f"Value: {value}", 0, 0)
        oled.show()
        last_display = now

Mistake: Line Sensor Threshold Too Fixed

# WRONG - hard-coded threshold fails on different surfaces
THRESHOLD = 30000  # Works on white paper, fails on carpet
# RIGHT - calibrate on actual surface
def calibrate_line_sensor():
    print("Place on WHITE surface, press button")
    wait_for_button()
    white = read_line_sensors()

    print("Place on BLACK line, press button")
    wait_for_button()
    black = read_line_sensors()

    threshold = (white + black) // 2
    print(f"Threshold: {threshold}")
    return threshold

Lab 5: Motion Control

Mistake: Assuming Motors Are Identical

# WRONG - motors have manufacturing variation
Motor.Car_Run(100, 100)  # Expects straight line, curves left!
# RIGHT - measure and compensate
LEFT_BALANCE = 1.05  # From characterization: left needs 5% more
Motor.Car_Run(int(100 * LEFT_BALANCE), 100)

Mistake: Ignoring Dead Zone

# WRONG - motor doesn't move at low PWM
for pwm in range(0, 255, 10):
    Motor.Car_Run(pwm, pwm)  # Nothing happens until PWM ~60!
# RIGHT - map speed to usable PWM range
DEAD_ZONE = 60  # Measured minimum PWM for movement
MAX_PWM = 255

def speed_to_pwm(speed_percent):
    if speed_percent <= 0:
        return 0
    return int(DEAD_ZONE + (MAX_PWM - DEAD_ZONE) * speed_percent / 100)

Mistake: Guessing Kp Instead of Calculating

# WRONG - trial and error
Kp = 50  # "Seemed to work"... but why? Will it work tomorrow?
# RIGHT - calculate from measurements (see Lab 5.4)
# From characterization: 6°/s turn rate per PWM unit
# Desired: correct 45° error in 0.5s = 90°/s
# Kp = 90 / 6 / max_error = 15
Kp = 15  # Justified by measurement!

Mistake: No Error Clamping

# WRONG - correction can exceed motor limits
correction = Kp * error  # Could be 500 when max PWM is 255!
Motor.Car_Run(base + correction, base - correction)
# RIGHT - clamp to valid range
correction = Kp * error
left = max(0, min(255, base + correction))
right = max(0, min(255, base - correction))
Motor.Car_Run(left, right)

Lab 6: Software Architecture

Mistake: Doing Work in ISR

# WRONG - ISR takes too long, blocks other interrupts
def button_isr(pin):
    update_display()      # 30ms!
    save_to_file()        # 50ms!
    play_sound()          # 100ms!
# RIGHT - ISR sets flag, main loop does work
button_pressed = False

def button_isr(pin):
    global button_pressed
    button_pressed = True  # <1µs, then return

# Main loop
while True:
    if button_pressed:
        button_pressed = False
        update_display()
        save_to_file()
        play_sound()

Mistake: Using time.sleep() in Event Loop

# WRONG - blocks everything
while True:
    if button.value() == 0:
        led.on()
        time.sleep(1)  # Can't check anything for 1 second!
        led.off()
# RIGHT - non-blocking with state
led_on_until = 0

while True:
    now = time.ticks_ms()

    if button.value() == 0:
        led.on()
        led_on_until = now + 1000  # Schedule off time

    if led_on_until and time.ticks_diff(now, led_on_until) >= 0:
        led.off()
        led_on_until = 0

Mistake: String States Instead of Constants

# WRONG - typos cause bugs, no autocomplete
state = "runing"  # Typo! Will never match "running"

if state == "running":  # Never true!
# RIGHT - constants catch typos at definition
STATE_IDLE = 0
STATE_RUNNING = 1
STATE_STOPPED = 2

state = STATE_RUNNING  # Typo here = NameError (caught immediately)

Mistake: No Default Case in State Machine

# WRONG - unknown state = silent failure
if state == STATE_IDLE:
    handle_idle()
elif state == STATE_RUNNING:
    handle_running()
# What if state == 99 somehow?
# RIGHT - catch unexpected states
if state == STATE_IDLE:
    handle_idle()
elif state == STATE_RUNNING:
    handle_running()
else:
    print(f"ERROR: Unknown state {state}")
    state = STATE_IDLE  # Recover to known state

Lab 7: System Integration

Mistake: No Pre-flight Checks

# WRONG - hope everything works
while True:
    run_mission()  # Motors don't work, battery dead, sensors disconnected...
# RIGHT - verify before running
def preflight_check():
    errors = []

    if battery_voltage() < 6.5:
        errors.append("Battery low")

    if not test_motors():
        errors.append("Motor test failed")

    if not calibrate_sensors():
        errors.append("Sensor calibration failed")

    if errors:
        for e in errors:
            print(f"FAIL: {e}")
        return False

    print("All checks passed")
    return True

Mistake: No Logging During Mission

# WRONG - can't analyze failures
while running:
    follow_line()  # Robot crashed... but why?
# RIGHT - log everything for post-mortem
logger = DataLogger()

while running:
    error = get_line_position()
    correction = Kp * error

    logger.log(
        error=error,
        correction=correction,
        left_pwm=left,
        right_pwm=right,
        state=state
    )

    follow_line()
# After crash: analyze log to find what went wrong

Mistake: Testing Only Happy Path

# WRONG - only tested on perfect track
# "It worked in the lab" → fails in competition
# RIGHT - test failure modes
- What if line sensor sees no line?
- What if battery drops during run?
- What if track has sharp corners?
- What if track has intersections?
- What if robot is bumped?

General Mistakes (All Labs)

Mistake: Print Debugging Affects Timing

# WRONG - print takes ~1ms, ruins fast loops
while True:
    print(f"Value: {value}")  # Slows loop from 1kHz to 500Hz
    fast_control_loop()
# RIGHT - conditional or sampled printing
count = 0
while True:
    fast_control_loop()
    count += 1
    if count % 100 == 0:  # Print every 100th iteration
        print(f"Value: {value}")

Mistake: Magic Numbers

# WRONG - what does 147 mean?
if adc.read_u16() > 32768 and pin.value() == 0:
    pwm.duty_u16(49152)
# RIGHT - named constants explain intent
LIGHT_THRESHOLD = 32768  # ~1.65V, half of 3.3V range
BUTTON_PRESSED = 0       # Active low button
MOTOR_75_PERCENT = 49152 # 75% of 65535

if adc.read_u16() > LIGHT_THRESHOLD and pin.value() == BUTTON_PRESSED:
    pwm.duty_u16(MOTOR_75_PERCENT)

Mistake: No Comments on Non-Obvious Code

# WRONG - why 1.8? Why +500?
result = (value * 1.8 + 500) >> 4
# RIGHT - explain the reasoning
# Convert ADC reading to temperature in °C
# Sensor: 10mV/°C, offset 500mV at 0°C
# ADC: 3.3V/65535 = 50.4µV/count
# Formula: T = (V - 0.5) / 0.01 = (count * 3.3/65535 - 0.5) / 0.01
result = (value * 1.8 + 500) >> 4  # Approximation of above, ~±0.5°C

See Also

  • [[Reference/extras/troubleshooting|Hardware Troubleshooting Guide]]
  • [[Reference/extras/glossary|Glossary]]
  • [[Reference/extras/embedded-thinking-cheatsheet|Embedded Thinking Cheatsheet]]