Repository navigation
I2CDeviceScanning
The I2C bus scanning system automatically detects devices connected to the I2C bus, including devices connected through bus multiplexers. Scanning is distinct from polling: scanning detects which devices are present and determines their type, while polling retrieves data from known online devices.
The scanning process is sophisticated, using priority-based address ordering rather than simple linear scanning, and handles complex scenarios including bus stuck conditions, and hot-swapping of devices.
It's important to understand the distinction between these two operations:
| Operation | Purpose | Frequency | Target |
|---|---|---|---|
| Scanning | Detect device presence and identify device type | Variable (fast initially, then slow periodic) | All potential I2C addresses |
| Polling | Retrieve data from known devices | Regular intervals (device-specific) | Only identified, online devices |
Scanning discovers what devices are present; polling retrieves data from those devices.
The scanner operates in different modes that progress through the initialization and steady-state operation:
IDLE
↓
MAIN_BUS_MUX_ONLY (Detect bus multiplexers on main bus)
↓
MAIN_BUS (Scan all addresses on main bus)
↓
SCAN_FAST (Fast scanning of all addresses including slots)
↓
SCAN_SLOW (Periodic slow scanning)
| Mode | Purpose | Duration | Slot Scanning |
|---|---|---|---|
IDLE |
Initial state before scanning starts | Momentary | No |
MAIN_BUS_MUX_ONLY |
Detect bus multiplexers only | Until multiplexers stable | Main bus only |
MAIN_BUS |
Scan main bus for all devices | Until sweep complete | Main bus only |
SCAN_FAST |
Rapid detection of all devices | Until sweep complete | Main bus + all slots |
SCAN_SLOW |
Periodic background scanning | Ongoing (configurable period) | Main bus + all slots |
- IDLE → MAIN_BUS_MUX_ONLY: On initialization or when requested
- MAIN_BUS_MUX_ONLY → MAIN_BUS: After multiplexers detected and stable
- MAIN_BUS → SCAN_FAST: After main bus scan complete
- SCAN_FAST → SCAN_SLOW: After fast scan sweep complete
- Any → MAIN_BUS_MUX_ONLY: When multiplexer state changes (new mux detected or mux online/offline status changes)
Unlike simple linear scanning (0x01, 0x02, 0x03...), the I2C scanner uses priority lists to scan addresses in an intelligent order based on:
- Common device addresses: Addresses that are frequently used by popular I2C devices
- Device type likelihood: Addresses associated with device types registered in the system
-
Boost addresses: high-priority addresses from the
scanBoostconfiguration, and those an identification hook sets at runtime (setScanBoostAddrs())
The lists come from the device type records (each record's scanPriority, built at runtime so records from the device type file count too), plus the boost addresses in the first list:
-
List 0: the primary address of each high-priority record, and the boost addresses (
scanBoostandsetScanBoostAddrs(); a boosted address is scanned in this list only) - List 1: the other addresses of high-priority records, and medium-priority records
- List 2: every other address that something could claim - a device type record identifies or claims it, or it is a multiplexer address
-
Background: every other address, including those only an identification hook handles. They are probed slowly, spread evenly over
backgroundSweepMs(default 10 s for every such address on the main bus and every slot). A device found there is identified as anywhere else - records first, then the hook if it handles the address - or appears as an unidentified element
List 0 is scanned a whole slot position at a time. After each run of list 0, list 1 scans a chunk of (its size / 3) addresses and list 2 a chunk of (its size / 9), fractions carried over. Each list gets the same share of probes as if it were scanned whole every 3 or 9 runs of list 0, but a list-0 address comes round again after one chunk of each lower list rather than after a whole lower list.
An identification hook declares the addresses it handles with setNewDeviceIdentAddrRanges() (a hook that declares none handles every address). Handling an address doesn't make it scanned often: the hook boosts the addresses where it expects its devices with setScanBoostAddrs(), and the rest of its addresses are covered by the background sweep. Robotical's Axiom RSAO support, for example, handles 0x0F and 0x10-0x6F minus its excluded ranges, and boosts 0x0F and its first-choice module addresses: a module there is found in about 2 s, and one elsewhere within the 10 s sweep. (RaftI2C 1.23.0 and later; before that, every address the hook handled was in list 2.)
- Faster detection of common devices (e.g., IMUs, displays, sensors)
- Reduced bus overhead by scanning less common addresses less frequently
-
Customizable through
scanBoostconfiguration for application-specific devices
High-priority addresses can be specified in configuration:
{
"DevMan": {
"scanBoost": ["0x6a", "0x36", "0x70"]
}
}These addresses are added to Priority 0 (highest priority) and will be scanned most frequently.
The scanning system supports I2C bus multiplexers (e.g., TCA9548A, PCA9548A) which allow multiple devices with the same address to coexist on the same bus by connecting them to different multiplexer slots.
- Slot 0: Main I2C bus (no multiplexer)
- Slots 1-N: Multiplexer slots (1-based numbering)
For a system with 2 multiplexers, each with 8 slots:
- Main bus: Slot 0
- Mux 0, Slot 0-7: Slots 1-8
- Mux 1, Slot 0-7: Slots 9-16
Multiplexers are detected during the MAIN_BUS_MUX_ONLY phase:
- Scanner attempts to communicate with addresses in the configured multiplexer range
- Devices that respond and exhibit multiplexer behavior are registered as multiplexers
- Detection requires multiple consecutive successful responses (threshold-based detection to avoid false positives)
- Once detected, the scanner builds a list of available slots for subsequent scanning phases
Each list is scanned on the main bus and every multiplexer slot in a cycle of slot visits, spread evenly through the cycle. How often a slot is visited depends on its activity:
| Slot | Visits per cycle (default) |
|---|---|
| Main bus | 2 |
A slot where something came online or went in the last slotChangedMs (10 s), or whose multiplexer changed |
slotChangedWeight (3) |
| A slot with an online device |
slotKnownWeight (2) |
| Any other slot | 1 |
Every slot is in every cycle, so a quiet slot is still covered; the cycle is the sum of the visits (e.g. 13 for the main bus and 8 slots of which 3 have devices, against 10 with no weighting). devman/busstatus reports scan.slots, slotsKnown, slotsChanged and slotCycle. A slot stays selected for consecutive probes on it (only one slot is ever selected), so the multiplexer isn't rewritten for every probe.
Every muxCheckMs (100 ms) one multiplexer address is probed, in turn, on the slot it is reached through, so a multiplexer that goes (a hub unplugged) or comes is noticed within a few hundred ms. When one goes, the devices on its slots are reported as gone (including devices that aren't polled). After startup, a multiplexer change doesn't restart scanning: its slots are marked as changed (so they are visited most) and a fast scan runs for up to fastScanMaxMs. During startup the multiplexers are scanned again from the start, as before.
A device that stays off the bus until it gets a general call (its record has a generalCall, e.g. the Excelitas CaliPile) is woken by the scanner: in the fast and slow modes it sends the general calls of the enabled records on one slot at a time in turn, a full round of the slots taking generalCallRoundMs (2 s), and the device is then found by the ordinary scan. busstatus counts them (scan.generalCalls). (RaftI2C versions after 1.24.0)
The scanner implements several checks to avoid incorrectly detecting the same device multiple times:
- Main bus devices excluded from slot scans: If a device is detected on the main bus, it won't be reported as present on multiplexer slots (even though it will respond on all slots)
- New slot devices checked on the main bus: a device that answers on a slot but isn't known there is probed on the main bus (slots disabled) first; if it answers there, it is a main-bus device (one that has just come back) and is recorded on the main bus
- Multiplexer address slot matching: Multiplexer addresses are only scanned on their correct slot
- IO Expander exclusion: Defined IO expander addresses are excluded from scanning to avoid conflicts
| Mode | Speed | Purpose |
|---|---|---|
| Fast Scan | As fast as the pass allows (fastScanMaxUnyieldMs per pass) |
After startup or requestScan(fast); ends after its sweeps or fastScanMaxMs (default 2 s) |
| Slow Scan | A share of the bus | Steady state: finding devices that are plugged in or removed |
Slow scanning uses a share of bus time that falls as polling needs more: scanShareMaxPct (default 30%) on a quiet bus, reduced in proportion to the poll load down to scanShareMinPct (default 10%), so a busy bus keeps time for polls and a quiet one finds devices quickly. The time is earned continuously and spent by each pass of the bus task (at most slowScanMaxUnyieldMs per pass, and not more often than every busScanPeriodMs). devman/busstatus reports the share in use (scan.sharePct).
Coming online: an address must answer twice before it is online. After the first answer it is probed again on the next pass (a few ms later) rather than when the scan comes round again, so a new device is online within a few ms of being seen, and identified straight after.
{
"busScanPeriodMs": 5,
"scanShareMinPct": 10,
"scanShareMaxPct": 30,
"backgroundSweepMs": 10000,
"fastScanMaxMs": 2000
}A device that comes back on the main bus at a high-priority address is identified 45-110 ms later with light polling, and 50-265 ms later with about half the bus used by polling (previously 86-206 ms and 430-475 ms).
A scan sweep is one complete pass through all addresses (and slots) that need to be scanned according to the current priority level.
A sweep is complete when:
- All addresses in the current priority list have been scanned
- All applicable slots have been checked for the current address set
After sweep completion:
- The mode may transition to the next mode (e.g., MAIN_BUS → SCAN_FAST)
- Priority lists are cycled according to their priority counts
- Sweep timing is recorded for performance monitoring
For each address to be scanned:
- Get Next Address: Priority-based selection from scan lists
-
Slot Validation:
- Skip if address is on main bus and we're scanning a slot
- Skip if it's a multiplexer on the wrong slot
- Skip if it's a defined IO expander
- Skip if address is currently being polled
- Enable Multiplexer Slot: If scanning a slot, enable the appropriate multiplexer configuration
- Perform Scan: Send I2C transaction to test device presence
-
Handle Result:
- Device responds: Mark online, trigger device identification
- No response: Mark offline or not present
- Bus stuck: Attempt recovery procedures
- Disable Multiplexer Slots: Return multiplexers to disabled state
- Update State: Update bus element tracker and status manager
When a device responds during scanning:
Device Responds
↓
Update Status Manager (online/offline tracking)
↓
Bus Element Tracker (record found on main bus or slot)
↓
If changing to ONLINE:
↓
Trigger Device Identification
↓
Store Device Type Information
The scanner includes sophisticated bus stuck detection and recovery:
- Bus stuck conditions detected during scan transactions
- Can occur on main bus or when enabling multiplexer slots
- Bus Clocking: Send clock pulses to release stuck devices
- Slot Disabling: Disable all multiplexer slots
- Power Cycling: If power control available, cycle power to problematic slot(s)
- Multiplexer Reset: Use hardware reset pins to reset multiplexers
Before Slot Enable (main bus issue):
- Clock the bus
- Disable all slots
- Power cycle entire bus (if available)
After Slot Enable (single slot issue):
- Clock the bus
- Disable the problematic slot
- Power cycle the single slot (if available)
- If still stuck, escalate to full bus recovery
Multiple recovery attempts are made before declaring the bus permanently stuck.
The scanner supports device hot-swapping through:
- Continuous Monitoring: Slow scan mode periodically checks all addresses
- Online/Offline Tracking: Threshold-based detection to avoid transient false positives
- Device Re-identification: When a device comes online, identification runs again
- Graceful Offline Handling: Devices that go offline are marked as such without disrupting other devices
To avoid false positives from transient communication errors:
- Two successful probes are needed to mark a device as online (the second is made on the next pass)
- Three consecutive failed accesses (scans or polls) mark it as offline - a success in between resets the count
The scanner is serviced from the I2C task at regular intervals:
bool taskService(uint64_t curTimeUs,
uint64_t maxFastTimeInLoopUs,
uint64_t maxSlowTimeInLoopUs)- Fast Scan: Allowed more time per service call for rapid detection
- Slow Scan: Reduced time budget to minimize impact on polling operations
The function returns true if fast scanning is in progress, allowing the caller to adjust task timing.
When scanning detects a device coming online, it triggers device identification:
Scan Detects Device Online
↓
Device Identification Manager
↓
Identify Device Type (query WHO_AM_I, etc.)
↓
Store Device Type Information
↓
Begin Polling (if supported)
See I2C Device Identification and Polling for details on how device types are determined, devices are initialized, and data is retrieved through polling.
Scanning avoids addresses that are currently being polled to prevent conflicts:
- The scanner checks
isAddrBeingPolled()before scanning an address - If an address is being polled, it is skipped during that sweep
- This ensures polling operations (which retrieve real data) are not interrupted by scanning operations
The scanner continuously updates the BusStatusMgr with device online/offline status:
-
updateBusElemState(): Called for each scan result -
informBusStuck(): Called when bus stuck condition detected -
goingOffline(): Called when power cycling devices offline -
setBusElemDeviceStatus(): Called with device identification results
Complete configuration for scanning with multiplexers:
{
"DevMan": {
"busScanPeriodMs": 20000,
"scanBoost": ["0x6a", "0x36"],
"Mux": {
"enable": true,
"minAddr": "0x70",
"maxAddr": "0x77",
"rstPin": 5,
"clearCascadeMux": false
}
}
}Parameters:
-
busScanPeriodMs: Slow scan period in milliseconds -
scanBoost: Array of high-priority addresses (hex strings) -
Mux.enable: Enable multiplexer support -
Mux.minAddr: Minimum multiplexer address -
Mux.maxAddr: Maximum multiplexer address -
Mux.rstPin: GPIO pin for hardware reset of multiplexers -
Mux.clearCascadeMux: Clear cascaded multiplexer slots on detection
- I2C Bus - I2C bus configuration and bus worker task overview
- I2C Device Identification and Polling - How device types are identified and data is retrieved
- Device Data Publishing - How device data is published
- DeviceManager SysMod - DeviceManager features and API
- DeviceManager Settings - Configuration settings for DeviceManager
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
Scaffolding & Building
- Raft CLI
- Web Apps (Device Types, Flasher)
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- WiFi Troubleshooting
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
- Camera
ROS 2
Helpers
Reference