Skip to main content

I2C and SPI peripheral verification

The AT1032S has I2C and SPI masters built in, running at whatever logic voltage the board uses. So proving that every sensor, EEPROM and flash on an assembled board is present, correctly addressed and actually responding takes a script and a loom, with no dongles or adapters anywhere on the bench.

The AT1032S scans the I2C bus on DA16 and DA18 to list every device that answers, then reads an ID register and an EEPROM page, while a separate SPI bus on DA20 to DA22 with a chip select on DA5 verifies the SPI flash.

An I2C bus and an SPI bus, live at the same time, both at the board's own logic level.

Wiring​

AT1032SDevice under test
DA16 SDA_0I2C data
DA18 SCL_0I2C clock
DA20 MOSI_1SPI flash data in
DA21 MISO_1SPI flash data out
DA22 SCK_1SPI flash clock
DA5SPI flash chip select

The test sequence​

import { AT1000 } from '@ikalogic/at1000';

// ---- The peripherals we expect to find ------------------------------------
const BUS_VCC = 3.3; // board logic voltage
const I2C_BAUD = 100000;

const SENSOR = { address: 0x68, id_register: 0x75, expected_id: 0x71, name: 'IMU' };
const EEPROM = { address: 0x50, page: 0x0010, name: 'EEPROM' };
const EEPROM_PAYLOAD = [0xA5, 0x5A, 0x12, 0x34];
const EEPROM_WRITE_MS = 10; // page write cycle

const SPI_BAUD = 1000000;
const SPI_CS_IO = 5;
const FLASH_JEDEC_ID = 0x9F; // read-identification opcode
const FLASH_EXPECTED = [0xEF, 0x40]; // first two bytes of the JEDEC id
// ---------------------------------------------------------------------------

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const hex = (n) => '0x' + n.toString(16).padStart(2, '0').toUpperCase();

const devices = await AT1000.findDevices();
const tester = await AT1000.open(devices[0]);
await tester.reset();

const i2c = tester.com.i2c(0);
await i2c.enable({ vcc: BUS_VCC, baud_rate: I2C_BAUD });

// ---- 1. Scan the bus ------------------------------------------------------
// A device that acknowledges its address accepts a zero-length write.
const found = [];
for (let address = 0x08; address <= 0x77; address++) {
try {
await i2c.tx({ address, data: [], start: true, stop: true });
found.push(address);
} catch {
// No acknowledge at this address: nothing is fitted there.
}
}
console.log('I2C devices found:', found.map(hex).join(', ') || 'none');

// ---- 2. Identify a known device -------------------------------------------
await i2c.tx({
address: SENSOR.address, data: [SENSOR.id_register], start: true, stop: false,
});
const [sensorId] = await i2c.rx({
address: SENSOR.address, length: 1, start: true, stop: true, nack_last_byte: true,
});
const sensorOk = sensorId === SENSOR.expected_id;
console.log(`${SENSOR.name} id: ${hex(sensorId)} (expected ${hex(SENSOR.expected_id)}) ` +
`-> ${sensorOk ? 'ok' : 'MISMATCH'}`);

// ---- 3. Write and read back an EEPROM page --------------------------------
const pageHigh = (EEPROM.page >> 8) & 0xFF;
const pageLow = EEPROM.page & 0xFF;

await i2c.tx({
address: EEPROM.address,
data: [pageHigh, pageLow, ...EEPROM_PAYLOAD],
start: true, stop: true,
});
await sleep(EEPROM_WRITE_MS);

await i2c.tx({
address: EEPROM.address, data: [pageHigh, pageLow], start: true, stop: false,
});
const readBack = await i2c.rx({
address: EEPROM.address, length: EEPROM_PAYLOAD.length,
start: true, stop: true, nack_last_byte: true,
});
const eepromOk = EEPROM_PAYLOAD.every((byte, i) => byte === readBack[i]);
console.log(`${EEPROM.name} read back: ${readBack.map(hex).join(' ')} ` +
`-> ${eepromOk ? 'ok' : 'MISMATCH'}`);

await i2c.disable();

// ---- 4. Identify the SPI flash --------------------------------------------
const spi = tester.com.spi(1);
const cs = tester.gpio.digital(SPI_CS_IO);
await cs.configure_output({ voh: BUS_VCC, vol: 0, vih: 2.0, vil: 0.8, value: true });
await spi.configure({ vcc: BUS_VCC, baud_rate: SPI_BAUD, mode: 0, enabled: true });

// Chip select is an ordinary output, so the transaction boundary is yours to set.
await cs.write(false);
const flashId = await spi.trx([FLASH_JEDEC_ID, 0x00, 0x00, 0x00]);
await cs.write(true);

// The first byte is clocked out while the opcode goes in, so the id starts at byte 1.
const flashOk = FLASH_EXPECTED.every((byte, i) => byte === flashId[i + 1]);
console.log(`SPI flash id: ${flashId.slice(1).map(hex).join(' ')} ` +
`-> ${flashOk ? 'ok' : 'MISMATCH'}`);

await spi.disable();

console.log('');
console.log(sensorOk && eepromOk && flashOk ? 'PASS' : 'FAIL');

await tester.reset();

Adapting it to your board​

  • BUS_VCC sets the bus voltage for both interfaces, so the same script covers a 1.8 V board and a 5 V board by changing one number.
  • SENSOR and EEPROM name the addresses and registers to check. Add an entry per device and loop over them.
  • I2C runs at 100 kHz, 400 kHz or 1 MHz; SPI runs up to 10 MHz in any of the four modes.
  • A second I2C bus and a second SPI bus are available on the other pin group, so two independent board sections can be verified in one run.

API used on this page​