i2c-ftdi-d2xx
Version:
i2c protocol implementation with ftdi-d2xx on standard boards that features no level translators on SDA and SCL
260 lines (215 loc) • 6.87 kB
JavaScript
const ftdi = require('ftdi-d2xx');
const { sleep } = require('./utils');
const STANDARD_TIMEOUT = 250;
const I2C_CLOCK_DIVISOR = 29;
const BUFFON_SCLH_SDAH = [0x80, 0xC3, 0xC3];
const BUFFON_SCLH_SDAL = [0x80, 0xC1, 0xC3];
const BUFFON_SCLL_SDAL = [0x80, 0xC0, 0xC3];
const BUFFON_SCLL_SDAH = [0x80, 0xC2, 0xC3];
const BUFOFF_SCLL_SDAL = [0x80, 0x00, 0xC0];
const BUFOFF_SCLH_SDAH = [0x80, 0x03, 0xC0];
const SCLL_SDAHIZ = [0x80, 0x80, 0xC1]
let device;
const isConnected = () => {
const ftdiConnected = device?.is_connected || false;
return ftdiConnected;
}
const getFtdiDevice = () => {
return device;
}
const setClockDivider = async (res=null, divider = I2C_CLOCK_DIVISOR) => {
try {
await device.write(Uint8Array.from([0x86, divider % 256, Math.floor(divider / 256)]))
await device.write(Uint8Array.from([0x8C])) //Three phase clock
} catch (error) {
if(res) {
res.success = false;
res.errors.push(error);
} else {
console.log(error);
}
}
}
const list = async (filtered = '') => {
const devices = await ftdi.getDeviceInfoList();
const filterRegex = new RegExp(filtered);
let validDevices;
if (filtered) {
validDevices = devices.filter(d => filterRegex.test(d.description));
} else {
validDevices = devices;
}
return validDevices;
}
const close = async () => {
const res = {success: false, errors: []}
try {
if (device && (device.is_connected)) {
//Reset bit mode
device.setBitMode(0, 0);
await sleep(STANDARD_TIMEOUT);
//Close connection
device.close();
}
res.success = true;
} catch (error) {
res.errors.push(error);
} finally {
return res;
}
}
const open = async (serialNumber) => {
const res = {success: false, errors: []};
try {
if (device?.is_connected) {
await close();
}
//Open connection
device = await ftdi.openDevice(serialNumber);
//Reset bit mode
device.setBitMode(0, 0);
await sleep(STANDARD_TIMEOUT);
//Set bit mode = MPSSE <-- I2C
device.setBitMode(0, 2);
res.success = true;
setClockDivider(res);
} catch (error) {
console.log(error);
res.errors.push(error);
} finally {
return res;
}
}
const i2cStart = () => {
return [
...[...Array(8)].flatMap(_ => BUFFON_SCLH_SDAH),
...[...Array(8)].flatMap(_ => BUFFON_SCLH_SDAL),
...[...Array(8)].flatMap(_ => BUFFON_SCLL_SDAL)
];
}
const i2cStop = () => {
return [
...[...Array(8)].flatMap(_ => BUFFON_SCLL_SDAL),
...[...Array(8)].flatMap(_ => BUFFON_SCLH_SDAL),
...[...Array(8)].flatMap(_ => BUFFON_SCLH_SDAH),
...BUFOFF_SCLH_SDAH
];
}
const i2cByte = (byte) => {
const command = [0x11, 0x00, 0x00];
const readAck = [0x22, 0x00];
const driveSdaAgainWithSclLow = [0x80, 0xC0, 0xC3]
return [
...command,
byte % 256,
...SCLL_SDAHIZ,
...readAck,
...driveSdaAgainWithSclLow
];
}
const i2cClock9 = (last=false) => {
const clock8 = [0x20, 0x00, 0x00];
const ack = [
...[...Array(8)].flatMap(_ => BUFFON_SCLL_SDAL),
...[...Array(8)].flatMap(_ => last ? BUFFON_SCLH_SDAH : BUFFON_SCLH_SDAL)
];
return [
...SCLL_SDAHIZ,
...clock8,
...BUFFON_SCLL_SDAL,
...ack
]
}
const write = async (slaveId=0x00, register=0x00, data=0x00) => {
const ftdiArray = [
...i2cStart(),
...i2cByte(slaveId),
...i2cByte(register),
...i2cByte(data),
...i2cStop()
];
await device.write(Uint8Array.from(ftdiArray));
const acks = await device.read(3);
const slaveAck = acks[0] % 2;
const addressAck = acks[1] % 2;
const dataAck = acks[2] % 2;
const res = {slaveAck: slaveAck, addressAck: addressAck, dataAck: dataAck};
return res;
}
const burstWrite = async (slaveId=0x00, register=0x00, data=[0x00]) => {
const ftdiArray = [
...i2cStart(),
...i2cByte(slaveId),
...i2cByte(register),
...data.flatMap(d => i2cByte(d)),
...i2cStop()
];
await device.write(Uint8Array.from(ftdiArray));
const acks = [...(await device.read(2 + data.length))];
const slaveAck = acks.shift() % 2;
const addressAck = acks.shift() % 2;
let dataAcks = [];
acks.forEach(a => {
dataAcks.push(a % 2)
});
const res = {slaveAck: slaveAck, addressAck: addressAck, dataAcks: dataAcks};
return res;
}
const sendCommand = async (slaveId=0x00, command=0x00) => {
const ftdiArray = [
...i2cStart(),
...i2cByte(slaveId),
...i2cByte(command),
...i2cStop()
];
await device.write(Uint8Array.from(ftdiArray));
const acks = await device.read(2);
const slaveAck = acks[0] % 2;
const commandAck = acks[1] % 2;
const res = {slaveAck: slaveAck, commandAck: commandAck};
return res;
}
const ping = async (slaveId=0x00) => {
const ftdiArray = [
...i2cStart(),
...i2cByte(slaveId),
...i2cStop()
];
await device.write(Uint8Array.from(ftdiArray));
const acks = await device.read(1);
const slaveAck = acks[0] % 2;
const res = {slaveAck: slaveAck};
return res;
}
const read = async (slaveId=0x00, register=0x00, nBytes=1) => {
const readBurst = [
...[...Array(nBytes -1)].flatMap(_ => i2cClock9()),
...[...Array(1)].flatMap(_ => i2cClock9(true)),
]
const ftdiArray = [
...i2cStart(),
...i2cByte(slaveId),
...i2cByte(register),
...i2cStart(),
...i2cByte(slaveId + 1),
...SCLL_SDAHIZ,
...readBurst,
...i2cStop()
];
await device.write(Uint8Array.from(ftdiArray));
const results = [...(await device.read(3 + nBytes))];
const acks = results.slice(0,3);
const data = results.slice(3,results.length);
const res = {data: data, slaveAck: acks[0] % 2, addressAck: acks[1] % 2, readAck: acks[2] % 2};
return res;
}
module.exports.list = list;
module.exports.open = open;
module.exports.close = close;
module.exports.isConnected = isConnected;
module.exports.write = write;
module.exports.burstWrite = burstWrite;
module.exports.sendCommand = sendCommand;
module.exports.ping = ping;
module.exports.read = read;
module.exports.getFtdiDevice = getFtdiDevice;