I2C
The I2C (Inter-Integrated Circuit) interface provides two-wire synchronous serial communication. Supports standard (100 kHz), fast (400 kHz), and fast-plus (1 MHz) modes with configurable voltage levels from 1.8 V to 3.3 V.
Please note that the AT1000 device only supports master mode for I2C communication. This means that it can initiate communication with slave devices, but it cannot act as a slave itself.
The AT1000 I2C master does not support clock stretching. A slave that holds SCL low to delay a transfer will not be waited for, so devices that rely on clock stretching need their own timing accommodation.
Some interfaces share the same GPIO pins, so only one can be used at a time. Check out the pin alternate functions page for more details.
The PWM pins use the same channels: enabling I2C fails with 409 CONFIGURATION_CONFLICT while PWM is enabled on a pin of the same channel (DA17/DA18 for I2C 0, DA21/DA22 for I2C 1).
Enabling I2C also fails with 409 CONFIGURATION_CONFLICT while its SDA or SCL pin is configured as a GPIO. Reset the pin to its default analog input (reset() on the pin) before enabling I2C.
Enabling I2C, or changing vcc while it is enabled, first measures its SDA and SCL pins. If any of them reads below −0.5 V or above vcc + 0.5 V, the request fails with 409 UNSAFE_PIN_VOLTAGE and the interface stays disabled. The error message names the pin and the voltage measured. Reconfiguring with an unchanged vcc does not repeat the check.
Quick Start​
The example code below shows how to enable the I2C bus, set the baud rate, and transmit/receive data.
- NodeJS
- Python
- Rust
import { AT1000 } from '@ikalogic/at1000';
let devices = await AT1000.findDevices(); // Find devices (returns host strings)
let tester = await AT1000.open(devices[0]); // Open the first detected device
await tester.reset(); // Reset the device to a known state
let i2c = tester.com.i2c(0); // Select the first I2C bus
await i2c.enable({ vcc: 3.3, baud_rate: 100000 }); // Enable I2C bus with 3.3V and 100kHz
// Define an I2C transaction
let i2c_transaction = {
address: 0x50, // I2C address of the slave device
start: true, // Start condition
stop: true, // Stop condition
nack_last_byte: false, // Don't NACK the last byte; the device default is true
};
// Write request to the slave device, registers 0x10 and 0x20
await i2c.tx({ ...i2c_transaction, data: [0x10, 0x20] });
i2c_transaction.nack_last_byte = true;
// Read 4 bytes from the slave device
let data = await i2c.rx({ ...i2c_transaction, length: 4 });
// Disable I2C bus on AT1000 master
await i2c.disable();
from ikalogic_at1000 import AT1000
devices = AT1000.find_devices() # Find devices (returns host strings)
tester = AT1000.open(devices[0]) # Open the first detected device
tester.reset() # Reset the device to a known state
i2c = tester.com.i2c(0) # Select the first I2C bus
i2c.enable(vcc=3.3, baud_rate=100000) # Enable I2C bus with 3.3V and 100kHz
# Define an I2C transaction
i2c_transaction = {
"address": 0x50, # I2C address of the slave device
"start": True, # Start condition
"stop": True, # Stop condition
"nack_last_byte": False, # Don't NACK the last byte; the device default is true
}
# Write request to the slave device, registers 0x10 and 0x20
i2c.tx({ **i2c_transaction, "data": [0x10, 0x20] })
i2c_transaction["nack_last_byte"] = True
# Read 4 bytes from the slave device
data = i2c.rx({ **i2c_transaction, "length": 4 })
# Disable I2C bus on AT1000 master
i2c.disable()
use ikalogic_at1000::{At1000, I2cAddress, I2cConfigPatch, I2cTransactionRead, I2cTransactionWrite};
use std::time::Duration;
let devices = At1000::find_devices(Duration::from_millis(500))?; // Find devices
let tester = At1000::open(&devices[0], None)?; // Open the first detected device
tester.reset()?; // Reset the device to a known state
let i2c = tester.com.i2c(0)?; // Select the first I2C bus
// Enable I2C bus with 3.3V and 100kHz
i2c.enable(I2cConfigPatch { vcc: Some(3.3), baud_rate: Some(100_000), ..Default::default() })?;
// I2C address of the slave device, validated once and reused
let address = I2cAddress::try_from(0x50)?;
// Write request to the slave device, registers 0x10 and 0x20
i2c.tx(
I2cTransactionWrite::new(address, &[0x10, 0x20])
.start(true) // Start condition
.stop(true), // Stop condition
)?;
// Read 4 bytes from the slave device
let data = i2c.rx(
I2cTransactionRead::new(address, 4)
.start(true)
.stop(true)
.nack_last_byte(true),
)?;
// Disable I2C bus on AT1000 master
i2c.disable()?;
API Reference​
Accessing I2C​
- NodeJS
- Python
- Rust
const i2c = tester.com.i2c(id);
i2c = tester.com.i2c(id)
let id: usize = 0;
let i2c = tester.com.i2c(id)?;
Parameters:
id(number/int/usize): The I2C interface identifier (0-based index)
Returns: I2c instance / Result<I2c, At1000Error> in Rust
Methods​
configure(config)​
Configures the I2C interface parameters without changing the enabled state.
- NodeJS
- Python
- Rust
await i2c.configure({
baud_rate: 400000,
vcc: 3.3
});
i2c.configure(
baud_rate=400000,
vcc=3.3
)
use ikalogic_at1000::I2cConfigPatch;
let i2c = tester.com.i2c(0)?;
i2c.configure(I2cConfigPatch {
baud_rate: Some(400_000),
vcc: Some(3.3),
..Default::default()
})?;
Parameters:
config(I2cConfigPatch): Configuration object with optional properties:enabled(boolean/bool/Option<bool>, optional): Enable or disable the interfacebaud_rate(number/int/Option<u32>, optional): I2C clock frequency - 100000, 400000, or 1000000vcc(number/float/Option<f64>, optional): Logic level voltage in volts (1.8 to 3.3)
Valid baud rates:
- 100000 (100 kHz): Standard mode
- 400000 (400 kHz): Fast mode
- 1000000 (1 MHz): Fast-plus mode
Default configuration (after tester.reset()): disabled, baud_rate 1000000, vcc 3.3 V.
Returns: Promise<I2cConfig> / I2cConfig / Result<I2cConfig, At1000Error> - The updated configuration
Exceptions:
- Throws validation error if
baud_rateis not one of the valid values - Throws validation error if
vccis outside the range [1.8, 3.3] - Throws error
409 UNSAFE_PIN_VOLTAGEif enabling, or changingvccwhile enabled, finds SDA or SCL below −0.5 V or abovevcc+ 0.5 V - Throws error
409 CONFIGURATION_CONFLICTwhen enabling while SDA or SCL is configured as a GPIO (not an analog input), or while SPI, UART or PWM uses the same channel
enable([config])​
Enables the I2C interface and optionally applies configuration.
- NodeJS
- Python
- Rust
await i2c.enable({
baud_rate: 400000,
vcc: 3.3
});
i2c.enable(
baud_rate=400000,
vcc=3.3
)
use ikalogic_at1000::I2cConfigPatch;
let i2c = tester.com.i2c(0)?;
i2c.enable(I2cConfigPatch {
baud_rate: Some(400_000),
vcc: Some(3.3),
..Default::default()
})?;
Parameters:
config(I2cEnableConfig, optional;I2cConfigPatchin Rust, always passed, withenabledset totruebyenableitself): Optional configuration to apply:baud_rate(number/int/Option<u32>, optional): I2C clock frequency (100000, 400000, or 1000000)vcc(number/float/Option<f64>, optional): Logic level voltage (1.8 to 3.3)
Returns: Promise<I2cConfig> / I2cConfig / Result<I2cConfig, At1000Error> - The updated configuration with enabled: true
Exceptions:
- Throws validation error if parameters are outside valid ranges
- Throws error
409 UNSAFE_PIN_VOLTAGEif SDA or SCL reads below −0.5 V or abovevcc+ 0.5 V - Throws error
409 CONFIGURATION_CONFLICTwhile SDA or SCL is configured as a GPIO (not an analog input), or while SPI, UART or PWM uses the same channel
disable()​
Disables the I2C interface.
- NodeJS
- Python
- Rust
await i2c.disable();
i2c.disable()
let i2c = tester.com.i2c(0)?;
i2c.disable()?;
Returns: Promise<I2cConfig> / I2cConfig / Result<I2cConfig, At1000Error> - The updated configuration with enabled: false
tx(transaction)​
Writes data to an I2C device.
- NodeJS
- Python
- Rust
// Write to device at address 0x50
await i2c.tx({
address: 0x50,
data: [0x00, 0x10, 0xAA, 0xBB]
});
// Write with custom start/stop conditions
await i2c.tx({
address: 0x3C,
start: true,
stop: false, // No stop - for repeated start
data: [0x40, 0xFF]
});
# Write to device at address 0x50
i2c.tx({
'address': 0x50,
'data': [0x00, 0x10, 0xAA, 0xBB]
})
# Write with custom start/stop conditions
i2c.tx({
'address': 0x3C,
'start': True,
'stop': False, # No stop - for repeated start
'data': [0x40, 0xFF]
})
use ikalogic_at1000::{I2cAddress, I2cTransactionWrite};
let i2c = tester.com.i2c(0)?;
// Write to device at address 0x50
i2c.tx(I2cTransactionWrite::new(
I2cAddress::try_from(0x50)?,
&[0x00, 0x10, 0xAA, 0xBB],
))?;
// Write with custom start/stop conditions
i2c.tx(
I2cTransactionWrite::new(I2cAddress::try_from(0x3C)?, &[0x40, 0xFF])
.start(true)
.stop(false), // No stop - for repeated start
)?;
Parameters:
transaction(I2cTransactionWrite): Write transaction configuration. In Rust it is passed by value and built withI2cTransactionWrite::new(address, data), with optional.start(bool)/.stop(bool)setters:address(number/int/I2cAddress, required): 7-bit I2C device addressdata(string | number[]/str | list[int]/&[u8], required): Data to write (max 65536 bytes)start(boolean/bool/.start(bool), optional, default:true): Generate START conditionstop(boolean/bool/.stop(bool), optional, default:true): Generate STOP conditionnack_last_byte(boolean/bool, optional, default:true): Send NACK on last byte (ignored on writes; not available on Rust writes)
Returns: Promise<number> / int / Result<usize, At1000Error> - Number of data bytes the slave acknowledged
A NACK is not an error, in any language: tx returns the number of bytes the slave acknowledged before the first NACK, which is 0 when it never acknowledged its address. Compare the result with the data length to detect a NACK.
Exceptions:
- Throws validation error if data exceeds 65536 bytes (the Rust SDK does not check the length itself)
- Throws error
400 INVALID_CONFIGURATIONif the interface is disabled
rx(transaction)​
Reads data from an I2C device.
- NodeJS
- Python
- Rust
// Read 16 bytes from device at address 0x50
const data = await i2c.rx({
address: 0x50,
length: 16
});
// Read with custom conditions
const data2 = await i2c.rx({
address: 0x3C,
length: 8,
start: true,
stop: true,
nack_last_byte: true
});
# Read 16 bytes from device at address 0x50
data = i2c.rx({
'address': 0x50,
'length': 16
})
# Read with custom conditions
data2 = i2c.rx({
'address': 0x3C,
'length': 8,
'start': True,
'stop': True,
'nack_last_byte': True
})
use ikalogic_at1000::{I2cAddress, I2cTransactionRead};
let i2c = tester.com.i2c(0)?;
// Read 16 bytes from device at address 0x50
let data = i2c.rx(I2cTransactionRead::new(I2cAddress::try_from(0x50)?, 16))?;
// Read with custom conditions
let data2 = i2c.rx(
I2cTransactionRead::new(I2cAddress::try_from(0x3C)?, 8)
.start(true)
.stop(true)
.nack_last_byte(true),
)?;
Parameters:
transaction(I2cTransactionRead): Read transaction configuration. In Rust it is passed by value and built withI2cTransactionRead::new(address, length), with optional.start(bool)/.stop(bool)/.nack_last_byte(bool)setters:address(number/int/I2cAddress, required): 7-bit I2C device addresslength(number/int/usize, required): Integer number of bytes to read (0 to 65536, inclusive)start(boolean/bool/.start(bool), optional, default:true): Generate START conditionstop(boolean/bool/.stop(bool), optional, default:true): Generate STOP conditionnack_last_byte(boolean/bool/.nack_last_byte(bool), optional, default:true): Send NACK on last byte
Returns: Promise<SerialData> / SerialData / Result<Vec<u8>, At1000Error> - Received data as byte array (number[], Vec<u8> in Rust)
A NACK is not an error, in any language: when the slave does not acknowledge its address, rx returns an empty array (an empty vector in Rust) instead of length bytes.
Exceptions:
- All three SDKs reject
lengthlocally unless it is an integer in [0, 65536], before sending a request. - Throws error
400 INVALID_CONFIGURATIONif the interface is disabled
Types​
I2cConfig​
Complete configuration object returned by configuration methods.
{
enabled: boolean,
baud_rate: number, // 100000, 400000, or 1000000
vcc: number // 1.8 to 3.3
}
Rust: ikalogic_at1000::I2cConfig
pub struct I2cConfig {
pub enabled: bool,
pub baud_rate: u32,
pub vcc: f64,
}
I2cConfigPatch​
Partial configuration for the configure() method. All properties are optional.
{
enabled?: boolean,
baud_rate?: number, // 100000, 400000, or 1000000
vcc?: number // 1.8 to 3.3
}
Rust: ikalogic_at1000::I2cConfigPatch
pub struct I2cConfigPatch {
pub enabled: Option<bool>,
pub baud_rate: Option<u32>,
pub vcc: Option<f64>,
}
I2cEnableConfig​
Optional configuration for the enable() method.
{
baud_rate?: number, // 100000, 400000, or 1000000
vcc?: number // 1.8 to 3.3
}
Rust has no separate enable type: enable takes I2cConfigPatch and sets enabled: true itself.
I2cTransactionWrite​
Write transaction parameters.
{
address: number, // 7-bit I2C address
data: string | number[], // Data to write (max 65536 bytes)
start?: boolean, // Generate START (default: true)
stop?: boolean, // Generate STOP (default: true)
nack_last_byte?: boolean // NACK last byte (default: true; ignored on writes)
}
Rust: ikalogic_at1000::I2cTransactionWrite. The fields are private: build the transaction with a constructor and setters. It is Copy and is passed to tx by value. The payload is borrowed for the duration of the call, and the address is a validated I2cAddress built with I2cAddress::try_from(0x50)?, which rejects anything above 0x7f. Rust writes have no nack_last_byte.
impl<'a> I2cTransactionWrite<'a> {
pub const fn new(address: I2cAddress, data: &'a [u8]) -> Self; // An empty payload is permitted
pub const fn start(self, start: bool) -> Self; // Generate START (device default: true)
pub const fn stop(self, stop: bool) -> Self; // Generate STOP (device default: true)
}
I2cTransactionRead​
Read transaction parameters.
{
address: number, // 7-bit I2C address
length: number, // Integer number of bytes to read (0-65536)
start?: boolean, // Generate START (default: true)
stop?: boolean, // Generate STOP (default: true)
nack_last_byte?: boolean // NACK last byte (default: true)
}
Rust: ikalogic_at1000::I2cTransactionRead. The fields are private: build the transaction with a constructor and setters. It is Copy and is passed to rx by value.
impl I2cTransactionRead {
pub const fn new(address: I2cAddress, length: usize) -> Self;
pub const fn start(self, start: bool) -> Self; // Generate START (device default: true)
pub const fn stop(self, stop: bool) -> Self; // Generate STOP (device default: true)
pub const fn nack_last_byte(self, nack_last_byte: bool) -> Self; // NACK last byte (device default: true)
}
SerialData​
Output data type, always returned as a byte array.
number[] // Array of bytes (0-255)
In Rust, data is a Vec<u8>.
Usage Example​
- NodeJS
- Python
- Rust
import { AT1000 } from '@ikalogic/at1000';
// Find and connect to device
const devices = await AT1000.findDevices();
const tester = await AT1000.open(devices[0]);
await tester.reset(); // Reset the device to a known state
// Access I2C interface 0
const i2c = tester.com.i2c(0);
// Enable with 400 kHz clock and 3.3V logic
await i2c.enable({
baud_rate: 400000,
vcc: 3.3
});
// Write to EEPROM at address 0x50
await i2c.tx({
address: 0x50,
data: [0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F] // "Hello" at address 0x0000
});
// Wait for write cycle to complete
await new Promise(resolve => setTimeout(resolve, 10));
// Read back from EEPROM
// First, set read address
await i2c.tx({
address: 0x50,
data: [0x00, 0x00],
stop: false // No stop for repeated start
});
// Then read data
const data = await i2c.rx({
address: 0x50,
length: 5
});
console.log("Read:", Buffer.from(data).toString()); // "Hello"
// Disable interface
await i2c.disable();
from ikalogic_at1000 import AT1000
import time
# Find and connect to device
devices = AT1000.find_devices()
tester = AT1000.open(devices[0])
tester.reset() # Reset the device to a known state
# Access I2C interface 0
i2c = tester.com.i2c(0)
# Enable with 400 kHz clock and 3.3V logic
i2c.enable(
baud_rate=400000,
vcc=3.3
)
# Write to EEPROM at address 0x50
i2c.tx({
'address': 0x50,
'data': [0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F] # "Hello" at address 0x0000
})
# Wait for write cycle to complete
time.sleep(0.01)
# Read back from EEPROM
# First, set read address
i2c.tx({
'address': 0x50,
'data': [0x00, 0x00],
'stop': False # No stop for repeated start
})
# Then read data
data = i2c.rx({
'address': 0x50,
'length': 5
})
print(f"Read: {bytes(data).decode()}") # "Hello"
# Disable interface
i2c.disable()
use ikalogic_at1000::{At1000, I2cAddress, I2cConfigPatch, I2cTransactionRead, I2cTransactionWrite};
use std::time::Duration;
// Find and connect to device
let devices = At1000::find_devices(Duration::from_millis(500))?;
let tester = At1000::open(&devices[0], None)?;
tester.reset()?; // Reset the device to a known state
// Access I2C interface 0
let i2c = tester.com.i2c(0)?;
// Enable with 400 kHz clock and 3.3V logic
i2c.enable(I2cConfigPatch {
baud_rate: Some(400_000),
vcc: Some(3.3),
..Default::default()
})?;
let eeprom = I2cAddress::try_from(0x50)?;
// Write to EEPROM at address 0x50
i2c.tx(I2cTransactionWrite::new(
eeprom,
&[0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F], // "Hello" at address 0x0000
))?;
// Wait for write cycle to complete
std::thread::sleep(Duration::from_millis(10));
// Read back from EEPROM
// First, set read address
i2c.tx(I2cTransactionWrite::new(eeprom, &[0x00, 0x00]).stop(false))?; // No stop for repeated start
// Then read data
let data = i2c.rx(I2cTransactionRead::new(eeprom, 5))?;
println!("Read: {}", String::from_utf8_lossy(&data)); // "Hello"
// Disable interface
i2c.disable()?;
Common Patterns​
Register Read (Single Byte)​
- NodeJS
- Python
- Rust
// Read register 0x75 from device at 0x68 (e.g., IMU WHO_AM_I register)
await i2c.tx({
address: 0x68,
data: [0x75],
stop: false
});
const whoAmI = await i2c.rx({
address: 0x68,
length: 1
});
console.log("WHO_AM_I:", whoAmI[0].toString(16));
# Read register 0x75 from device at 0x68 (e.g., IMU WHO_AM_I register)
i2c.tx({
'address': 0x68,
'data': [0x75],
'stop': False
})
who_am_i = i2c.rx({
'address': 0x68,
'length': 1
})
print(f"WHO_AM_I: {who_am_i[0]:02x}")
use ikalogic_at1000::{I2cAddress, I2cTransactionRead, I2cTransactionWrite};
let i2c = tester.com.i2c(0)?;
let imu = I2cAddress::try_from(0x68)?;
// Read register 0x75 from device at 0x68 (e.g., IMU WHO_AM_I register)
i2c.tx(I2cTransactionWrite::new(imu, &[0x75]).stop(false))?;
let who_am_i = i2c.rx(I2cTransactionRead::new(imu, 1))?;
println!("WHO_AM_I: {:02x}", who_am_i[0]);
Register Write​
- NodeJS
- Python
- Rust
// Write 0x80 to register 0x6B (e.g., PWR_MGMT_1 register)
await i2c.tx({
address: 0x68,
data: [0x6B, 0x80]
});
# Write 0x80 to register 0x6B (e.g., PWR_MGMT_1 register)
i2c.tx({
'address': 0x68,
'data': [0x6B, 0x80]
})
use ikalogic_at1000::{I2cAddress, I2cTransactionWrite};
let i2c = tester.com.i2c(0)?;
// Write 0x80 to register 0x6B (e.g., PWR_MGMT_1 register)
i2c.tx(I2cTransactionWrite::new(I2cAddress::try_from(0x68)?, &[0x6B, 0x80]))?;
Multi-Byte Register Read​
- NodeJS
- Python
- Rust
// Read 6 bytes starting from register 0x3B (e.g., accelerometer data)
await i2c.tx({
address: 0x68,
data: [0x3B],
stop: false
});
const accelData = await i2c.rx({
address: 0x68,
length: 6
});
// Parse as 16-bit signed integers
const accelX = (accelData[0] << 8) | accelData[1];
const accelY = (accelData[2] << 8) | accelData[3];
const accelZ = (accelData[4] << 8) | accelData[5];
# Read 6 bytes starting from register 0x3B (e.g., accelerometer data)
i2c.tx({
'address': 0x68,
'data': [0x3B],
'stop': False
})
accel_data = i2c.rx({
'address': 0x68,
'length': 6
})
# Parse as 16-bit signed integers
accel_x = (accel_data[0] << 8) | accel_data[1]
accel_y = (accel_data[2] << 8) | accel_data[3]
accel_z = (accel_data[4] << 8) | accel_data[5]
use ikalogic_at1000::{I2cAddress, I2cTransactionRead, I2cTransactionWrite};
let i2c = tester.com.i2c(0)?;
let imu = I2cAddress::try_from(0x68)?;
// Read 6 bytes starting from register 0x3B (e.g., accelerometer data)
i2c.tx(I2cTransactionWrite::new(imu, &[0x3B]).stop(false))?;
let accel_data = i2c.rx(I2cTransactionRead::new(imu, 6))?;
// Parse as 16-bit signed integers
let accel_x = i16::from_be_bytes([accel_data[0], accel_data[1]]);
let accel_y = i16::from_be_bytes([accel_data[2], accel_data[3]]);
let accel_z = i16::from_be_bytes([accel_data[4], accel_data[5]]);
EEPROM Operations​
- NodeJS
- Python
- Rust
// Write to EEPROM (24C256 example)
const eepromAddr = 0x50;
const memAddr = [0x00, 0x10]; // Address 0x0010
const writeData = [0xAA, 0xBB, 0xCC, 0xDD];
await i2c.tx({
address: eepromAddr,
data: [...memAddr, ...writeData]
});
// Wait for write cycle (5ms typical)
await new Promise(resolve => setTimeout(resolve, 10));
// Read back
await i2c.tx({
address: eepromAddr,
data: memAddr,
stop: false
});
const readData = await i2c.rx({
address: eepromAddr,
length: 4
});
import time
# Write to EEPROM (24C256 example)
eeprom_addr = 0x50
mem_addr = [0x00, 0x10] # Address 0x0010
write_data = [0xAA, 0xBB, 0xCC, 0xDD]
i2c.tx({
'address': eeprom_addr,
'data': mem_addr + write_data
})
# Wait for write cycle (5ms typical)
time.sleep(0.01)
# Read back
i2c.tx({
'address': eeprom_addr,
'data': mem_addr,
'stop': False
})
read_data = i2c.rx({
'address': eeprom_addr,
'length': 4
})
use ikalogic_at1000::{I2cAddress, I2cTransactionRead, I2cTransactionWrite};
use std::time::Duration;
let i2c = tester.com.i2c(0)?;
// Write to EEPROM (24C256 example)
let eeprom_addr = I2cAddress::try_from(0x50)?;
let mem_addr = [0x00u8, 0x10]; // Address 0x0010
let write_data = [0xAAu8, 0xBB, 0xCC, 0xDD];
let payload: Vec<u8> = mem_addr.iter().chain(write_data.iter()).copied().collect();
i2c.tx(I2cTransactionWrite::new(eeprom_addr, &payload))?;
// Wait for write cycle (5ms typical)
std::thread::sleep(Duration::from_millis(10));
// Read back
i2c.tx(I2cTransactionWrite::new(eeprom_addr, &mem_addr).stop(false))?;
let read_data = i2c.rx(I2cTransactionRead::new(eeprom_addr, 4))?;
Scanning I2C Bus​
Each address is probed with a 1-byte read: a slave that acknowledges its address returns one byte, an absent one returns an empty array. A write with no data cannot be used for this, because its result is 0 whether or not the address was acknowledged. Some devices act on a read (for example by popping a FIFO), so scan only buses where that is harmless.
- NodeJS
- Python
- Rust
console.log("Scanning I2C bus...");
const foundDevices = [];
for (let addr = 0x08; addr < 0x78; addr++) {
// An absent device NACKs its address and the read returns no bytes
const data = await i2c.rx({ address: addr, length: 1 });
if (data.length > 0) {
foundDevices.push(addr);
console.log(`Found device at 0x${addr.toString(16).padStart(2, '0')}`);
}
}
console.log(`Found ${foundDevices.length} device(s)`);
print("Scanning I2C bus...")
found_devices = []
for addr in range(0x08, 0x78):
# An absent device NACKs its address and the read returns no bytes
data = i2c.rx({'address': addr, 'length': 1})
if len(data) > 0:
found_devices.append(addr)
print(f"Found device at 0x{addr:02x}")
print(f"Found {len(found_devices)} device(s)")
use ikalogic_at1000::{I2cAddress, I2cTransactionRead};
let i2c = tester.com.i2c(0)?;
println!("Scanning I2C bus...");
let mut found_devices = Vec::new();
for addr in 0x08u8..0x78 {
// An absent device NACKs its address and the read returns no bytes
let data = i2c.rx(I2cTransactionRead::new(I2cAddress::try_from(addr)?, 1))?;
if !data.is_empty() {
found_devices.push(addr);
println!("Found device at 0x{addr:02x}");
}
}
println!("Found {} device(s)", found_devices.len());
Repeated Start Transaction​
- NodeJS
- Python
- Rust
// Combined write-read transaction with repeated start
// (More efficient than separate transactions)
// Write register address
await i2c.tx({
address: 0x3C,
data: [0x00],
stop: false // Keep bus active for repeated start
});
// Immediately read without releasing bus
const data = await i2c.rx({
address: 0x3C,
length: 16,
start: true, // Repeated START
stop: true
});
# Combined write-read transaction with repeated start
# (More efficient than separate transactions)
# Write register address
i2c.tx({
'address': 0x3C,
'data': [0x00],
'stop': False # Keep bus active for repeated start
})
# Immediately read without releasing bus
data = i2c.rx({
'address': 0x3C,
'length': 16,
'start': True, # Repeated START
'stop': True
})
use ikalogic_at1000::{I2cAddress, I2cTransactionRead, I2cTransactionWrite};
let i2c = tester.com.i2c(0)?;
let display = I2cAddress::try_from(0x3C)?;
// Combined write-read transaction with repeated start
// (More efficient than separate transactions)
// Write register address
i2c.tx(I2cTransactionWrite::new(display, &[0x00]).stop(false))?; // Keep bus active for repeated start
// Immediately read without releasing bus
let data = i2c.rx(
I2cTransactionRead::new(display, 16)
.start(true) // Repeated START
.stop(true),
)?;
embedded-hal Support (Rust)​
The Rust SDK implements the embedded-hal 1.0 traits, so existing driver crates can talk to a device wired to the AT1000. Add embedded-hal = "1" to your dependencies to call the trait methods directly.
I2cimplementsembedded_hal::i2c::I2cwith 7-bit addresses. Atransactionsends START before the first operation, a repeated START when the direction changes, and STOP after the last operation- A NACK fails with
At1000Error::NoAcknowledge, whose embedded-hal kind isErrorKind::NoAcknowledge. This differs from thetx()method, which returns the number of acknowledged bytes Spiimplementsembedded_hal::spi::SpiBus. The bus has no chip select of its own: drive one from aDigitalIooutput, for example withExclusiveDevicefrom theembedded-hal-buscrateDigitalIoimplementsOutputPinandInputPin, once configured as a digital output or input
use embedded_hal::i2c::I2c as _;
use ikalogic_at1000::I2cConfigPatch;
let mut i2c = tester.com.i2c(0)?;
i2c.enable(I2cConfigPatch { vcc: Some(3.3), baud_rate: Some(400_000), ..Default::default() })?;
// Read register 0x75 from device at 0x68 (e.g., IMU WHO_AM_I register), with a repeated START
let mut buf = [0u8; 1];
i2c.write_read(0x68, &[0x75], &mut buf)?;
println!("WHO_AM_I: {:02x}", buf[0]);
Notes​
- I2C addresses are 7-bit values (0 to 127 / 0x00 to 0x7F); 0x08 to 0x77 is the non-reserved address range
- Pull-up resistors are typically required on SDA and SCL lines (often integrated)
- Clock stretching is not supported by the underlying hardware
- Use
nack_last_byte: true(.nack_last_byte(true)in Rust) when reading to signal end of transaction - Read transactions accept an integer length from 0 to 65536 bytes, inclusive, with local validation in all three SDKs. Write data may be up to 65536 bytes per transaction