Reading Sensors Using the ESP32-Bit-Pirate I2C Interface: A Complete Guide

The ESP32-Bit-Pirate firmware provides a terminal-driven I2C toolbox that lets you scan, identify, read, and write any I2C-compatible sensor without writing low-level driver code.

Reading sensors via I2C on the ESP32-Bit-Pirate involves using the built-in I2cController class to execute interactive commands like scan, read, and identify. The firmware abstracts the ESP32's I2C hardware into a service-oriented architecture that handles address validation, register operations, and formatted output automatically.

How the I2C System Is Architected

The ESP32-Bit-Pirate organizes its I2C functionality into four distinct layers, as implemented in the geo-tp/ESP32-Bit-Pirate repository:

Layer Responsibility Key Source File
Command dispatcher Routes the i2c keyword to the controller src/Dispatchers/ActionDispatcher.cpp
Controller Parses sub-commands and orchestrates workflows src/Controllers/I2cController.cpp
Service layer Provides I2C primitives (beginTransmission, requestFrom) and advanced features src/Services/I2cService.h
Utility helpers Argument parsing, user input, and terminal formatting Various files under src/Utilities/

The controller uses a command-per-function pattern: each public method (handleRead, handleWrite, handleScan, handleDump, etc.) corresponds to one terminal sub-command. All commands follow a consistent four-step flow:

  1. Validate the address — tryParseAddress (lines 212–233 in I2cController.cpp) ensures 7-bit address format (0x00–0x7F)
  2. Check device presence — i2cService.beginTransmission(addr) followed by endTransmission() confirms the target ACKs
  3. Execute the operation — delegate to II2cService methods; for reads, write the register pointer then call requestFrom
  4. Format output — use argTransformer.toHex and terminalView.println for human-readable results

Essential I2C Commands for Sensor Reading

Configure Bus Settings (One-Time Setup)

Before reading any sensor, configure the SDA pin, SCL pin, and clock frequency:

> i2c config
Enter SDA pin: 21
Enter SCL pin: 22
Enter frequency (Hz) [100000]: 400000

This persists for the session and affects all subsequent I2C operations.

Scan for Connected Devices

The scan command enumerates all responsive addresses on the bus:

> i2c scan
I2C Scan: Scanning I2C bus... Press [ENTER] to stop

Found device at 0x40
Found device at 0x5A
...

Press Enter to abort the scan early. The implementation (lines 70–94 in I2cController.cpp) tests each address with a zero-byte write and reports ACK responses.

Identify a Specific Sensor

Once you know an address, use identify to match it against known device signatures:

> i2c identify 0x40
📟 I2C 0x40 Identification Result
  ➤ Could be: - [SENSOR] Temperature/Humidity (e.g., SHT31)

The identifyToString method maps addresses to friendly names based on a built-in database of common sensors.

Reading and Writing Sensor Registers

Read a Single Register

The most common sensor operation—write a register pointer, then read the data:

> i2c read 0x40 0x01
I2C Read: 0x5A (90) from reg 0x01 @ dev 0x40.

What happens under the hood: The handleRead method (lines 42–89) performs a write-then-read sequence: it calls i2cService.beginTransmission(0x40), writes 0x01 as the register address, restarts with requestFrom(0x40, 1), and returns the byte in both hex and decimal formats.

Write to a Configuration Register

Configure sensor settings by writing to control registers:

> i2c write 0x40 0x02 0x03
I2C Write: 0x03 -> reg 0x02 @ dev 0x40.

The handleWrite method validates the address, writes the register pointer followed by the data byte, and confirms successful transmission.

Dump an Entire Register Map

For reverse engineering unknown sensors or verifying configuration:

> i2c dump 0x40 64
I2C Dump: 0x40 from 0x00 for 64 bytes...
00:  3F  A2  ??  ??  7C  00  12  FF  .?.?.|...
08:  00  00  81  3E  ??  ??  00  00 ....?.>....
...

The handleDump command first attempts a register-style read; if the device doesn't support register pointers, it automatically falls back to performRawRead (raw sequential reads).

Advanced Diagnostics and Debugging

Health Check a Device

Verify signal integrity and bus timing for problematic sensors:

> i2c health 0x40
I2C Health: Analyzing @ 0x40...
[ACK latency (ping)]
...
✅ Stable ACK
✅ Stable reads

The handleHealth method uses the service layer's Stats helper to measure ACK latency, read stability, and bus error rates across multiple iterations.

Low-Level Bus Recovery

If a sensor locks the bus (SDA or SCL stuck low), trigger the bit-bang recovery routine:

> i2c recover

This invokes i2cBitBangRecoverBus from the I2cService layer, which manually toggles SCL until SDA releases.

Working with I2C EEPROM and Specialized Devices

For memory devices like 24Cxx EEPROMs, use the dedicated shell that reuses the controller's API:

> i2c eeprom
[I2C EEPROM Shell]
eeprom> read 0x50 0x0000 16
00: 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 FF FF FF  Hello World!....

The I2cEepromShell class (src/Shells/I2cEepromShell.h) wraps I2cController with page-aware read/write logic and address size handling (8-bit vs 16-bit device addresses).

Key Source Files for Reference

File Purpose
src/Controllers/I2cController.cpp Implements all I2C commands: scan, read, write, dump, health, identify, jam, glitch
src/Controllers/I2cController.h Class declaration with handleCommand entry point and sub-command handlers
src/Services/I2cService.h Abstract II2cService interface exposing beginTransmission, requestFrom, sniff, stats, and bit-bang recovery
src/Dispatchers/ActionDispatcher.cpp Routes terminal input starting with "i2c" to I2cController::handleCommand
src/Utilities/ArgTransformer.cpp Parses decimal/hex arguments and formats hex output strings
src/Utilities/UserInputManager.cpp Handles interactive prompts and abort-on-Enter functionality

Summary

  • Reading sensors using the ESP32-Bit-Pirate I2C interface requires no driver coding—use interactive terminal commands instead
  • The four-layer architecture (dispatcher → controller → service → utilities) separates concerns and enables extensibility
  • Core workflow: configure pins → scan → identify → read/write registers → optionally dump or health-check
  • All commands support abort-on-Enter for long-running operations like scans or sniffers
  • Advanced features include bus recovery, glitching, jamming, and EEPROM shells for specialized devices

Frequently Asked Questions

How do I find the I2C address of an unknown sensor?

Use the i2c scan command to enumerate all responsive devices. Disconnect your suspected sensor, run a scan, reconnect it, and scan again—the new address is your target. According to the I2cController.cpp source (lines 70–94), the scan tests addresses 0x03 through 0x77 with zero-byte transmissions.

Can I read multiple bytes at once from a sensor register?

Yes. The i2c read command accepts an optional length parameter: i2c read <addr> <reg> <len>. The controller repeatedly calls requestFrom and buffers results, formatting them as space-separated hex values with ASCII preview.

What happens if a sensor doesn't use register pointers?

Use i2c rawread <addr> <len> or the dump command—the handleDump implementation automatically falls back to performRawRead when register-style access fails. This reads sequential bytes without first writing a register address, compatible with simple I2C devices like some ADCs or port expanders.

Why does my sensor read return ?? in dump output?

The ?? placeholder indicates a read failure (NAK during requestFrom). Check wiring, pull-up resistors, and bus frequency—i2c health <addr> diagnoses ACK timing and stability issues. The controller marks failed reads rather than stopping, so you can see partial results from problematic devices.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →