@johntalton/ftdi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/lib/consts.d.ts +33 -0
- package/lib/consts.d.ts.map +1 -0
- package/lib/consts.js +42 -0
- package/lib/consts.js.map +1 -0
- package/lib/eeprom.d.ts +129 -0
- package/lib/eeprom.d.ts.map +1 -0
- package/lib/eeprom.js +495 -0
- package/lib/eeprom.js.map +1 -0
- package/lib/ft232h.d.ts +37 -0
- package/lib/ft232h.d.ts.map +1 -0
- package/lib/ft232h.js +204 -0
- package/lib/ft232h.js.map +1 -0
- package/lib/ftdi.d.ts +37 -0
- package/lib/ftdi.d.ts.map +1 -0
- package/lib/ftdi.js +204 -0
- package/lib/ftdi.js.map +1 -0
- package/lib/i2c/i2c-template.d.ts +10 -0
- package/lib/i2c/i2c-template.d.ts.map +1 -0
- package/lib/i2c/i2c-template.js +122 -0
- package/lib/i2c/i2c-template.js.map +1 -0
- package/lib/i2c/i2c.d.ts +29 -0
- package/lib/i2c/i2c.d.ts.map +1 -0
- package/lib/i2c/i2c.js +169 -0
- package/lib/i2c/i2c.js.map +1 -0
- package/lib/i2c/index.d.ts +5 -0
- package/lib/i2c/index.d.ts.map +1 -0
- package/lib/i2c/index.js +5 -0
- package/lib/i2c/index.js.map +1 -0
- package/lib/i2c/range.d.ts +2 -0
- package/lib/i2c/range.d.ts.map +1 -0
- package/lib/i2c/range.js +6 -0
- package/lib/i2c/range.js.map +1 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9 -0
- package/lib/index.js.map +1 -0
- package/lib/mpsse/clock.d.ts +16 -0
- package/lib/mpsse/clock.d.ts.map +1 -0
- package/lib/mpsse/clock.js +34 -0
- package/lib/mpsse/clock.js.map +1 -0
- package/lib/mpsse/command.d.ts +66 -0
- package/lib/mpsse/command.d.ts.map +1 -0
- package/lib/mpsse/command.js +89 -0
- package/lib/mpsse/command.js.map +1 -0
- package/lib/mpsse/index.d.ts +7 -0
- package/lib/mpsse/index.d.ts.map +1 -0
- package/lib/mpsse/index.js +7 -0
- package/lib/mpsse/index.js.map +1 -0
- package/lib/mpsse/mpsse-template.d.ts +19 -0
- package/lib/mpsse/mpsse-template.d.ts.map +1 -0
- package/lib/mpsse/mpsse-template.js +81 -0
- package/lib/mpsse/mpsse-template.js.map +1 -0
- package/lib/mpsse/mpsse.d.ts +18 -0
- package/lib/mpsse/mpsse.d.ts.map +1 -0
- package/lib/mpsse/mpsse.js +77 -0
- package/lib/mpsse/mpsse.js.map +1 -0
- package/lib/status.d.ts +39 -0
- package/lib/status.d.ts.map +1 -0
- package/lib/status.js +62 -0
- package/lib/status.js.map +1 -0
- package/lib/util.d.ts +26 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +57 -0
- package/lib/util.js.map +1 -0
- package/package.json +38 -0
- package/src/consts.ts +57 -0
- package/src/eeprom.ts +663 -0
- package/src/ftdi.ts +215 -0
- package/src/i2c/i2c-template.ts +156 -0
- package/src/i2c/i2c.ts +210 -0
- package/src/i2c/index.ts +4 -0
- package/src/i2c/range.ts +6 -0
- package/src/index.ts +8 -0
- package/src/mpsse/clock.ts +36 -0
- package/src/mpsse/command.ts +113 -0
- package/src/mpsse/index.ts +6 -0
- package/src/mpsse/mpsse-template.ts +96 -0
- package/src/mpsse/mpsse.ts +95 -0
- package/src/status.ts +94 -0
- package/src/util.ts +72 -0
package/src/ftdi.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNestedTernary: <explanation> */
|
|
2
|
+
import type { BitMode, ModemControl, RequestType } from './consts.ts'
|
|
3
|
+
import { REQUESTS, RESET_USB } from './consts.ts'
|
|
4
|
+
import { MPSSE } from './mpsse/mpsse.ts'
|
|
5
|
+
import {
|
|
6
|
+
DeviceStatus,
|
|
7
|
+
type DeviceStatusInfo,
|
|
8
|
+
STATUS_PREFIX_LENGTH,
|
|
9
|
+
} from './status.ts'
|
|
10
|
+
|
|
11
|
+
export const USB_TRANSFER_OK: USBTransferStatus = 'ok'
|
|
12
|
+
export const USB_TRANSFER_STALL: USBTransferStatus = 'stall'
|
|
13
|
+
export const USB_TRANSFER_BABBLE: USBTransferStatus = 'babble'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export const REQUEST_TYPE_VENDOR = 'vendor'
|
|
17
|
+
export const RECIPIENT_DEVICE = 'device'
|
|
18
|
+
|
|
19
|
+
export const INTERFACE_DIRECTION_IN = 'in'
|
|
20
|
+
export const INTERFACE_DIRECTION_OUT = 'out'
|
|
21
|
+
export const INTERFACE_TYPE_BULK = 'bulk'
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_CONFIGURATION_NUMBER = 1
|
|
24
|
+
export const DEFAULT_INTERFACE_NUMBER = 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export function assertDataViewNotShared(view: DataView): asserts view is DataView & { buffer: ArrayBuffer } {
|
|
28
|
+
if (typeof SharedArrayBuffer !== 'undefined' && view.buffer instanceof SharedArrayBuffer) {
|
|
29
|
+
throw new TypeError('DataView cannot be backed by a SharedArrayBuffer')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class FTDIDevice {
|
|
34
|
+
readonly #device
|
|
35
|
+
readonly #endpointBulkIn: number
|
|
36
|
+
readonly #endpointBulkOut: number
|
|
37
|
+
readonly #interfaceNumber: number
|
|
38
|
+
readonly #mpsse: MPSSE
|
|
39
|
+
|
|
40
|
+
static async from(device: USBDevice): Promise<FTDIDevice> {
|
|
41
|
+
const { interfaceNumber, epIn, epOut } = await FTDIDevice.#discoverEndpoints(device)
|
|
42
|
+
return new FTDIDevice(device, interfaceNumber, epIn, epOut)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor(device: USBDevice, interfaceNumber: number, epIn: number, epOut: number) {
|
|
46
|
+
this.#device = device
|
|
47
|
+
this.#endpointBulkIn = epIn
|
|
48
|
+
this.#endpointBulkOut = epOut
|
|
49
|
+
this.#interfaceNumber = interfaceNumber
|
|
50
|
+
this.#mpsse = new MPSSE(this)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get mpsse(): MPSSE { return this.#mpsse }
|
|
54
|
+
|
|
55
|
+
static async #discoverEndpoints(device: USBDevice): Promise<{ interfaceNumber: number, epIn: number, epOut: number}> {
|
|
56
|
+
if (device.configuration === null) {
|
|
57
|
+
await device.selectConfiguration(DEFAULT_CONFIGURATION_NUMBER)
|
|
58
|
+
}
|
|
59
|
+
await device.claimInterface(DEFAULT_INTERFACE_NUMBER)
|
|
60
|
+
|
|
61
|
+
if(device.configuration === null) {
|
|
62
|
+
throw new Error('Configuration is NULL')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { interfaces } = device.configuration
|
|
66
|
+
const [ iface ] = interfaces
|
|
67
|
+
if(iface === undefined) { throw new Error('Interface is undefined') }
|
|
68
|
+
const { alternate, interfaceNumber } = iface
|
|
69
|
+
const { endpoints } = alternate
|
|
70
|
+
|
|
71
|
+
const epIn = endpoints.find(ep => ep.direction === INTERFACE_DIRECTION_IN && ep.type === INTERFACE_TYPE_BULK)
|
|
72
|
+
const epOut = endpoints.find(ep => ep.direction === INTERFACE_DIRECTION_OUT && ep.type === INTERFACE_TYPE_BULK)
|
|
73
|
+
|
|
74
|
+
if(epIn === undefined) { throw new Error('Endpoint In Bulk not found') }
|
|
75
|
+
if(epOut === undefined) { throw new Error('Endpoint Out Bulk not found') }
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
interfaceNumber,
|
|
79
|
+
epIn: epIn.endpointNumber,
|
|
80
|
+
epOut: epOut.endpointNumber
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async #requestIn(request: RequestType, length: number, index: number|undefined = undefined): Promise<USBInTransferResult> {
|
|
85
|
+
return this.#device.controlTransferIn({
|
|
86
|
+
requestType: REQUEST_TYPE_VENDOR,
|
|
87
|
+
recipient: RECIPIENT_DEVICE,
|
|
88
|
+
request,
|
|
89
|
+
value: 0,
|
|
90
|
+
index: index ?? this.#interfaceNumber
|
|
91
|
+
}, length)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async #requestOut(request: RequestType, value: number, index: number|undefined = undefined): Promise<USBOutTransferResult> {
|
|
95
|
+
return this.#device.controlTransferOut({
|
|
96
|
+
requestType: REQUEST_TYPE_VENDOR,
|
|
97
|
+
recipient: RECIPIENT_DEVICE,
|
|
98
|
+
request,
|
|
99
|
+
value,
|
|
100
|
+
index: index ?? this.#interfaceNumber
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async reset(kind = RESET_USB.RESET): Promise<void> {
|
|
105
|
+
await this.#requestOut(REQUESTS.RESET, kind)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async setBitMode(mode: BitMode): Promise<void> {
|
|
109
|
+
const gpioInit = 0
|
|
110
|
+
const value = mode | gpioInit
|
|
111
|
+
await this.#requestOut(REQUESTS.SET_BITMODE, value)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getModemStatus(): Promise<DeviceStatusInfo|undefined> {
|
|
115
|
+
const status = await this.#requestIn(REQUESTS.POLL_MODEM_STATUS, STATUS_PREFIX_LENGTH)
|
|
116
|
+
if(status.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
117
|
+
if(status.data === undefined) { throw new Error('undefined data') }
|
|
118
|
+
assertDataViewNotShared(status.data)
|
|
119
|
+
return DeviceStatus.parse(status.data)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getLatencyTimer(): Promise<number> {
|
|
123
|
+
const result = await this.#requestIn(REQUESTS.GET_LATENCY_TIMER, 1)
|
|
124
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
125
|
+
if(result.data === undefined) { throw new Error('undefined data') }
|
|
126
|
+
|
|
127
|
+
const latency = result.data.getUint8(0)
|
|
128
|
+
return latency
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async setLatencyTimer(latency: number): Promise<void> {
|
|
132
|
+
if(!Number.isInteger(latency)) { throw new TypeError('latency is not valid integer') }
|
|
133
|
+
if(latency < 0 || latency > 255) { throw new RangeError('latency out of range') }
|
|
134
|
+
|
|
135
|
+
const result = await this.#requestOut(REQUESTS.SET_LATENCY_TIMER, latency)
|
|
136
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async readPins(): Promise<number> {
|
|
140
|
+
const result = await this.#requestIn(REQUESTS.READ_PINS, 1)
|
|
141
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
142
|
+
if(result.data === undefined) { throw new Error('undefined data') }
|
|
143
|
+
return result.data.getUint8(0)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async setModemControl(control: ModemControl): Promise<void> {
|
|
147
|
+
const DTR_ENABLE_BIT = 0x01_00
|
|
148
|
+
const RTS_ENABLE_BIT = 0x02_00
|
|
149
|
+
const DTR_SET = 0x01
|
|
150
|
+
const RTS_SET = 0x02
|
|
151
|
+
|
|
152
|
+
const setDTR = control.DataTerminalReady !== undefined
|
|
153
|
+
const setRTS = control.RequestToSend !== undefined
|
|
154
|
+
const dtrValue = setDTR ? DTR_ENABLE_BIT | (control.DataTerminalReady ? DTR_SET : 0) : 0
|
|
155
|
+
const rtsValue = setRTS ? RTS_ENABLE_BIT | (control.RequestToSend ? RTS_SET : 0) : 0
|
|
156
|
+
|
|
157
|
+
const value = dtrValue | rtsValue
|
|
158
|
+
|
|
159
|
+
const result = await this.#requestOut(REQUESTS.SET_MODEM_CTRL, value)
|
|
160
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async setEventChar(data: number, enable = true): Promise<void> {
|
|
164
|
+
const ENABLE_BIT = 0x01_00
|
|
165
|
+
|
|
166
|
+
const value = enable ? ENABLE_BIT | data : data
|
|
167
|
+
|
|
168
|
+
const result = await this.#requestOut(REQUESTS.SET_EVENT_CHAR, value)
|
|
169
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async setErrorChar(data: number, enable = true): Promise<void> {
|
|
173
|
+
const ENABLE_BIT = 0x01_00
|
|
174
|
+
|
|
175
|
+
const value = enable ? ENABLE_BIT | data : data
|
|
176
|
+
|
|
177
|
+
const result = await this.#requestOut(REQUESTS.SET_ERROR_CHAR, value)
|
|
178
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('status not ok') }
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async sendData(data: BufferSource): Promise<number> {
|
|
184
|
+
const result = await this.#device.transferOut(this.#endpointBulkOut, data)
|
|
185
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('failure sending data') }
|
|
186
|
+
return result.bytesWritten
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async readData(length: number): Promise<DataView<ArrayBuffer>> {
|
|
190
|
+
const result = await this.#device.transferIn(this.#endpointBulkIn, length)
|
|
191
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('failure to read data') }
|
|
192
|
+
if(result.data === undefined) { throw new Error('result data undefined') }
|
|
193
|
+
assertDataViewNotShared(result.data)
|
|
194
|
+
return result.data
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async readEEPROM(address: number, length: number): Promise<DataView<ArrayBuffer>> {
|
|
198
|
+
const result = await this.#requestIn(REQUESTS.READ_EEPROM, length, address)
|
|
199
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('failure to read eeprom') }
|
|
200
|
+
if(result.data === undefined) { throw new Error('result data undefined') }
|
|
201
|
+
assertDataViewNotShared(result.data)
|
|
202
|
+
|
|
203
|
+
return result.data
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async writeEEPROM(address: number, value: number): Promise<number> {
|
|
207
|
+
const result = await this.#requestOut(REQUESTS.WRITE_EEPROM, value, address)
|
|
208
|
+
if(result.status !== USB_TRANSFER_OK) { throw new Error('failure to read eeprom') }
|
|
209
|
+
return result.bytesWritten
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// async eraseEEPROM(): Promise<void> {
|
|
213
|
+
|
|
214
|
+
// }
|
|
215
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/useConsistentArrayType: <explanation> */
|
|
2
|
+
import {
|
|
3
|
+
CLOCK_BASE_12,
|
|
4
|
+
CLOCK_BASE_60,
|
|
5
|
+
Clock
|
|
6
|
+
} from '../mpsse/clock.ts'
|
|
7
|
+
import {
|
|
8
|
+
CLOCK_DIVISOR_COMMANDS,
|
|
9
|
+
FT232H_ONLY_COMMANDS,
|
|
10
|
+
H_COMMANDS,
|
|
11
|
+
HOST_AND_MPSSE_MODE_COMMANDS,
|
|
12
|
+
PIN_STATE_COMMANDS,
|
|
13
|
+
SHIFT_COMMAND
|
|
14
|
+
} from '../mpsse/command.ts'
|
|
15
|
+
import { MPSSETemplate } from '../mpsse/mpsse-template.ts'
|
|
16
|
+
|
|
17
|
+
const SDA_PIN_MASK = 0b0000_0010
|
|
18
|
+
const SCL_PIN_MASK = 0b0000_0001
|
|
19
|
+
|
|
20
|
+
const SCL_HIGH_SDA_HIGH: number = SDA_PIN_MASK | SCL_PIN_MASK
|
|
21
|
+
const SCL_HIGH_SDA_LOW: number = SCL_PIN_MASK
|
|
22
|
+
const SCL_LOW_SDA_HIGH: number = SDA_PIN_MASK
|
|
23
|
+
const SCL_LOW_SDA_LOW = 0
|
|
24
|
+
|
|
25
|
+
const SDA_DIRECTION_OUT = 0b0000_0010
|
|
26
|
+
const SCL_DIRECTION_OUT = 0b0000_0001
|
|
27
|
+
const SDA_SCL_DIRECTION_OUT: number = SDA_DIRECTION_OUT | SCL_DIRECTION_OUT
|
|
28
|
+
|
|
29
|
+
const OPEN_DRAIN_LOW_PIN_MASK = 0b0000_0111
|
|
30
|
+
const OPEN_DRAIN_HIGH_PIN_MASK = 0b0000_0000
|
|
31
|
+
|
|
32
|
+
export class I2CTemplate {
|
|
33
|
+
static initI2C(targetHertz: number, enableClockDivideBy5 = false): Uint8Array<ArrayBuffer> {
|
|
34
|
+
const clockBase = enableClockDivideBy5 ? CLOCK_BASE_12 : CLOCK_BASE_60
|
|
35
|
+
const divideBy5Command = enableClockDivideBy5 ? H_COMMANDS.ENABLE_CLOCK_DIVIDE_BY_FIVE : H_COMMANDS.DISABLE_CLOCK_DIVIDE_BY_FIVE
|
|
36
|
+
|
|
37
|
+
const divisor = Clock.clockDivisor(targetHertz, clockBase)
|
|
38
|
+
const divisorH = (divisor >> 8) & 0xFF
|
|
39
|
+
const divisorL = divisor & 0xFF
|
|
40
|
+
|
|
41
|
+
return Uint8Array.from([
|
|
42
|
+
divideBy5Command,
|
|
43
|
+
H_COMMANDS.ENABLE_THREE_PHASE_CLOCKING,
|
|
44
|
+
FT232H_ONLY_COMMANDS.SET_IO_ONLY_DRIVE_LOW, OPEN_DRAIN_LOW_PIN_MASK, OPEN_DRAIN_HIGH_PIN_MASK, // low pins as open-drain
|
|
45
|
+
PIN_STATE_COMMANDS.SET_DATA_BITS_LOW_BYTE, SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT, // value, direction
|
|
46
|
+
CLOCK_DIVISOR_COMMANDS.SET_CLK_DIVISOR, divisorL, divisorH, // 0x4A, 0x01, // ~100kHz
|
|
47
|
+
|
|
48
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
49
|
+
])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static #consumeAck(): Array<number> {
|
|
53
|
+
return [
|
|
54
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_HIGH, SDA_SCL_DIRECTION_OUT), // release SDA
|
|
55
|
+
...MPSSETemplate.shiftInBits(SHIFT_COMMAND.IN_MSB_BITS_POSITIVE_VE, 1),
|
|
56
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT), // regain
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static #start(): Array<number> {
|
|
61
|
+
return [
|
|
62
|
+
// SDA: 1, SCL: 0
|
|
63
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
64
|
+
|
|
65
|
+
// SDA: 1, SCL: 1 (release both lines)
|
|
66
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
67
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
68
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
69
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
70
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
71
|
+
|
|
72
|
+
// SDA: 0, SCL: 1 (low data while clock high)
|
|
73
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
74
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
75
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
76
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
77
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
78
|
+
|
|
79
|
+
// SDA: 0, SCL: 0 (both low - hold start)
|
|
80
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
81
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
82
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
83
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
84
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static startWithAddress(addr: number, write = false): Uint8Array<ArrayBuffer> {
|
|
89
|
+
const data = (addr << 1) | (write ? 0 : 1)
|
|
90
|
+
|
|
91
|
+
return Uint8Array.from([
|
|
92
|
+
...I2CTemplate.#start(),
|
|
93
|
+
...MPSSETemplate.shiftOutByte(SHIFT_COMMAND.OUT_MSB_BYTES_NEGATIVE_VE, 1, data),
|
|
94
|
+
...I2CTemplate.#consumeAck(),
|
|
95
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
96
|
+
])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static writeByte(data: number): Uint8Array<ArrayBuffer> {
|
|
100
|
+
return Uint8Array.from([
|
|
101
|
+
...MPSSETemplate.shiftOutByte(SHIFT_COMMAND.OUT_MSB_BYTES_NEGATIVE_VE, 1, data),
|
|
102
|
+
...I2CTemplate.#consumeAck(),
|
|
103
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
104
|
+
])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static repeatStartWithAddress(addr: number, write = false): Uint8Array<ArrayBuffer> {
|
|
108
|
+
const data = (addr << 1) | (write ? 0 : 1)
|
|
109
|
+
|
|
110
|
+
return Uint8Array.from([
|
|
111
|
+
...I2CTemplate.#start(),
|
|
112
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
113
|
+
...MPSSETemplate.shiftOutByte(SHIFT_COMMAND.OUT_MSB_BYTES_NEGATIVE_VE, 1, data),
|
|
114
|
+
...I2CTemplate.#consumeAck(),
|
|
115
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
116
|
+
])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static readData(ack = true): Uint8Array<ArrayBuffer> {
|
|
120
|
+
const MSB_ACK_BIT = 0x00
|
|
121
|
+
const MSB_NACK_BIT = 0x80
|
|
122
|
+
const data = ack ? MSB_ACK_BIT : MSB_NACK_BIT
|
|
123
|
+
|
|
124
|
+
return Uint8Array.from([
|
|
125
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
126
|
+
...MPSSETemplate.shiftInByte(SHIFT_COMMAND.IN_MSB_BYTES_POSITIVE_VE, 1),
|
|
127
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
128
|
+
...MPSSETemplate.shiftOutBits(SHIFT_COMMAND.OUT_MSB_BITS_NEGATIVE_VE, 1, data),
|
|
129
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
130
|
+
])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static stop(): Uint8Array<ArrayBuffer> {
|
|
134
|
+
return Uint8Array.from([
|
|
135
|
+
// Set SDA low, SCL low
|
|
136
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
137
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
138
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
139
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
140
|
+
...MPSSETemplate.gpioSetLow(SCL_LOW_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
141
|
+
|
|
142
|
+
// Set SDA low, SCL high
|
|
143
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
144
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
145
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
146
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
147
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_LOW, SDA_SCL_DIRECTION_OUT),
|
|
148
|
+
|
|
149
|
+
// Set SDA, SCL high
|
|
150
|
+
...MPSSETemplate.gpioSetLow(SCL_HIGH_SDA_HIGH, SDA_SCL_DIRECTION_OUT),
|
|
151
|
+
|
|
152
|
+
//
|
|
153
|
+
HOST_AND_MPSSE_MODE_COMMANDS.SEND_IMMEDIATE
|
|
154
|
+
])
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/i2c/i2c.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNestedTernary: preserve const assign */
|
|
2
|
+
/** biome-ignore-all lint/performance/noAwaitInLoops: simplify impl */
|
|
3
|
+
/** biome-ignore-all lint/style/useConsistentArrayType: <explanation> */
|
|
4
|
+
import type {
|
|
5
|
+
I2CAddress,
|
|
6
|
+
I2CBufferSource,
|
|
7
|
+
I2CBus,
|
|
8
|
+
I2CCommand,
|
|
9
|
+
I2CReadResult,
|
|
10
|
+
I2CWriteResult
|
|
11
|
+
} from '@johntalton/and-other-delights'
|
|
12
|
+
import type { FTDIDevice } from '../ftdi.ts'
|
|
13
|
+
import { CLOCK_DEFAULT_STANDARD_MODE_100_kHz } from '../mpsse/clock.ts'
|
|
14
|
+
import { DeviceStatus } from '../status.ts'
|
|
15
|
+
import { DEFAULT_DATA_READ_SIZE, Util } from '../util.ts'
|
|
16
|
+
import { I2CTemplate } from './i2c-template.ts'
|
|
17
|
+
import { range } from './range.ts'
|
|
18
|
+
|
|
19
|
+
export function checkAck(data: Uint8Array<ArrayBuffer>): boolean {
|
|
20
|
+
if(data.byteLength !== 1) {
|
|
21
|
+
console.log('more ack', data)
|
|
22
|
+
// throw new Error('not just an ack')
|
|
23
|
+
}
|
|
24
|
+
const [ byte ] = data
|
|
25
|
+
if(byte === undefined) { return false }
|
|
26
|
+
|
|
27
|
+
return (byte & 0b0000_0001) === 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function sendAndReadACK(device: FTDIDevice, transaction: Uint8Array<ArrayBuffer>): Promise<boolean> {
|
|
31
|
+
await device.sendData(transaction)
|
|
32
|
+
const response = await Util.pollData(device)
|
|
33
|
+
return checkAck(response)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function readData(device: FTDIDevice, length: number, targetBuffer?: I2CBufferSource): Promise<I2CBufferSource> {
|
|
37
|
+
const buffer = (targetBuffer === undefined) ?
|
|
38
|
+
new Uint8Array(length) :
|
|
39
|
+
(ArrayBuffer.isView(targetBuffer) ?
|
|
40
|
+
new Uint8Array(targetBuffer.buffer, targetBuffer.byteOffset, length) :
|
|
41
|
+
new Uint8Array(targetBuffer, 0, length))
|
|
42
|
+
|
|
43
|
+
// c0 low - RX-LED-emulation
|
|
44
|
+
// await device.mpsse.setGpioHigh(0b0000_0000, 0b0000_0001)
|
|
45
|
+
|
|
46
|
+
for(let offset = 0; offset < length; offset += 1) {
|
|
47
|
+
const ack = offset + 1 < length // is last byte
|
|
48
|
+
|
|
49
|
+
const readByteTransaction = I2CTemplate.readData(ack)
|
|
50
|
+
await device.sendData(readByteTransaction)
|
|
51
|
+
const byteReadResponse = await Util.pollData(device)
|
|
52
|
+
|
|
53
|
+
buffer.set(new Uint8Array(byteReadResponse.buffer, byteReadResponse.byteOffset, 1), offset)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// c0 high - RX-LED-emulation
|
|
57
|
+
// await device.mpsse.setGpioHigh(0b0000_0001, 0b0000_0001)
|
|
58
|
+
|
|
59
|
+
return buffer
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function writeData(device: FTDIDevice, length: number, buffer: I2CBufferSource): Promise<void> {
|
|
63
|
+
const u8 = ArrayBuffer.isView(buffer) ?
|
|
64
|
+
new Uint8Array(buffer.buffer, buffer.byteOffset, length) :
|
|
65
|
+
new Uint8Array(buffer, 0, length)
|
|
66
|
+
|
|
67
|
+
for(let i = 0; i < length; i += 1) {
|
|
68
|
+
const data = u8[i]
|
|
69
|
+
if(data === undefined) { throw new Error('data byte undefined') }
|
|
70
|
+
|
|
71
|
+
const writeAck = await sendAndReadACK(device, I2CTemplate.writeByte(data))
|
|
72
|
+
if(!writeAck) { throw new Error('write data nack') }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface FT232HBusOptions {
|
|
77
|
+
enableClockDivideBy5?: boolean
|
|
78
|
+
targetClockHz?: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class FT232HBus implements I2CBus {
|
|
82
|
+
readonly name = 'FT232H'
|
|
83
|
+
readonly supportsScan = true
|
|
84
|
+
readonly supportsMultiByteDataAddress = false
|
|
85
|
+
|
|
86
|
+
readonly #device: FTDIDevice
|
|
87
|
+
|
|
88
|
+
static async init(device: FTDIDevice, options?: FT232HBusOptions): Promise<void> {
|
|
89
|
+
const transaction = I2CTemplate.initI2C(options?.targetClockHz ?? CLOCK_DEFAULT_STANDARD_MODE_100_kHz, options?.enableClockDivideBy5 ?? false)
|
|
90
|
+
await device.sendData(transaction)
|
|
91
|
+
|
|
92
|
+
// todo this is direct read, not pollData
|
|
93
|
+
const result = await device.readData(DEFAULT_DATA_READ_SIZE)
|
|
94
|
+
const status = DeviceStatus.parse(result)
|
|
95
|
+
console.log('init status', status)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
constructor(device: FTDIDevice) {
|
|
99
|
+
this.#device = device
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// biome-ignore lint/nursery/useThisInClassMethods: close is a noop
|
|
103
|
+
close(): void {
|
|
104
|
+
// noop
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async scan(): Promise<I2CAddress[]> {
|
|
108
|
+
const result: Array<I2CAddress> = []
|
|
109
|
+
|
|
110
|
+
const I2C_RANGE_START = 0x08
|
|
111
|
+
const I2C_RANGE_END = 0x77
|
|
112
|
+
|
|
113
|
+
for(const addr of range(I2C_RANGE_START, I2C_RANGE_END)) {
|
|
114
|
+
//
|
|
115
|
+
const startAck = await sendAndReadACK(this.#device, I2CTemplate.startWithAddress(addr, true))
|
|
116
|
+
if(startAck) { result.push(addr) }
|
|
117
|
+
|
|
118
|
+
// todo reset or send stop?
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// biome-ignore lint/nursery/useThisInClassMethods: no impl
|
|
125
|
+
async sendByte(_address: I2CAddress, _byteValue: number): Promise<void> {
|
|
126
|
+
throw new Error('Method not implemented.')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async readI2cBlock(address: I2CAddress, cmd: I2CCommand, length: number, targetBuffer?: I2CBufferSource): Promise<I2CReadResult> {
|
|
130
|
+
//
|
|
131
|
+
const startAck = await sendAndReadACK(this.#device, I2CTemplate.startWithAddress(address, true))
|
|
132
|
+
if(!startAck) { throw new Error('start with address nack') }
|
|
133
|
+
|
|
134
|
+
//
|
|
135
|
+
const cmdBuffer = Uint8Array.from(Array.isArray(cmd) ? cmd : [ cmd ])
|
|
136
|
+
await writeData(this.#device, cmdBuffer.byteLength, cmdBuffer)
|
|
137
|
+
|
|
138
|
+
//
|
|
139
|
+
const repeatStartAck = await sendAndReadACK(this.#device, I2CTemplate.repeatStartWithAddress(address))
|
|
140
|
+
if(!repeatStartAck) { throw new Error('repeat start nack') }
|
|
141
|
+
|
|
142
|
+
//
|
|
143
|
+
const buffer = await readData(this.#device, length, targetBuffer)
|
|
144
|
+
|
|
145
|
+
//
|
|
146
|
+
await this.#device.sendData(I2CTemplate.stop())
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
bytesRead: length,
|
|
150
|
+
buffer
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async writeI2cBlock(address: I2CAddress, cmd: I2CCommand, length: number, buffer: I2CBufferSource): Promise<I2CWriteResult> {
|
|
155
|
+
if(Array.isArray(cmd)) { throw new Error('single command byte only') }
|
|
156
|
+
|
|
157
|
+
//
|
|
158
|
+
const startAck = await sendAndReadACK(this.#device, I2CTemplate.startWithAddress(address, true))
|
|
159
|
+
if(!startAck) { throw new Error('start with address nack') }
|
|
160
|
+
|
|
161
|
+
//
|
|
162
|
+
const cmdBuffer = Uint8Array.from(Array.isArray(cmd) ? cmd : [ cmd ])
|
|
163
|
+
await writeData(this.#device, cmdBuffer.byteLength, cmdBuffer)
|
|
164
|
+
|
|
165
|
+
//
|
|
166
|
+
await writeData(this.#device, length, buffer)
|
|
167
|
+
|
|
168
|
+
//
|
|
169
|
+
await this.#device.sendData(I2CTemplate.stop())
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
bytesWritten: length,
|
|
173
|
+
buffer
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async i2cRead(address: I2CAddress, length: number, targetBuffer?: I2CBufferSource): Promise<I2CReadResult> {
|
|
178
|
+
//
|
|
179
|
+
const startAck = await sendAndReadACK(this.#device, I2CTemplate.startWithAddress(address))
|
|
180
|
+
if(!startAck) { throw new Error('start with address nack') }
|
|
181
|
+
|
|
182
|
+
//
|
|
183
|
+
const buffer = await readData(this.#device, length, targetBuffer)
|
|
184
|
+
|
|
185
|
+
//
|
|
186
|
+
await this.#device.sendData(I2CTemplate.stop())
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
bytesRead: length,
|
|
190
|
+
buffer
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async i2cWrite(address: I2CAddress, length: number, buffer: I2CBufferSource): Promise<I2CWriteResult> {
|
|
195
|
+
//
|
|
196
|
+
const startAck = await sendAndReadACK(this.#device, I2CTemplate.startWithAddress(address, true))
|
|
197
|
+
if(!startAck) { throw new Error('start with address nack') }
|
|
198
|
+
|
|
199
|
+
//
|
|
200
|
+
await writeData(this.#device, length, buffer)
|
|
201
|
+
|
|
202
|
+
//
|
|
203
|
+
await this.#device.sendData(I2CTemplate.stop())
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
bytesWritten: length,
|
|
207
|
+
buffer
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
package/src/i2c/index.ts
ADDED
package/src/i2c/range.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** biome-ignore-all lint/performance/noBarrelFile: barrel */
|
|
2
|
+
/** biome-ignore-all lint/performance/noReExportAll: barrel */
|
|
3
|
+
export * from './consts.ts'
|
|
4
|
+
export * from './eeprom.ts'
|
|
5
|
+
export * from './ftdi.ts'
|
|
6
|
+
export * from './mpsse/index.ts'
|
|
7
|
+
export * from './status.ts'
|
|
8
|
+
export * from './util.ts'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
export const CLOCK_BASE_12 = 12_000_000 // 91 Hz - 6 MHz
|
|
3
|
+
export const CLOCK_BASE_60 = 60_000_000 // 457 Hz - 30 MHz
|
|
4
|
+
|
|
5
|
+
export const DIVISOR_MIN = 0
|
|
6
|
+
export const DIVISOR_MAX = 0xFF_FF
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const CLOCK_DEFAULT_STANDARD_MODE_100_kHz = 100 * 1000
|
|
10
|
+
export const CLOCK_DEFAULT_FAST_MODE_400_kHz = 400 * 1000
|
|
11
|
+
export const CLOCK_DEFAULT_FAST_MODE_PLUS_1_MHz = 1 * 1000 * 1000
|
|
12
|
+
export const CLOCK_DEFAULT_HIGH_SPEED_MODE_3_4_MHz = 3.4 * 1000 * 1000
|
|
13
|
+
export const CLOCK_DEFAULT_ULTRA_FAST_MODE_5_MHz = 5 * 1000 * 1000
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export type ClockBase = typeof CLOCK_BASE_12 | typeof CLOCK_BASE_60
|
|
17
|
+
|
|
18
|
+
export class Clock {
|
|
19
|
+
static clockHz(divisor: number, base: ClockBase): number {
|
|
20
|
+
return (base / ((1 + divisor) * 2))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static clockDivisor(hertz: number, base: ClockBase): number {
|
|
24
|
+
const raw = Math.trunc(base / (hertz * 2)) - 1
|
|
25
|
+
|
|
26
|
+
if(raw < DIVISOR_MIN) { return DIVISOR_MIN }
|
|
27
|
+
if(raw > DIVISOR_MAX) { return DIVISOR_MAX }
|
|
28
|
+
return raw
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static hertzToHuman(hertz: number): string {
|
|
32
|
+
if(hertz >= (1000 * 1000)) { return `${hertz / (1000 * 1000)} MHz`}
|
|
33
|
+
if(hertz >= (1000)) { return `${hertz / (1000)} kHz`}
|
|
34
|
+
return `${hertz} Hz`
|
|
35
|
+
}
|
|
36
|
+
}
|