Exercise A: I2C Bus Scan

~20 min

Before you can talk to an I2C device, you need to know its address. Let's scan the bus and see who's out there.

Starting Point

Open the examples/i2c-scan/ project in the workshop repo. This is a working I2C bus scanner.

Read through src/main.rs. Map it to Create → Configure → Control:

#![allow(unused)]
fn main() {
// CREATE: instantiate the I2C controller
let i2c = I2c::new(
    peripherals.I2C0,
    Config::default(),      // ← What does default give us?
)
.with_sda(peripherals.GPIO?)  // ← Which pin?
.with_scl(peripherals.GPIO?); // ← Which pin?

// CONTROL: scan all possible addresses
for addr in 0x01..=0x7F {
    if i2c.write(addr, &[]).is_ok() {
        // Device found at this address!
    }
}
}

Your Task

Step 1: Adapt the Pins

Check your uFerris pinout card:

  • Which GPIO is SDA?
  • Which GPIO is SCL?

Update the example with the correct pins.

Step 2: Run the Scanner

cd examples/i2c-scan
cargo build --release
espflash flash target/riscv32imc-unknown-none-elf/release/i2c-scan --monitor

The scanner should print the addresses of all devices it finds. Write them down — you'll need them in the next exercise.

Common I2C addresses on embedded dev boards:
- `0x68` or `0x69` — IMU / accelerometer
- `0x44` or `0x70` — temperature/humidity sensor
- `0x3C` or `0x3D` — OLED display

Cross-reference with your uFerris board documentation to identify each device.

Step 3: Explore the Configuration

Now look at the docs:

Open [`esp_hal::i2c::master::Config`](https://docs.espressif.com/projects/rust/esp-hal/latest/esp32c3/esp_hal/i2c/master/struct.Config.html). What can you configure?
  1. Clock frequency: The default is likely 100 kHz (standard mode). Try changing it:

    #![allow(unused)]
    fn main() {
    let config = Config::default()
        .with_frequency(Rate::from_khz(400)); // Fast mode
    }

    Does the scan still work at 400 kHz? What about other speeds?

  2. Timeout: Is there a timeout setting? What happens if you change it?

  3. What does Config::default() actually give you? Look it up in the docs.

esp-hal 1.0 uses a builder pattern for I2C:
```rust
let i2c = I2c::new(peripherals.I2C0, Config::default())
    .with_sda(peripherals.GPIOXX)
    .with_scl(peripherals.GPIOXX);

The .with_sda() and .with_scl() calls are chained after new().


```admonish hint title="Hint: Full scanner" collapsible=true
See `solutions/i2c-configs/src/main.rs` for the complete scanner with uFerris pin assignments and different configurations.