@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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/lib/consts.d.ts +33 -0
  4. package/lib/consts.d.ts.map +1 -0
  5. package/lib/consts.js +42 -0
  6. package/lib/consts.js.map +1 -0
  7. package/lib/eeprom.d.ts +129 -0
  8. package/lib/eeprom.d.ts.map +1 -0
  9. package/lib/eeprom.js +495 -0
  10. package/lib/eeprom.js.map +1 -0
  11. package/lib/ft232h.d.ts +37 -0
  12. package/lib/ft232h.d.ts.map +1 -0
  13. package/lib/ft232h.js +204 -0
  14. package/lib/ft232h.js.map +1 -0
  15. package/lib/ftdi.d.ts +37 -0
  16. package/lib/ftdi.d.ts.map +1 -0
  17. package/lib/ftdi.js +204 -0
  18. package/lib/ftdi.js.map +1 -0
  19. package/lib/i2c/i2c-template.d.ts +10 -0
  20. package/lib/i2c/i2c-template.d.ts.map +1 -0
  21. package/lib/i2c/i2c-template.js +122 -0
  22. package/lib/i2c/i2c-template.js.map +1 -0
  23. package/lib/i2c/i2c.d.ts +29 -0
  24. package/lib/i2c/i2c.d.ts.map +1 -0
  25. package/lib/i2c/i2c.js +169 -0
  26. package/lib/i2c/i2c.js.map +1 -0
  27. package/lib/i2c/index.d.ts +5 -0
  28. package/lib/i2c/index.d.ts.map +1 -0
  29. package/lib/i2c/index.js +5 -0
  30. package/lib/i2c/index.js.map +1 -0
  31. package/lib/i2c/range.d.ts +2 -0
  32. package/lib/i2c/range.d.ts.map +1 -0
  33. package/lib/i2c/range.js +6 -0
  34. package/lib/i2c/range.js.map +1 -0
  35. package/lib/index.d.ts +9 -0
  36. package/lib/index.d.ts.map +1 -0
  37. package/lib/index.js +9 -0
  38. package/lib/index.js.map +1 -0
  39. package/lib/mpsse/clock.d.ts +16 -0
  40. package/lib/mpsse/clock.d.ts.map +1 -0
  41. package/lib/mpsse/clock.js +34 -0
  42. package/lib/mpsse/clock.js.map +1 -0
  43. package/lib/mpsse/command.d.ts +66 -0
  44. package/lib/mpsse/command.d.ts.map +1 -0
  45. package/lib/mpsse/command.js +89 -0
  46. package/lib/mpsse/command.js.map +1 -0
  47. package/lib/mpsse/index.d.ts +7 -0
  48. package/lib/mpsse/index.d.ts.map +1 -0
  49. package/lib/mpsse/index.js +7 -0
  50. package/lib/mpsse/index.js.map +1 -0
  51. package/lib/mpsse/mpsse-template.d.ts +19 -0
  52. package/lib/mpsse/mpsse-template.d.ts.map +1 -0
  53. package/lib/mpsse/mpsse-template.js +81 -0
  54. package/lib/mpsse/mpsse-template.js.map +1 -0
  55. package/lib/mpsse/mpsse.d.ts +18 -0
  56. package/lib/mpsse/mpsse.d.ts.map +1 -0
  57. package/lib/mpsse/mpsse.js +77 -0
  58. package/lib/mpsse/mpsse.js.map +1 -0
  59. package/lib/status.d.ts +39 -0
  60. package/lib/status.d.ts.map +1 -0
  61. package/lib/status.js +62 -0
  62. package/lib/status.js.map +1 -0
  63. package/lib/util.d.ts +26 -0
  64. package/lib/util.d.ts.map +1 -0
  65. package/lib/util.js +57 -0
  66. package/lib/util.js.map +1 -0
  67. package/package.json +38 -0
  68. package/src/consts.ts +57 -0
  69. package/src/eeprom.ts +663 -0
  70. package/src/ftdi.ts +215 -0
  71. package/src/i2c/i2c-template.ts +156 -0
  72. package/src/i2c/i2c.ts +210 -0
  73. package/src/i2c/index.ts +4 -0
  74. package/src/i2c/range.ts +6 -0
  75. package/src/index.ts +8 -0
  76. package/src/mpsse/clock.ts +36 -0
  77. package/src/mpsse/command.ts +113 -0
  78. package/src/mpsse/index.ts +6 -0
  79. package/src/mpsse/mpsse-template.ts +96 -0
  80. package/src/mpsse/mpsse.ts +95 -0
  81. package/src/status.ts +94 -0
  82. 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
+ }
@@ -0,0 +1,4 @@
1
+ /** biome-ignore-all lint/performance/noBarrelFile: barrel */
2
+ /** biome-ignore-all lint/performance/noReExportAll: barrel */
3
+ export * from './i2c.ts'
4
+ export * from './i2c-template.ts'
@@ -0,0 +1,6 @@
1
+
2
+ export function* range(start: number, end: number, step = 1): Generator<number> {
3
+ for(let i = start; i <= end; i += step) {
4
+ yield i
5
+ }
6
+ }
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
+ }