I2C with Wire
The Wire object, the shared I2C1 sensor bus, the on-board device addresses, and the bus locking functions.
Wire is the standard Arduino I2C master. Third-party sensor libraries that use Wire.beginTransmission, Wire.write, Wire.endTransmission, Wire.requestFrom and Wire.read work without modification.
#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin();
}
void loop() {
int found = 0;
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0) {
Serial.print(" device at 0x");
Serial.println(address, HEX);
found++;
}
}
if (found == 0) Serial.println(" no devices found");
delay(5000);
}This is the WireScanner example. The IMU and magnetometer on the Neuro N6 respond on every board; the camera responds when a module is fitted, and the time of flight sensor on the OV5640, TFT and STCam modules.
The bus
Wire drives the sensor bus, which the core's HAL handle names hi2c1. The IMU and magnetometer on the Neuro N6, the camera control interface, the time of flight sensor on the OV5640, TFT and STCam modules, the touch controller on the TFT carriers and the thermal camera's command interface share it. Any external device on this bus must use a free address.
The board has two I2C interfaces: one on the Feather header SDA and SCL pins (PD15 and PD14, 3.3 V, with pull-ups) and one on the 40-pin board-to-board connector to the camera module. A device on the header pins is verified with the WireScanner example before use.
| Device | 7-bit address |
|---|---|
| OV5640 camera | 0x3C |
| VD66GY camera | 0x10 |
| LSM6DSL IMU | 0x6A or 0x6B |
| MMC5603NJ magnetometer | 0x30 |
| VL53L1CB time of flight | 0x29 |
| GT911 touch controller | 0x5D or 0x14 |
| FLIR Lepton | 0x2A |
Addresses are 7-bit, following the Arduino convention.
Implementation
- Master mode only.
Wire.begin()has no effect; the core initialises the bus beforesetup().Wire.setClock()has no effect. The bus runs at a fixed timing suitable for all on-board devices.- The transaction buffer is 32 bytes, matching Arduino's
BUFFER_LENGTH. endTransmission()returns the standard codes: 0 on success, non-zero on NACK or timeout.
Bus sharing
The camera driver, the sensor libraries and the core's diagnostics all use I2C1, some from other FreeRTOS tasks. Every user brackets each transaction with a recursive mutex, so transactions from different tasks cannot interleave. Wire and the bundled libraries take the mutex internally.
Three lower-level functions, provided by Arduino.h, cover cases where per-transaction locking is insufficient.
Locking a transaction
n6_i2c_lock() and n6_i2c_unlock() are the functions Wire uses. They are needed only when calling the HAL directly:
n6_i2c_lock();
HAL_StatusTypeDef st = HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 2, 5);
n6_i2c_unlock();Claiming the bus for a period
Some devices require the bus to be silent, not merely ordered. The FLIR Lepton fails if it observes traffic to other addresses during its boot window. n6_i2c_claim() blocks every other user until n6_i2c_release():
n6_i2c_claim();
thermal.powerOn();
delay(2500);
n6_i2c_release();Claim and release must be called from the same task, and the claimed period must be bounded, since other users wait indefinitely.
Non-blocking acquisition
n6_i2c_trylock() returns 1 if the bus was acquired and 0 if it was busy, without waiting. A periodic publisher uses it to skip a reading rather than stall:
if (n6_i2c_trylock()) {
imu.sendNS();
mag.sendNS();
n6_i2c_unlock();
}Bundled sensor libraries
Each on-board sensor library follows the same pattern: declare an object, call begin(), read, and optionally call sendNS() to stream the reading to Neuro Studio. The libraries do not block: reads are bounded and return a sentinel value on failure. See On-board Sensors.
Address conflicts
A device whose address conflicts with an on-board part is normally reconfigured through its address select pin, or driven by a software I2C library on two spare digital pins.