<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.bespokerobotsociety.org/index.php?action=history&amp;feed=atom&amp;title=Sensor_Interfacing</id>
	<title>Sensor Interfacing - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.bespokerobotsociety.org/index.php?action=history&amp;feed=atom&amp;title=Sensor_Interfacing"/>
	<link rel="alternate" type="text/html" href="https://wiki.bespokerobotsociety.org/index.php?title=Sensor_Interfacing&amp;action=history"/>
	<updated>2026-04-25T08:41:54Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://wiki.bespokerobotsociety.org/index.php?title=Sensor_Interfacing&amp;diff=88&amp;oldid=prev</id>
		<title>John: Created page with &quot;{{Tutorial |name=Sensor Interfacing |competency=Electronics |difficulty=Intermediate |time=4-6 hours |prerequisites=Electronics Fundamentals, MicroPython Basics, basic understanding of digital signals |materials=Microcontroller (Raspberry Pi Pico), breadboard, I2C sensor (MPU6050 IMU), analog sensor (photoresistor), digital sensor (IR line detector), 10kΩ resistors, jumper wires, multimeter, logic analyzer (optional but helpful) |next_steps=Add IMU or distan...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.bespokerobotsociety.org/index.php?title=Sensor_Interfacing&amp;diff=88&amp;oldid=prev"/>
		<updated>2025-10-11T20:16:48Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;{{Tutorial |name=Sensor Interfacing |competency=&lt;a href=&quot;/wiki/Electronics&quot; title=&quot;Electronics&quot;&gt;Electronics&lt;/a&gt; |difficulty=Intermediate |time=4-6 hours |prerequisites=&lt;a href=&quot;/wiki/Electronics_Fundamentals&quot; title=&quot;Electronics Fundamentals&quot;&gt;Electronics Fundamentals&lt;/a&gt;, &lt;a href=&quot;/wiki/MicroPython_Basics&quot; title=&quot;MicroPython Basics&quot;&gt;MicroPython Basics&lt;/a&gt;, basic understanding of digital signals |materials=Microcontroller (Raspberry Pi Pico), breadboard, I2C sensor (MPU6050 IMU), analog sensor (photoresistor), digital sensor (IR line detector), 10kΩ resistors, jumper wires, multimeter, logic analyzer (optional but helpful) |next_steps=Add IMU or distan...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Tutorial&lt;br /&gt;
|name=Sensor Interfacing&lt;br /&gt;
|competency=[[Electronics]]&lt;br /&gt;
|difficulty=Intermediate&lt;br /&gt;
|time=4-6 hours&lt;br /&gt;
|prerequisites=[[Electronics Fundamentals]], [[MicroPython Basics]], basic understanding of digital signals&lt;br /&gt;
|materials=Microcontroller (Raspberry Pi Pico), breadboard, I2C sensor (MPU6050 IMU), analog sensor (photoresistor), digital sensor (IR line detector), 10kΩ resistors, jumper wires, multimeter, logic analyzer (optional but helpful)&lt;br /&gt;
|next_steps=Add IMU or distance sensors to your robot, [[MicroPython Programming]], build advanced capabilities&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Sensor Interfacing&amp;#039;&amp;#039;&amp;#039; teaches you how to connect and read sensors in robotics applications. You&amp;#039;ll learn about communication protocols (I2C, SPI), analog sensors with ADC, pull-up resistors, and active HIGH/LOW logic. By the end of this tutorial, you&amp;#039;ll be able to integrate new sensors into your robots and troubleshoot sensor communication issues.&lt;br /&gt;
&lt;br /&gt;
This tutorial builds on [[Electronics Fundamentals]] and [[MicroPython Basics]] - you should understand voltage, digital signals, and basic Python programming.&lt;br /&gt;
&lt;br /&gt;
== Why Sensor Interfacing Matters ==&lt;br /&gt;
&lt;br /&gt;
Sensors are your robot&amp;#039;s perception of the world:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Vision&amp;#039;&amp;#039;&amp;#039; - Detect lines, obstacles, colors (cameras, distance sensors)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Motion&amp;#039;&amp;#039;&amp;#039; - Measure acceleration, rotation, orientation (IMUs, gyroscopes)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Position&amp;#039;&amp;#039;&amp;#039; - Track wheel rotation, distance traveled (encoders, odometry)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Environment&amp;#039;&amp;#039;&amp;#039; - Detect light, temperature, sound (photoresistors, thermistors, microphones)&lt;br /&gt;
&lt;br /&gt;
Without proper sensor interfacing, you cannot:&lt;br /&gt;
* Add new capabilities to your robot&lt;br /&gt;
* Debug sensor communication problems&lt;br /&gt;
* Select appropriate sensors for your application&lt;br /&gt;
* Understand sensor datasheets and specifications&lt;br /&gt;
&lt;br /&gt;
== Part 1: Sensor Types and Outputs ==&lt;br /&gt;
&lt;br /&gt;
=== Digital Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Digital sensors&amp;#039;&amp;#039;&amp;#039; output HIGH or LOW signals:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Active HIGH&amp;#039;&amp;#039;&amp;#039; - HIGH means &amp;quot;detected&amp;quot; (e.g., button pressed = HIGH)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Active LOW&amp;#039;&amp;#039;&amp;#039; - LOW means &amp;quot;detected&amp;quot; (e.g., [[Infrared Line Detector]] on black line = LOW)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example: SimpleBot line sensors&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Output HIGH when no line (white surface reflects IR light)&lt;br /&gt;
* Output LOW when on line (black tape absorbs IR light)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Reading digital sensors:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import Pin&lt;br /&gt;
&lt;br /&gt;
# Configure with pull-up resistor (sensor is active LOW)&lt;br /&gt;
sensor = Pin(10, Pin.IN, Pin.PULL_UP)&lt;br /&gt;
&lt;br /&gt;
if sensor.value() == 0:  # LOW = line detected&lt;br /&gt;
    print(&amp;quot;Line detected!&amp;quot;)&lt;br /&gt;
else:&lt;br /&gt;
    print(&amp;quot;No line&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Analog Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Analog sensors&amp;#039;&amp;#039;&amp;#039; output variable voltage:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Photoresistor&amp;#039;&amp;#039;&amp;#039; - Resistance changes with light (requires voltage divider)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Temperature sensor&amp;#039;&amp;#039;&amp;#039; - Voltage proportional to temperature&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Potentiometer&amp;#039;&amp;#039;&amp;#039; - Voltage varies from 0V to Vcc&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Pressure sensor&amp;#039;&amp;#039;&amp;#039; - Voltage indicates force&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;ADC (Analog-to-Digital Conversion)&amp;#039;&amp;#039;&amp;#039; converts voltage to number:&lt;br /&gt;
* Raspberry Pi Pico: 12-bit ADC (0-4095) with 3.3V reference&lt;br /&gt;
* ESP32: 12-bit ADC (0-4095) with configurable reference&lt;br /&gt;
* Arduino Uno: 10-bit ADC (0-1023) with 5V reference&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Reading analog sensors:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import ADC&lt;br /&gt;
&lt;br /&gt;
# Configure ADC on GPIO26 (ADC0 on Raspberry Pi Pico)&lt;br /&gt;
sensor = ADC(26)&lt;br /&gt;
&lt;br /&gt;
# Read raw ADC value (0-65535 on Pico using 16-bit conversion)&lt;br /&gt;
raw_value = sensor.read_u16()&lt;br /&gt;
&lt;br /&gt;
# Convert to voltage (Pico uses 3.3V reference)&lt;br /&gt;
voltage = raw_value * 3.3 / 65535&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;ADC: {raw_value}, Voltage: {voltage:.2f}V&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Communication Protocol Sensors ===&lt;br /&gt;
&lt;br /&gt;
Advanced sensors use digital communication protocols:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;I2C&amp;#039;&amp;#039;&amp;#039; - Two-wire bus, multiple devices, addresses (IMUs, distance sensors)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;SPI&amp;#039;&amp;#039;&amp;#039; - Four-wire, high-speed, chip-select per device (displays, SD cards)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;UART&amp;#039;&amp;#039;&amp;#039; - Two-wire, point-to-point (GPS modules, serial sensors)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;1-Wire&amp;#039;&amp;#039;&amp;#039; - Single data wire (temperature sensors)&lt;br /&gt;
&lt;br /&gt;
Most modern robot sensors use &amp;#039;&amp;#039;&amp;#039;I2C&amp;#039;&amp;#039;&amp;#039; because:&lt;br /&gt;
* Only 2 wires (SDA and SCL) for multiple devices&lt;br /&gt;
* Built-in addressing (up to 127 devices on one bus)&lt;br /&gt;
* Widely supported by microcontrollers&lt;br /&gt;
* Good for moderate-speed data (100 kHz - 400 kHz typical)&lt;br /&gt;
&lt;br /&gt;
== Part 2: Pull-up and Pull-down Resistors ==&lt;br /&gt;
&lt;br /&gt;
=== The Floating Input Problem ===&lt;br /&gt;
&lt;br /&gt;
Digital inputs must be either HIGH or LOW - never &amp;#039;&amp;#039;&amp;#039;floating&amp;#039;&amp;#039;&amp;#039; (undefined):&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Floating input&amp;#039;&amp;#039;&amp;#039; - Not connected to anything, voltage drifts randomly&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Symptom&amp;#039;&amp;#039;&amp;#039; - Erratic readings, false triggers, unpredictable behavior&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Solution&amp;#039;&amp;#039;&amp;#039; - Pull-up or pull-down resistor&lt;br /&gt;
&lt;br /&gt;
=== Pull-up Resistors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Pull-up resistor&amp;#039;&amp;#039;&amp;#039; connects input to HIGH voltage (3.3V or 5V):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    +3.3V&lt;br /&gt;
      |&lt;br /&gt;
     [10kΩ]  ← Pull-up resistor&lt;br /&gt;
      |&lt;br /&gt;
      +------- Input Pin&lt;br /&gt;
      |&lt;br /&gt;
    [Switch] ← When closed, pulls to GND&lt;br /&gt;
      |&lt;br /&gt;
     GND&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Behavior:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Switch open → Input = HIGH (pulled up by resistor)&lt;br /&gt;
* Switch closed → Input = LOW (connected to GND)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Default state: HIGH&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example: I2C communication requires pull-ups on SDA and SCL lines&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=== Pull-down Resistors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Pull-down resistor&amp;#039;&amp;#039;&amp;#039; connects input to LOW voltage (GND):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    +3.3V&lt;br /&gt;
      |&lt;br /&gt;
    [Switch] ← When closed, pulls to +3.3V&lt;br /&gt;
      |&lt;br /&gt;
      +------- Input Pin&lt;br /&gt;
      |&lt;br /&gt;
     [10kΩ]  ← Pull-down resistor&lt;br /&gt;
      |&lt;br /&gt;
     GND&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Behavior:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Switch open → Input = LOW (pulled down by resistor)&lt;br /&gt;
* Switch closed → Input = HIGH (connected to +3.3V)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Default state: LOW&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=== Internal Pull-up/Pull-down ===&lt;br /&gt;
&lt;br /&gt;
Most microcontrollers have &amp;#039;&amp;#039;&amp;#039;internal pull-up/pull-down resistors&amp;#039;&amp;#039;&amp;#039; (typically 10-50kΩ):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import Pin&lt;br /&gt;
&lt;br /&gt;
# Enable internal pull-up&lt;br /&gt;
sensor_pull_up = Pin(10, Pin.IN, Pin.PULL_UP)&lt;br /&gt;
&lt;br /&gt;
# Enable internal pull-down&lt;br /&gt;
sensor_pull_down = Pin(11, Pin.IN, Pin.PULL_DOWN)&lt;br /&gt;
&lt;br /&gt;
# No pull resistor (floating - avoid this for digital inputs!)&lt;br /&gt;
sensor_floating = Pin(12, Pin.IN)  # BAD PRACTICE&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When to use external pull-ups:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* I2C communication (typically 4.7kΩ or 10kΩ)&lt;br /&gt;
* Multiple devices sharing the same line&lt;br /&gt;
* When internal pull-up is too weak (long wires, high capacitance)&lt;br /&gt;
&lt;br /&gt;
=== Resistor Value Selection ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Typical values:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;10kΩ&amp;#039;&amp;#039;&amp;#039; - General-purpose pull-up/pull-down&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;4.7kΩ&amp;#039;&amp;#039;&amp;#039; - I2C pull-ups (standard value)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;1kΩ&amp;#039;&amp;#039;&amp;#039; - Strong pull-up for high-speed or long wires&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;100kΩ&amp;#039;&amp;#039;&amp;#039; - Weak pull-up to save power&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Trade-offs:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Lower resistance&amp;#039;&amp;#039;&amp;#039; (1kΩ) - Stronger pull, faster signals, more power consumption&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Higher resistance&amp;#039;&amp;#039;&amp;#039; (100kΩ) - Weaker pull, slower signals, less power consumption&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Rule of thumb:&amp;#039;&amp;#039;&amp;#039; 10kΩ works for most applications. Use 4.7kΩ for I2C.&lt;br /&gt;
&lt;br /&gt;
== Part 3: I2C Communication ==&lt;br /&gt;
&lt;br /&gt;
=== What is I2C? ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;I2C (Inter-Integrated Circuit)&amp;#039;&amp;#039;&amp;#039; is a two-wire communication protocol:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;SDA&amp;#039;&amp;#039;&amp;#039; - Serial Data (bidirectional data line)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;SCL&amp;#039;&amp;#039;&amp;#039; - Serial Clock (clock signal from master)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Plus&amp;#039;&amp;#039;&amp;#039; - Ground (GND) and power (VCC)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Characteristics:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Multi-master, multi-slave (but typically one master)&lt;br /&gt;
* 7-bit or 10-bit device addresses&lt;br /&gt;
* Speeds: 100 kHz (standard), 400 kHz (fast), up to 3.4 MHz (high-speed)&lt;br /&gt;
* Open-drain design requires pull-up resistors&lt;br /&gt;
&lt;br /&gt;
=== I2C Device Addressing ===&lt;br /&gt;
&lt;br /&gt;
Each I2C device has a unique &amp;#039;&amp;#039;&amp;#039;7-bit address&amp;#039;&amp;#039;&amp;#039; (0x00 - 0x7F):&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Common sensor addresses:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;MPU6050 IMU&amp;#039;&amp;#039;&amp;#039; - 0x68 (default) or 0x69 (alternate)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;VL53L0X distance sensor&amp;#039;&amp;#039;&amp;#039; - 0x29&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;BMP280 pressure sensor&amp;#039;&amp;#039;&amp;#039; - 0x76 or 0x77&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OLED display (SSD1306)&amp;#039;&amp;#039;&amp;#039; - 0x3C or 0x3D&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Address conflicts:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
If two sensors have the same address, you cannot use them on the same I2C bus (unless they have configurable addresses via ADR pin).&lt;br /&gt;
&lt;br /&gt;
=== Wiring I2C Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Raspberry Pi Pico → MPU6050 IMU:&lt;br /&gt;
- GP0 (I2C0 SDA) → SDA&lt;br /&gt;
- GP1 (I2C0 SCL) → SCL&lt;br /&gt;
- 3.3V → VCC&lt;br /&gt;
- GND → GND&lt;br /&gt;
&lt;br /&gt;
Pull-up resistors (4.7kΩ):&lt;br /&gt;
- SDA to 3.3V&lt;br /&gt;
- SCL to 3.3V&lt;br /&gt;
&lt;br /&gt;
Note: Some breakout boards have built-in pull-ups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Important:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Use I2C-capable pins (check your microcontroller pinout)&lt;br /&gt;
* Raspberry Pi Pico has two I2C buses: I2C0 (GP0/GP1, GP4/GP5, etc.) and I2C1 (GP2/GP3, GP6/GP7, etc.)&lt;br /&gt;
* Add pull-up resistors if not present on breakout board&lt;br /&gt;
&lt;br /&gt;
=== I2C Code Example: Scanning for Devices ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import I2C, Pin&lt;br /&gt;
&lt;br /&gt;
# Initialize I2C bus&lt;br /&gt;
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)&lt;br /&gt;
&lt;br /&gt;
# Scan for devices&lt;br /&gt;
devices = i2c.scan()&lt;br /&gt;
&lt;br /&gt;
if devices:&lt;br /&gt;
    print(f&amp;quot;Found {len(devices)} I2C device(s):&amp;quot;)&lt;br /&gt;
    for device in devices:&lt;br /&gt;
        print(f&amp;quot;  - Address: 0x{device:02X}&amp;quot;)&lt;br /&gt;
else:&lt;br /&gt;
    print(&amp;quot;No I2C devices found&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Expected output (MPU6050 connected):&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Found 1 I2C device(s):&lt;br /&gt;
  - Address: 0x68&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== I2C Code Example: Reading MPU6050 IMU ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import I2C, Pin&lt;br /&gt;
import time&lt;br /&gt;
import struct&lt;br /&gt;
&lt;br /&gt;
class MPU6050:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Simple MPU6050 IMU driver&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    # I2C address&lt;br /&gt;
    ADDR = 0x68&lt;br /&gt;
&lt;br /&gt;
    # Register addresses&lt;br /&gt;
    PWR_MGMT_1 = 0x6B  # Power management&lt;br /&gt;
    ACCEL_XOUT_H = 0x3B  # Accelerometer data start&lt;br /&gt;
    GYRO_XOUT_H = 0x43   # Gyroscope data start&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, i2c):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Initialize MPU6050 on given I2C bus&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.i2c = i2c&lt;br /&gt;
&lt;br /&gt;
        # Wake up sensor (clear sleep bit)&lt;br /&gt;
        self.i2c.writeto_mem(self.ADDR, self.PWR_MGMT_1, b&amp;#039;\x00&amp;#039;)&lt;br /&gt;
        time.sleep_ms(100)&lt;br /&gt;
&lt;br /&gt;
    def read_accel(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Read accelerometer data (m/s²)&lt;br /&gt;
&lt;br /&gt;
        Returns:&lt;br /&gt;
            Tuple (x, y, z) in m/s²&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Read 6 bytes starting at ACCEL_XOUT_H&lt;br /&gt;
        data = self.i2c.readfrom_mem(self.ADDR, self.ACCEL_XOUT_H, 6)&lt;br /&gt;
&lt;br /&gt;
        # Unpack as three 16-bit signed integers (big-endian)&lt;br /&gt;
        ax, ay, az = struct.unpack(&amp;#039;&amp;gt;hhh&amp;#039;, data)&lt;br /&gt;
&lt;br /&gt;
        # Convert to m/s² (16384 LSB/g for default ±2g range)&lt;br /&gt;
        scale = 9.81 / 16384&lt;br /&gt;
        return (ax * scale, ay * scale, az * scale)&lt;br /&gt;
&lt;br /&gt;
    def read_gyro(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Read gyroscope data (deg/s)&lt;br /&gt;
&lt;br /&gt;
        Returns:&lt;br /&gt;
            Tuple (x, y, z) in deg/s&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Read 6 bytes starting at GYRO_XOUT_H&lt;br /&gt;
        data = self.i2c.readfrom_mem(self.ADDR, self.GYRO_XOUT_H, 6)&lt;br /&gt;
&lt;br /&gt;
        # Unpack as three 16-bit signed integers (big-endian)&lt;br /&gt;
        gx, gy, gz = struct.unpack(&amp;#039;&amp;gt;hhh&amp;#039;, data)&lt;br /&gt;
&lt;br /&gt;
        # Convert to deg/s (131 LSB/(deg/s) for default ±250 deg/s range)&lt;br /&gt;
        scale = 1 / 131&lt;br /&gt;
        return (gx * scale, gy * scale, gz * scale)&lt;br /&gt;
&lt;br /&gt;
# Initialize I2C and sensor&lt;br /&gt;
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)&lt;br /&gt;
imu = MPU6050(i2c)&lt;br /&gt;
&lt;br /&gt;
# Read sensor data&lt;br /&gt;
while True:&lt;br /&gt;
    accel = imu.read_accel()&lt;br /&gt;
    gyro = imu.read_gyro()&lt;br /&gt;
&lt;br /&gt;
    print(f&amp;quot;Accel: X={accel[0]:6.2f} Y={accel[1]:6.2f} Z={accel[2]:6.2f} m/s²&amp;quot;)&lt;br /&gt;
    print(f&amp;quot;Gyro:  X={gyro[0]:6.2f} Y={gyro[1]:6.2f} Z={gyro[2]:6.2f} deg/s&amp;quot;)&lt;br /&gt;
    print()&lt;br /&gt;
&lt;br /&gt;
    time.sleep(0.5)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== I2C Troubleshooting ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;No devices found (empty scan):&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Check wiring (SDA, SCL, GND, VCC all connected)&lt;br /&gt;
* Verify power supply (3.3V or 5V depending on sensor)&lt;br /&gt;
* Check pull-up resistors (need 4.7kΩ on SDA and SCL)&lt;br /&gt;
* Try lower I2C frequency (100 kHz instead of 400 kHz)&lt;br /&gt;
* Verify I2C pins (use correct pins for your microcontroller)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OSError: [Errno 5] EIO (Input/Output Error):&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Wrong I2C address (use scan to find actual address)&lt;br /&gt;
* Sensor not responding (check power, try different sensor)&lt;br /&gt;
* Bus contention (multiple masters or short circuit)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Intermittent readings:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Weak pull-up resistors (use 4.7kΩ or 2.2kΩ)&lt;br /&gt;
* Long wires (keep I2C wires &amp;lt;1 meter, preferably &amp;lt;30cm)&lt;br /&gt;
* EMI interference (route I2C wires away from motors)&lt;br /&gt;
* Bad connections (check breadboard connections)&lt;br /&gt;
&lt;br /&gt;
== Part 4: Analog Sensors and ADC ==&lt;br /&gt;
&lt;br /&gt;
=== Voltage Dividers for Resistive Sensors ===&lt;br /&gt;
&lt;br /&gt;
Many sensors (photoresistors, thermistors, FSRs) are &amp;#039;&amp;#039;&amp;#039;variable resistors&amp;#039;&amp;#039;&amp;#039;. To read them, create a voltage divider:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    +3.3V&lt;br /&gt;
      |&lt;br /&gt;
     [R1]  ← Fixed resistor (e.g., 10kΩ)&lt;br /&gt;
      |&lt;br /&gt;
      +------- ADC Input (Vout)&lt;br /&gt;
      |&lt;br /&gt;
  [Photoresistor] ← Variable resistor&lt;br /&gt;
      |&lt;br /&gt;
     GND&lt;br /&gt;
&lt;br /&gt;
Vout = 3.3V × R_photo / (R1 + R_photo)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;How it works:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Bright light → Photoresistor low resistance (~1kΩ) → Vout low&lt;br /&gt;
* Dark → Photoresistor high resistance (~100kΩ) → Vout high&lt;br /&gt;
&lt;br /&gt;
=== Reading Analog Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example: Photoresistor for optical encoders (SimpleBot)&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import ADC, Pin&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# Configure ADC on GP26 (ADC0)&lt;br /&gt;
photoresistor = ADC(26)&lt;br /&gt;
&lt;br /&gt;
# Read continuously&lt;br /&gt;
while True:&lt;br /&gt;
    # Read 16-bit value (0-65535)&lt;br /&gt;
    raw = photoresistor.read_u16()&lt;br /&gt;
&lt;br /&gt;
    # Convert to voltage (3.3V reference)&lt;br /&gt;
    voltage = raw * 3.3 / 65535&lt;br /&gt;
&lt;br /&gt;
    # Convert to percentage (0-100%)&lt;br /&gt;
    percentage = raw / 655.35&lt;br /&gt;
&lt;br /&gt;
    print(f&amp;quot;Raw: {raw:5d}  Voltage: {voltage:.2f}V  Brightness: {percentage:.1f}%&amp;quot;)&lt;br /&gt;
    time.sleep(0.1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ADC Reference Voltage ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Reference voltage&amp;#039;&amp;#039;&amp;#039; is the maximum voltage the ADC can read:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Raspberry Pi Pico&amp;#039;&amp;#039;&amp;#039; - 3.3V reference (ADC input max 3.3V)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;ESP32&amp;#039;&amp;#039;&amp;#039; - Configurable (0-3.3V typical, can be attenuated to read higher voltages)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Arduino Uno&amp;#039;&amp;#039;&amp;#039; - 5V reference (ADC input max 5V)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Critical warning:&amp;#039;&amp;#039;&amp;#039; Exceeding ADC input voltage can damage your microcontroller!&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Voltage divider for higher voltages:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
To measure a 9V battery with a 3.3V ADC:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    +9V Battery&lt;br /&gt;
      |&lt;br /&gt;
     [20kΩ]  ← R1&lt;br /&gt;
      |&lt;br /&gt;
      +------- ADC Input (Vout = 3V when battery = 9V)&lt;br /&gt;
      |&lt;br /&gt;
     [10kΩ]  ← R2&lt;br /&gt;
      |&lt;br /&gt;
     GND&lt;br /&gt;
&lt;br /&gt;
Vout = Vin × R2 / (R1 + R2) = 9V × 10kΩ / 30kΩ = 3V&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Read battery voltage (9V nominal, divided by 3)&lt;br /&gt;
adc = ADC(26)&lt;br /&gt;
raw = adc.read_u16()&lt;br /&gt;
divided_voltage = raw * 3.3 / 65535&lt;br /&gt;
actual_voltage = divided_voltage * 3  # Multiply by divider ratio&lt;br /&gt;
print(f&amp;quot;Battery: {actual_voltage:.2f}V&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ADC Noise and Averaging ===&lt;br /&gt;
&lt;br /&gt;
ADC readings contain noise. &amp;#039;&amp;#039;&amp;#039;Averaging&amp;#039;&amp;#039;&amp;#039; multiple readings improves stability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def read_adc_average(adc, samples=10):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read ADC and return average of multiple samples&lt;br /&gt;
&lt;br /&gt;
    Args:&lt;br /&gt;
        adc: ADC object&lt;br /&gt;
        samples: Number of samples to average&lt;br /&gt;
&lt;br /&gt;
    Returns:&lt;br /&gt;
        Average ADC value (0-65535)&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    total = 0&lt;br /&gt;
    for _ in range(samples):&lt;br /&gt;
        total += adc.read_u16()&lt;br /&gt;
    return total // samples&lt;br /&gt;
&lt;br /&gt;
# Usage&lt;br /&gt;
adc = ADC(26)&lt;br /&gt;
averaged_value = read_adc_average(adc, samples=20)&lt;br /&gt;
print(f&amp;quot;Averaged ADC: {averaged_value}&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Optical Encoders (SimpleBot Example) ===&lt;br /&gt;
&lt;br /&gt;
SimpleBot uses &amp;#039;&amp;#039;&amp;#039;optical encoders&amp;#039;&amp;#039;&amp;#039; to measure wheel rotation:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Slotted wheel&amp;#039;&amp;#039;&amp;#039; attached to motor shaft&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;LED&amp;#039;&amp;#039;&amp;#039; shines through slots&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Photoresistor&amp;#039;&amp;#039;&amp;#039; detects light pulses&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Count pulses&amp;#039;&amp;#039;&amp;#039; to measure distance&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import ADC&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# Configure optical encoder on ADC0&lt;br /&gt;
encoder = ADC(26)&lt;br /&gt;
&lt;br /&gt;
# Threshold for detecting slot vs spoke&lt;br /&gt;
THRESHOLD = 30000  # Tune based on your setup&lt;br /&gt;
&lt;br /&gt;
# Track state&lt;br /&gt;
pulse_count = 0&lt;br /&gt;
prev_state = False&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Read sensor&lt;br /&gt;
    value = encoder.read_u16()&lt;br /&gt;
&lt;br /&gt;
    # Detect light (slot) or dark (spoke)&lt;br /&gt;
    current_state = (value &amp;gt; THRESHOLD)&lt;br /&gt;
&lt;br /&gt;
    # Count rising edges (transition from dark to light)&lt;br /&gt;
    if current_state and not prev_state:&lt;br /&gt;
        pulse_count += 1&lt;br /&gt;
        print(f&amp;quot;Pulse count: {pulse_count}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    prev_state = current_state&lt;br /&gt;
    time.sleep(0.01)  # Poll at 100 Hz&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Better approach:&amp;#039;&amp;#039;&amp;#039; Use interrupts for faster, more reliable pulse counting (see advanced section).&lt;br /&gt;
&lt;br /&gt;
== Part 5: SPI Communication ==&lt;br /&gt;
&lt;br /&gt;
=== What is SPI? ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;SPI (Serial Peripheral Interface)&amp;#039;&amp;#039;&amp;#039; is a four-wire communication protocol:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;MOSI&amp;#039;&amp;#039;&amp;#039; - Master Out, Slave In (data from controller to device)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;MISO&amp;#039;&amp;#039;&amp;#039; - Master In, Slave Out (data from device to controller)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;SCK&amp;#039;&amp;#039;&amp;#039; - Serial Clock (clock signal from master)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;CS&amp;#039;&amp;#039;&amp;#039; - Chip Select (one per device, active LOW)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Characteristics:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Full-duplex (simultaneous send and receive)&lt;br /&gt;
* High speed (MHz range, faster than I2C)&lt;br /&gt;
* No addressing (use CS pin to select device)&lt;br /&gt;
* Requires one CS pin per device&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Common SPI devices:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* SD cards&lt;br /&gt;
* TFT displays&lt;br /&gt;
* LoRa radio modules&lt;br /&gt;
* Flash memory chips&lt;br /&gt;
* ADC/DAC chips&lt;br /&gt;
&lt;br /&gt;
=== Wiring SPI Devices ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Raspberry Pi Pico → SPI Device:&lt;br /&gt;
- GP18 (SPI0 SCK) → SCK&lt;br /&gt;
- GP19 (SPI0 MOSI) → MOSI&lt;br /&gt;
- GP16 (SPI0 MISO) → MISO&lt;br /&gt;
- GP17 (any GPIO) → CS&lt;br /&gt;
- 3.3V → VCC&lt;br /&gt;
- GND → GND&lt;br /&gt;
&lt;br /&gt;
Multiple devices:&lt;br /&gt;
- Share SCK, MOSI, MISO&lt;br /&gt;
- Each device gets unique CS pin&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SPI Code Example: Reading an ID Register ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import SPI, Pin&lt;br /&gt;
&lt;br /&gt;
# Initialize SPI bus&lt;br /&gt;
spi = SPI(0, baudrate=1000000, polarity=0, phase=0,&lt;br /&gt;
          sck=Pin(18), mosi=Pin(19), miso=Pin(16))&lt;br /&gt;
&lt;br /&gt;
# Chip select pin&lt;br /&gt;
cs = Pin(17, Pin.OUT)&lt;br /&gt;
cs.value(1)  # Deselect (CS is active LOW)&lt;br /&gt;
&lt;br /&gt;
def read_device_id(register_addr):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read device ID register via SPI&lt;br /&gt;
&lt;br /&gt;
    Args:&lt;br /&gt;
        register_addr: Register address to read&lt;br /&gt;
&lt;br /&gt;
    Returns:&lt;br /&gt;
        Register value&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    cs.value(0)  # Select device&lt;br /&gt;
&lt;br /&gt;
    # Write register address&lt;br /&gt;
    spi.write(bytearray([register_addr]))&lt;br /&gt;
&lt;br /&gt;
    # Read response&lt;br /&gt;
    response = spi.read(1)&lt;br /&gt;
&lt;br /&gt;
    cs.value(1)  # Deselect device&lt;br /&gt;
&lt;br /&gt;
    return response[0]&lt;br /&gt;
&lt;br /&gt;
# Example: Read ID register at 0x0F (common for many sensors)&lt;br /&gt;
device_id = read_device_id(0x0F)&lt;br /&gt;
print(f&amp;quot;Device ID: 0x{device_id:02X}&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SPI vs I2C Comparison ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Feature !! I2C !! SPI&lt;br /&gt;
|-&lt;br /&gt;
| Wires || 2 (SDA, SCL) || 4 (MOSI, MISO, SCK, CS)&lt;br /&gt;
|-&lt;br /&gt;
| Speed || 100-400 kHz typical || 1-10 MHz typical&lt;br /&gt;
|-&lt;br /&gt;
| Addressing || 7-bit address || Chip Select pin&lt;br /&gt;
|-&lt;br /&gt;
| Multiple devices || Easy (shared bus) || Requires one CS pin per device&lt;br /&gt;
|-&lt;br /&gt;
| Complexity || Moderate || Simple hardware, more wiring&lt;br /&gt;
|-&lt;br /&gt;
| Power || Lower || Higher (faster switching)&lt;br /&gt;
|-&lt;br /&gt;
| Distance || &amp;lt;1m typical || &amp;lt;30cm typical&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When to use I2C:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Multiple sensors with unique addresses&lt;br /&gt;
* Moderate data rates&lt;br /&gt;
* Fewer available GPIO pins&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When to use SPI:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* High-speed data (displays, SD cards)&lt;br /&gt;
* Full-duplex communication needed&lt;br /&gt;
* Plenty of GPIO pins available&lt;br /&gt;
&lt;br /&gt;
== Part 6: Debugging Sensor Communication ==&lt;br /&gt;
&lt;br /&gt;
=== Logic Analyzer ===&lt;br /&gt;
&lt;br /&gt;
A &amp;#039;&amp;#039;&amp;#039;logic analyzer&amp;#039;&amp;#039;&amp;#039; captures and displays digital signals:&lt;br /&gt;
&lt;br /&gt;
* Shows timing of SDA, SCL, MOSI, MISO, etc.&lt;br /&gt;
* Decodes I2C, SPI, UART protocols automatically&lt;br /&gt;
* Essential for debugging communication issues&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Affordable options:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Saleae Logic clone&amp;#039;&amp;#039;&amp;#039; ($10-20) - 8 channels, USB&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;DSLogic Plus&amp;#039;&amp;#039;&amp;#039; ($100) - 16 channels, high speed&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Oscilloscope with protocol decode&amp;#039;&amp;#039;&amp;#039; ($200+) - Shows analog waveforms too&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;How to use:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
1. Connect logic analyzer probes to I2C/SPI lines (SDA, SCL, etc.)&lt;br /&gt;
2. Connect ground to circuit ground&lt;br /&gt;
3. Capture communication sequence&lt;br /&gt;
4. Analyze timing, decode data, identify errors&lt;br /&gt;
&lt;br /&gt;
=== I2C Debugging Checklist ===&lt;br /&gt;
&lt;br /&gt;
If I2C communication fails:&lt;br /&gt;
&lt;br /&gt;
1. &amp;#039;&amp;#039;&amp;#039;Scan for devices&amp;#039;&amp;#039;&amp;#039; - Does `i2c.scan()` find your sensor?&lt;br /&gt;
2. &amp;#039;&amp;#039;&amp;#039;Check address&amp;#039;&amp;#039;&amp;#039; - Is device address correct? (Some have alternate addresses)&lt;br /&gt;
3. &amp;#039;&amp;#039;&amp;#039;Verify wiring&amp;#039;&amp;#039;&amp;#039; - SDA, SCL, GND, VCC all connected?&lt;br /&gt;
4. &amp;#039;&amp;#039;&amp;#039;Pull-up resistors&amp;#039;&amp;#039;&amp;#039; - 4.7kΩ on SDA and SCL?&lt;br /&gt;
5. &amp;#039;&amp;#039;&amp;#039;Power supply&amp;#039;&amp;#039;&amp;#039; - Is sensor getting correct voltage (3.3V or 5V)?&lt;br /&gt;
6. &amp;#039;&amp;#039;&amp;#039;Clock speed&amp;#039;&amp;#039;&amp;#039; - Try lower frequency (100 kHz instead of 400 kHz)&lt;br /&gt;
7. &amp;#039;&amp;#039;&amp;#039;Multiple masters&amp;#039;&amp;#039;&amp;#039; - Only one device should control I2C bus&lt;br /&gt;
8. &amp;#039;&amp;#039;&amp;#039;Cable length&amp;#039;&amp;#039;&amp;#039; - Keep I2C wires short (&amp;lt;30cm preferred)&lt;br /&gt;
&lt;br /&gt;
=== Common Sensor Problems ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Sensor returns garbage data:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Incorrect data format (check datasheet for byte order, signed/unsigned)&lt;br /&gt;
* Wrong register address&lt;br /&gt;
* Sensor needs initialization sequence&lt;br /&gt;
* Data needs calibration or offset correction&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Sensor works then stops:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Power supply voltage sag (motors drawing too much current)&lt;br /&gt;
* Loose connection (check breadboard contacts)&lt;br /&gt;
* Thermal shutdown (sensor overheating)&lt;br /&gt;
* EMI from motors (add capacitors, shield wires)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Readings are noisy or fluctuate:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* ADC noise (use averaging, add filter capacitor)&lt;br /&gt;
* EMI from motors (route sensor wires away from motor wires)&lt;br /&gt;
* Poor ground connection (ensure solid GND)&lt;br /&gt;
* Floating inputs (add pull-up/pull-down resistors)&lt;br /&gt;
&lt;br /&gt;
=== Multimeter Debugging ===&lt;br /&gt;
&lt;br /&gt;
Use a multimeter to check:&lt;br /&gt;
&lt;br /&gt;
1. &amp;#039;&amp;#039;&amp;#039;Power supply&amp;#039;&amp;#039;&amp;#039; - Measure voltage at sensor VCC pin (should be 3.3V or 5V)&lt;br /&gt;
2. &amp;#039;&amp;#039;&amp;#039;Ground continuity&amp;#039;&amp;#039;&amp;#039; - Check continuity between sensor GND and microcontroller GND&lt;br /&gt;
3. &amp;#039;&amp;#039;&amp;#039;Signal levels&amp;#039;&amp;#039;&amp;#039; - Measure SDA/SCL when idle (should be pulled up to 3.3V/5V)&lt;br /&gt;
4. &amp;#039;&amp;#039;&amp;#039;Analog sensor output&amp;#039;&amp;#039;&amp;#039; - Measure voltage at ADC pin (should vary with sensor input)&lt;br /&gt;
&lt;br /&gt;
== Part 7: Advanced Sensor Techniques ==&lt;br /&gt;
&lt;br /&gt;
=== Interrupts for Fast Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Polling&amp;#039;&amp;#039;&amp;#039; (checking sensor in loop) is slow and wastes CPU time. &amp;#039;&amp;#039;&amp;#039;Interrupts&amp;#039;&amp;#039;&amp;#039; trigger code immediately when signal changes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import Pin&lt;br /&gt;
&lt;br /&gt;
# Global counter&lt;br /&gt;
pulse_count = 0&lt;br /&gt;
&lt;br /&gt;
def encoder_interrupt(pin):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Called when encoder signal changes&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    global pulse_count&lt;br /&gt;
    pulse_count += 1&lt;br /&gt;
&lt;br /&gt;
# Configure encoder pin with interrupt&lt;br /&gt;
encoder_pin = Pin(10, Pin.IN, Pin.PULL_UP)&lt;br /&gt;
encoder_pin.irq(trigger=Pin.IRQ_RISING, handler=encoder_interrupt)&lt;br /&gt;
&lt;br /&gt;
# Main loop can do other things&lt;br /&gt;
while True:&lt;br /&gt;
    print(f&amp;quot;Pulses: {pulse_count}&amp;quot;)&lt;br /&gt;
    time.sleep(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When to use interrupts:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* High-speed encoders (&amp;gt;100 Hz)&lt;br /&gt;
* Critical timing (missing a pulse matters)&lt;br /&gt;
* Freeing CPU for other tasks&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When NOT to use interrupts:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Slow sensors (&amp;lt;10 Hz) - polling is simpler&lt;br /&gt;
* Complex processing needed - interrupts should be fast&lt;br /&gt;
* Debugging (harder to troubleshoot)&lt;br /&gt;
&lt;br /&gt;
=== Kalman Filters for Sensor Fusion ===&lt;br /&gt;
&lt;br /&gt;
Combine multiple sensors for better accuracy:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;IMU + Encoders&amp;#039;&amp;#039;&amp;#039; - Accurate position and orientation&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Camera + Distance sensor&amp;#039;&amp;#039;&amp;#039; - 3D object localization&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;GPS + IMU&amp;#039;&amp;#039;&amp;#039; - Precise outdoor navigation&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Kalman filter&amp;#039;&amp;#039;&amp;#039; is an algorithm that optimally combines sensor data, accounting for noise and uncertainty. This is an advanced topic beyond this tutorial.&lt;br /&gt;
&lt;br /&gt;
=== Low-Pass Filters for Noisy Sensors ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Low-pass filter&amp;#039;&amp;#039;&amp;#039; smooths noisy analog readings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class LowPassFilter:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Simple exponential moving average low-pass filter&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, alpha=0.1):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Initialize filter&lt;br /&gt;
&lt;br /&gt;
        Args:&lt;br /&gt;
            alpha: Smoothing factor (0-1). Lower = smoother but slower.&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.alpha = alpha&lt;br /&gt;
        self.value = None&lt;br /&gt;
&lt;br /&gt;
    def update(self, new_value):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Update filter with new reading&lt;br /&gt;
&lt;br /&gt;
        Args:&lt;br /&gt;
            new_value: Latest sensor reading&lt;br /&gt;
&lt;br /&gt;
        Returns:&lt;br /&gt;
            Filtered value&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if self.value is None:&lt;br /&gt;
            self.value = new_value&lt;br /&gt;
        else:&lt;br /&gt;
            self.value = self.alpha * new_value + (1 - self.alpha) * self.value&lt;br /&gt;
        return self.value&lt;br /&gt;
&lt;br /&gt;
# Usage&lt;br /&gt;
filter = LowPassFilter(alpha=0.2)&lt;br /&gt;
adc = ADC(26)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    raw = adc.read_u16()&lt;br /&gt;
    filtered = filter.update(raw)&lt;br /&gt;
    print(f&amp;quot;Raw: {raw:5d}  Filtered: {filtered:5.0f}&amp;quot;)&lt;br /&gt;
    time.sleep(0.1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alpha parameter:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;alpha = 1.0&amp;#039;&amp;#039;&amp;#039; - No filtering (output = input)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;alpha = 0.5&amp;#039;&amp;#039;&amp;#039; - Moderate filtering&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;alpha = 0.1&amp;#039;&amp;#039;&amp;#039; - Heavy filtering (smooth but slow response)&lt;br /&gt;
&lt;br /&gt;
=== Sensor Calibration ===&lt;br /&gt;
&lt;br /&gt;
Many sensors need calibration to convert raw values to real-world units:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example: Calibrating a line sensor threshold&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from machine import Pin&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# Configure sensor&lt;br /&gt;
sensor = Pin(10, Pin.IN, Pin.PULL_UP)&lt;br /&gt;
&lt;br /&gt;
# Calibration procedure&lt;br /&gt;
print(&amp;quot;Calibration: Place sensor on WHITE surface&amp;quot;)&lt;br /&gt;
time.sleep(3)&lt;br /&gt;
white_count = 0&lt;br /&gt;
for _ in range(100):&lt;br /&gt;
    if sensor.value():&lt;br /&gt;
        white_count += 1&lt;br /&gt;
    time.sleep(0.01)&lt;br /&gt;
white_confidence = white_count / 100&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;White confidence: {white_confidence:.2f}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;Calibration: Place sensor on BLACK surface&amp;quot;)&lt;br /&gt;
time.sleep(3)&lt;br /&gt;
black_count = 0&lt;br /&gt;
for _ in range(100):&lt;br /&gt;
    if not sensor.value():&lt;br /&gt;
        black_count += 1&lt;br /&gt;
    time.sleep(0.01)&lt;br /&gt;
black_confidence = black_count / 100&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;Black confidence: {black_confidence:.2f}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Use calibrated thresholds&lt;br /&gt;
if white_confidence &amp;gt; 0.8 and black_confidence &amp;gt; 0.8:&lt;br /&gt;
    print(&amp;quot;Calibration successful!&amp;quot;)&lt;br /&gt;
else:&lt;br /&gt;
    print(&amp;quot;Calibration failed - adjust potentiometer&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Part 8: Practical Skills Checklist ==&lt;br /&gt;
&lt;br /&gt;
By now, you should be able to:&lt;br /&gt;
&lt;br /&gt;
* ☐ Explain the difference between digital and analog sensors&lt;br /&gt;
* ☐ Configure pull-up/pull-down resistors for digital inputs&lt;br /&gt;
* ☐ Understand active HIGH vs active LOW logic&lt;br /&gt;
* ☐ Wire and read an I2C sensor (IMU, distance sensor)&lt;br /&gt;
* ☐ Scan for I2C devices and identify addresses&lt;br /&gt;
* ☐ Read analog sensors using ADC&lt;br /&gt;
* ☐ Create voltage dividers for resistive sensors&lt;br /&gt;
* ☐ Understand SPI communication basics&lt;br /&gt;
* ☐ Debug sensor communication issues with multimeter&lt;br /&gt;
* ☐ Implement averaging and filtering for noisy sensors&lt;br /&gt;
* ☐ Use interrupts for fast sensors (encoders)&lt;br /&gt;
&lt;br /&gt;
If you can check most of these boxes, you&amp;#039;re ready to add sensors to your robots!&lt;br /&gt;
&lt;br /&gt;
== Part 9: Sensor Selection Guide ==&lt;br /&gt;
&lt;br /&gt;
=== Choosing the Right Sensor ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Sensor Type !! Use Case !! Interface !! Cost !! Complexity&lt;br /&gt;
|-&lt;br /&gt;
| IR Line Detector || Line following || Digital || $0.50 || Beginner&lt;br /&gt;
|-&lt;br /&gt;
| Ultrasonic (HC-SR04) || Obstacle avoidance || Digital (trigger/echo) || $1-2 || Beginner&lt;br /&gt;
|-&lt;br /&gt;
| MPU6050 IMU || Tilt, acceleration, rotation || I2C || $2-5 || Intermediate&lt;br /&gt;
|-&lt;br /&gt;
| VL53L0X Distance || Precise distance (2m) || I2C || $3-8 || Intermediate&lt;br /&gt;
|-&lt;br /&gt;
| Optical Encoder || Wheel rotation, odometry || Analog or Digital || $1-3 || Intermediate&lt;br /&gt;
|-&lt;br /&gt;
| Camera (OV7670) || Vision, object detection || Complex (parallel/SPI) || $5-15 || Advanced&lt;br /&gt;
|-&lt;br /&gt;
| LIDAR (RPLidar) || 360° mapping || UART || $100+ || Advanced&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Sensor Interface Summary ===&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Digital (GPIO)&amp;#039;&amp;#039;&amp;#039; - Simplest, on/off sensors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Analog (ADC)&amp;#039;&amp;#039;&amp;#039; - Variable sensors, requires voltage divider for resistive sensors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;I2C&amp;#039;&amp;#039;&amp;#039; - Most common for IMU, distance, environmental sensors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;SPI&amp;#039;&amp;#039;&amp;#039; - High-speed displays, SD cards, some sensors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;UART&amp;#039;&amp;#039;&amp;#039; - GPS modules, some distance sensors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PWM&amp;#039;&amp;#039;&amp;#039; - Servo motors, some ultrasonic sensors (output)&lt;br /&gt;
&lt;br /&gt;
== Next Steps ==&lt;br /&gt;
&lt;br /&gt;
=== Add Sensors to Your Robot ===&lt;br /&gt;
* [[Capability:IMU Sensing]] - Add MPU6050 for tilt and rotation detection&lt;br /&gt;
* [[Capability:Time-of-Flight Sensing]] - Add VL53L0X for obstacle detection&lt;br /&gt;
* [[Capability:Encoder Sensing]] - Add quadrature encoders for precise odometry&lt;br /&gt;
* [[Capability:Ultrasonic Sensing]] - Add HC-SR04 for distance measurement&lt;br /&gt;
&lt;br /&gt;
=== Build Advanced Projects ===&lt;br /&gt;
* [[SimpleBot]] - Build a robot that uses multiple sensor types&lt;br /&gt;
* [[Activity:Maze Solving]] - Use distance sensors to navigate mazes&lt;br /&gt;
* Create a self-balancing robot using IMU feedback&lt;br /&gt;
&lt;br /&gt;
=== Learn More Electronics ===&lt;br /&gt;
* [[Motor Control Basics]] - Control actuators based on sensor feedback&lt;br /&gt;
* [[Electronics]] - Full electronics competency overview&lt;br /&gt;
* [[MicroPython Programming]] - Advanced sensor data processing&lt;br /&gt;
&lt;br /&gt;
== Common Mistakes and Pitfalls ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Missing pull-up resistors&amp;#039;&amp;#039;&amp;#039; - I2C won&amp;#039;t work without pull-ups on SDA/SCL&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Floating inputs&amp;#039;&amp;#039;&amp;#039; - Digital inputs without pull-up/pull-down give random readings&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Wrong voltage&amp;#039;&amp;#039;&amp;#039; - Exceeding ADC input voltage (3.3V on Pico) damages microcontroller&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Incorrect I2C address&amp;#039;&amp;#039;&amp;#039; - Check datasheet, some sensors have alternate addresses&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Mixed logic levels&amp;#039;&amp;#039;&amp;#039; - 5V sensor output can damage 3.3V microcontroller (use level shifter)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Long I2C wires&amp;#039;&amp;#039;&amp;#039; - Keep I2C wires short (&amp;lt;30cm) to avoid communication errors&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;No common ground&amp;#039;&amp;#039;&amp;#039; - Sensor GND must connect to microcontroller GND&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Polling too slowly&amp;#039;&amp;#039;&amp;#039; - Fast sensors (encoders) may miss pulses if polling is too slow&lt;br /&gt;
&lt;br /&gt;
== Resources and Further Reading ==&lt;br /&gt;
&lt;br /&gt;
=== BRS Wiki Pages ===&lt;br /&gt;
* [[Electronics Fundamentals]] - Prerequisite tutorial&lt;br /&gt;
* [[Motor Control Basics]] - Combine sensors with motors&lt;br /&gt;
* [[Capability:IMU Sensing]] - MPU6050 usage in robots&lt;br /&gt;
* [[Capability:Optical Odometry]] - Encoder implementation&lt;br /&gt;
* [[Infrared Line Detector]] - Line sensor details&lt;br /&gt;
&lt;br /&gt;
=== External Resources ===&lt;br /&gt;
* [https://learn.sparkfun.com/tutorials/i2c SparkFun I2C Tutorial]&lt;br /&gt;
* [https://learn.sparkfun.com/tutorials/analog-to-digital-conversion SparkFun ADC Tutorial]&lt;br /&gt;
* [https://learn.adafruit.com/working-with-i2c-devices Adafruit I2C Guide]&lt;br /&gt;
* [https://www.nxp.com/docs/en/user-guide/UM10204.pdf I2C Specification] (PDF, official)&lt;br /&gt;
&lt;br /&gt;
=== Datasheets ===&lt;br /&gt;
Reading datasheets is essential for sensor interfacing:&lt;br /&gt;
* Sensor address and register map&lt;br /&gt;
* Timing requirements&lt;br /&gt;
* Power supply specifications&lt;br /&gt;
* Communication protocol details&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Electronics Fundamentals]] - Prerequisite tutorial&lt;br /&gt;
* [[Motor Control Basics]] - Actuator control&lt;br /&gt;
* [[MicroPython Programming]] - Advanced sensor programming&lt;br /&gt;
* [[Electronics]] - Full electronics competency overview&lt;br /&gt;
* [[Capabilities]] - Hardware abilities unlocked by sensors&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
[[Category:Electronics]]&lt;br /&gt;
[[Category:Intermediate]]&lt;/div&gt;</summary>
		<author><name>John</name></author>
	</entry>
</feed>