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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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]]