driverge-mcp 0.0.0 → 0.0.1
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/README.md +275 -29
- package/dist/codegen/esp32.d.ts +11 -3
- package/dist/codegen/esp32.js +221 -10
- package/dist/codegen/esp32.js.map +1 -1
- package/dist/codegen/ident.d.ts +24 -2
- package/dist/codegen/ident.js +37 -4
- package/dist/codegen/ident.js.map +1 -1
- package/dist/codegen/index.d.ts +12 -7
- package/dist/codegen/index.js +17 -14
- package/dist/codegen/index.js.map +1 -1
- package/dist/codegen/lint.js +28 -8
- package/dist/codegen/lint.js.map +1 -1
- package/dist/codegen/portable-cpp.d.ts +8 -0
- package/dist/codegen/portable-cpp.js +281 -0
- package/dist/codegen/portable-cpp.js.map +1 -0
- package/dist/codegen/portable.d.ts +99 -3
- package/dist/codegen/portable.js +419 -99
- package/dist/codegen/portable.js.map +1 -1
- package/dist/codegen/stm32.d.ts +11 -3
- package/dist/codegen/stm32.js +156 -11
- package/dist/codegen/stm32.js.map +1 -1
- package/dist/codegen/types.d.ts +32 -0
- package/dist/codegen/types.js +27 -0
- package/dist/codegen/types.js.map +1 -1
- package/dist/mcp/cache.d.ts +15 -2
- package/dist/mcp/cache.js +32 -4
- package/dist/mcp/cache.js.map +1 -1
- package/dist/mcp/register.d.ts +17 -0
- package/dist/mcp/register.js +149 -21
- package/dist/mcp/register.js.map +1 -1
- package/dist/mcp/summary.js +8 -0
- package/dist/mcp/summary.js.map +1 -1
- package/dist/pdf/address.d.ts +4 -0
- package/dist/pdf/address.js +28 -0
- package/dist/pdf/address.js.map +1 -0
- package/dist/pdf/command.js +87 -14
- package/dist/pdf/command.js.map +1 -1
- package/dist/pdf/extract.js +23 -2
- package/dist/pdf/extract.js.map +1 -1
- package/dist/pdf/generic-register-table.d.ts +5 -0
- package/dist/pdf/generic-register-table.js +125 -0
- package/dist/pdf/generic-register-table.js.map +1 -0
- package/dist/pdf/index.d.ts +2 -0
- package/dist/pdf/index.js +2 -0
- package/dist/pdf/index.js.map +1 -1
- package/dist/pdf/interface-kind.d.ts +11 -0
- package/dist/pdf/interface-kind.js +18 -0
- package/dist/pdf/interface-kind.js.map +1 -1
- package/dist/pdf/manufacturer.js +39 -0
- package/dist/pdf/manufacturer.js.map +1 -1
- package/dist/pdf/maxim-register-map.d.ts +11 -0
- package/dist/pdf/maxim-register-map.js +544 -0
- package/dist/pdf/maxim-register-map.js.map +1 -0
- package/dist/pdf/part.js +8 -0
- package/dist/pdf/part.js.map +1 -1
- package/dist/pdf/prose-commands.d.ts +4 -0
- package/dist/pdf/prose-commands.js +90 -0
- package/dist/pdf/prose-commands.js.map +1 -0
- package/dist/pdf/st-bit-layout.d.ts +3 -0
- package/dist/pdf/st-bit-layout.js +238 -0
- package/dist/pdf/st-bit-layout.js.map +1 -0
- package/dist/pdf/ti-field-descriptions.d.ts +10 -0
- package/dist/pdf/ti-field-descriptions.js +87 -0
- package/dist/pdf/ti-field-descriptions.js.map +1 -0
- package/dist/pdf/ti-register-map.d.ts +5 -0
- package/dist/pdf/ti-register-map.js +84 -0
- package/dist/pdf/ti-register-map.js.map +1 -0
- package/dist/pdf/types.d.ts +15 -3
- package/dist/pdf/types.js +4 -1
- package/dist/pdf/types.js.map +1 -1
- package/dist/schema/assemble.d.ts +35 -3
- package/dist/schema/assemble.js +86 -13
- package/dist/schema/assemble.js.map +1 -1
- package/dist/schema/types.d.ts +21 -1
- package/dist/schema/validate.js +37 -10
- package/dist/schema/validate.js.map +1 -1
- package/dist/server.d.ts +18 -0
- package/dist/server.js +49 -3
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/schemas/datasheet.schema.json +17 -3
package/README.md
CHANGED
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
<p align="center"><em>Datasheet PDF → embedded C/C++ driver, from any MCP client.</em></p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
|
-
<img alt="
|
|
7
|
+
<a href="https://www.npmjs.com/package/driverge-mcp"><img alt="npm" src="https://img.shields.io/npm/v/driverge-mcp"></a>
|
|
8
|
+
<a href="https://github.com/MehmetTopuz/driverge-mcp/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/MehmetTopuz/driverge-mcp/actions/workflows/ci.yml/badge.svg"></a>
|
|
9
|
+
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
|
|
8
10
|
<img alt="status" src="https://img.shields.io/badge/status-pre--release-orange">
|
|
9
11
|
<img alt="mcp" src="https://img.shields.io/badge/MCP-server-black">
|
|
10
12
|
</p>
|
|
11
13
|
|
|
12
|
-
> 🚧 **
|
|
13
|
-
> `npx driverge-mcp`
|
|
14
|
-
>
|
|
14
|
+
> 🚧 **Early pre-release** — [`driverge-mcp`](https://www.npmjs.com/package/driverge-mcp)
|
|
15
|
+
> is on npm, so the `npx driverge-mcp` install below works today. APIs and the
|
|
16
|
+
> JSON schema may still change before v0.1.0; expect rough edges.
|
|
15
17
|
|
|
16
18
|
---
|
|
17
19
|
|
|
@@ -27,15 +29,68 @@ TypeScript pipeline extracts a *validated, structured JSON* model of the chip, a
|
|
|
27
29
|
the host AI you're already talking to fills in the reasoning-heavy parts (init
|
|
28
30
|
sequence, vendor quirks, docs). Your datasheet never leaves your machine.
|
|
29
31
|
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
Add one entry to your MCP client's config (Claude Desktop, Claude Code, Cursor —
|
|
35
|
+
paths per client in [Installation](#installation)):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"driverge": { "command": "npx", "args": ["-y", "driverge-mcp"] }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Restart the client, then ask:
|
|
46
|
+
|
|
47
|
+
> "Analyze the datasheet at `C:/ds/bme280.pdf` with driverge, then generate a
|
|
48
|
+
> portable driver."
|
|
49
|
+
|
|
50
|
+
That's it — no clone, no build, no API key. Details in
|
|
51
|
+
[Installation](#installation) and [Usage](#usage).
|
|
52
|
+
|
|
53
|
+
## Why Driverge?
|
|
54
|
+
|
|
55
|
+
Bringing up a new sensor or IC means hand-transcribing dozens of register
|
|
56
|
+
addresses, bit-field masks, and command codes out of a 40-page PDF — slow work,
|
|
57
|
+
and a classic source of silent bugs (one wrong mask or transposed address and the
|
|
58
|
+
driver "works" but reads garbage). Driverge does that mechanical part
|
|
59
|
+
deterministically and leaves the reasoning to the AI you already use.
|
|
60
|
+
|
|
61
|
+
- **No hallucinated register maps.** Addresses, bit-field masks, and command codes
|
|
62
|
+
are *extracted from the datasheet and validated* — not guessed. That's the
|
|
63
|
+
failure mode of "just ask an LLM to write the whole driver"; here, invalid or
|
|
64
|
+
incomplete data is rejected before it ever reaches code generation.
|
|
65
|
+
- **Bring your own client — no API keys, no lock-in.** Driverge is a plain MCP
|
|
66
|
+
server with no embedded LLM. It runs inside whatever MCP client you already use
|
|
67
|
+
(Claude Desktop, Claude Code, Cursor, …) and reasons with the model you're
|
|
68
|
+
already paying for — no separate subscription or service.
|
|
69
|
+
- **Private & offline.** The datasheet is parsed locally and never uploaded — safe
|
|
70
|
+
for NDA'd or unreleased parts.
|
|
71
|
+
- **Deterministic & reproducible.** The same PDF always yields the same JSON and
|
|
72
|
+
the same driver skeleton — reviewable, diff-able, and testable, not a one-shot
|
|
73
|
+
black box.
|
|
74
|
+
- **Portable by construction.** One driver core targets any platform through a
|
|
75
|
+
tiny per-bus thin-HAL seam (2–3 functions); the native targets (STM32, ESP32)
|
|
76
|
+
pre-fill that seam for you — switch platforms without touching driver logic.
|
|
77
|
+
- **The AI does only what it's good at.** Register geometry is deterministic;
|
|
78
|
+
init-sequence ordering, timing quirks, and compensation math need judgment.
|
|
79
|
+
Driverge marks exactly those spots with `TODO(driverge)` and a `fill_in_brief`,
|
|
80
|
+
the host AI completes them, then `validate_driver` checks the result.
|
|
81
|
+
|
|
82
|
+
**Good for:** quickly evaluating a new sensor, prototyping, porting an existing
|
|
83
|
+
driver to a different MCU, or just learning an unfamiliar chip's register map.
|
|
84
|
+
|
|
30
85
|
## What it does
|
|
31
86
|
|
|
32
87
|
1. **Analyze** a datasheet PDF → detect format, manufacturer, and interface kind
|
|
33
88
|
(register-map vs. command-set), then extract registers / bit-fields (or
|
|
34
89
|
commands + CRC) and the bus protocol into a **frozen JSON contract**, gated by
|
|
35
90
|
a validator.
|
|
36
|
-
2. **Generate** a driver for a target platform
|
|
37
|
-
skeleton** — register/bit-field constants, the
|
|
38
|
-
stubs — with every reasoning gap marked `TODO(driverge)` plus a `fill_in_brief`
|
|
91
|
+
2. **Generate** a driver for a target platform, in C or C++: a deterministic
|
|
92
|
+
**thin-HAL skeleton** — register/bit-field constants, the per-bus thin-HAL
|
|
93
|
+
seam, function stubs — with every reasoning gap marked `TODO(driverge)` plus a `fill_in_brief`
|
|
39
94
|
telling the host AI exactly what to complete.
|
|
40
95
|
3. **Validate** the completed driver: thin-HAL purity, no leftover TODOs, register
|
|
41
96
|
references exist, bit-field masks match the JSON.
|
|
@@ -46,26 +101,132 @@ Every target specializes the same portable **[thin-HAL](https://en.wikipedia.org
|
|
|
46
101
|
seam — the driver core is identical across platforms; only the seam implementation
|
|
47
102
|
changes.
|
|
48
103
|
|
|
49
|
-
| Target | Bus binding | Language | Status |
|
|
50
|
-
|
|
51
|
-
| **Portable (thin-HAL)** | user-implemented `
|
|
52
|
-
| **ESP32** | ESP-IDF `i2c_master_
|
|
53
|
-
| **STM32** | CubeHAL `
|
|
54
|
-
| **Arduino** | `Wire` / `SPI` | C++ | planned |
|
|
104
|
+
| Target | Bus binding | Buses | Language | Status |
|
|
105
|
+
|---|---|---|---|---|
|
|
106
|
+
| **Portable (thin-HAL)** | user-implemented `hal_*` seam | I²C, SPI, UART, CAN | C / C++ | ✅ |
|
|
107
|
+
| **ESP32** | ESP-IDF (`i2c_master_*`, `spi_master`, `uart`, TWAI) | I²C, SPI, UART, CAN | C / C++ | ✅ |
|
|
108
|
+
| **STM32** | CubeHAL (`HAL_I2C_*`, `HAL_SPI_*` + GPIO CS, `HAL_UART_*`) | I²C, SPI, UART | C / C++ | ✅ |
|
|
109
|
+
| **Arduino** | `Wire` / `SPI` | — | C++ | planned |
|
|
55
110
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
111
|
+
STM32 CAN is planned (the bxCAN/FDCAN family split needs its own pass); asking
|
|
112
|
+
a target for a bus it doesn't support fails fast with a clear
|
|
113
|
+
`UnsupportedBusError` rather than emitting a wrong seam. Pass
|
|
114
|
+
`language: "cpp"` to `generate_driver` for a class-based C++ driver
|
|
115
|
+
(`.hpp`/`.cpp`) instead of the default C output — same registers, same seam,
|
|
116
|
+
same validation.
|
|
59
117
|
|
|
60
118
|
> ⚠️ **Generated code is a strong draft, not a certified driver.** Init sequences,
|
|
61
119
|
> compensation formulas, and timing quirks are completed by the host AI and
|
|
62
120
|
> **must be reviewed** before use on hardware. Not safety-certified.
|
|
63
121
|
|
|
122
|
+
### Verified parts
|
|
123
|
+
|
|
124
|
+
The extraction pipeline is regression-tested against real datasheets from
|
|
125
|
+
**12 manufacturers**. Parts with fully automatic extraction (registers *and*
|
|
126
|
+
bit-fields, or a clean command set):
|
|
127
|
+
|
|
128
|
+
| Part | Manufacturer | Kind | Extracted |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| BME280 | Bosch Sensortec | register map | 16 regs, 19 bit-fields |
|
|
131
|
+
| MCP23017 | Microchip | register map | 12 regs, 96 bit-fields |
|
|
132
|
+
| SHT3x | Sensirion | command set | 6 commands + CRC |
|
|
133
|
+
| TMAG5170 | Texas Instruments | register map (SPI) | 21 regs, 79 bit-fields |
|
|
134
|
+
| DHT20 | Aosong | command set | 2 commands |
|
|
135
|
+
| LSM6DSRX | STMicroelectronics | register map | 31 regs, 91 bit-fields |
|
|
136
|
+
| MAX30102 | Maxim Integrated | register map | 20 regs, 33 bit-fields |
|
|
137
|
+
|
|
138
|
+
Other tested parts (ADXL345, MLX90614, AEAT-8811, PCA9685, VL53L3CX, TLE5014)
|
|
139
|
+
extract **partially** or **defer** to the host AI — the pipeline says so
|
|
140
|
+
explicitly instead of guessing, and the generated skeleton tells the host AI
|
|
141
|
+
what to complete. The full, always-current matrix lives in the
|
|
142
|
+
[coverage scorecard](tests/scorecard/scorecard.snap.md).
|
|
143
|
+
|
|
144
|
+
## Concepts behind Driverge
|
|
145
|
+
|
|
146
|
+
Driverge splits driver-writing into two kinds of work: the **mechanical part**
|
|
147
|
+
(register addresses, masks, command tables — extracted and checked by
|
|
148
|
+
deterministic code) and the **judgment part** (init ordering, timing quirks,
|
|
149
|
+
compensation math — completed by the host AI). Everything below exists to keep
|
|
150
|
+
that boundary sharp.
|
|
151
|
+
|
|
152
|
+
```mermaid
|
|
153
|
+
flowchart LR
|
|
154
|
+
PDF["Datasheet PDF"]
|
|
155
|
+
subgraph D["Driverge — deterministic, no LLM"]
|
|
156
|
+
A["analyze_datasheet<br>L1–L5 parse + validate"]
|
|
157
|
+
J[("frozen JSON<br>cached under a ref")]
|
|
158
|
+
G["generate_driver<br>thin-HAL skeleton +<br>TODO(driverge) markers"]
|
|
159
|
+
V["validate_driver<br>static lint"]
|
|
160
|
+
end
|
|
161
|
+
subgraph H["Host AI — reasoning"]
|
|
162
|
+
F["fill the TODOs: init sequence,<br>quirks, compensation docs"]
|
|
163
|
+
end
|
|
164
|
+
OUT["driver.c / driver.h"]
|
|
165
|
+
PDF --> A --> J --> G --> F --> V
|
|
166
|
+
V -->|pass| OUT
|
|
167
|
+
V -->|fail| F
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Deterministic core, reasoning at the edge
|
|
171
|
+
|
|
172
|
+
Register geometry is mechanical: an address is right or wrong, a mask either
|
|
173
|
+
matches the datasheet or it doesn't. Driverge handles that part with plain
|
|
174
|
+
TypeScript — no internal LLM, no API keys, no sampling — so the output is the
|
|
175
|
+
same on every run. What genuinely needs judgment (in what order to poke the
|
|
176
|
+
registers, which timing quirk applies, how to document a compensation formula)
|
|
177
|
+
is left to the host AI you're already talking to.
|
|
178
|
+
|
|
179
|
+
### The frozen JSON contract
|
|
180
|
+
|
|
181
|
+
`analyze_datasheet` runs a five-stage pipeline (L1–L5): detect the PDF type,
|
|
182
|
+
map keyword pages, identify the manufacturer and interface kind, extract the
|
|
183
|
+
register table (or command set + CRC), and validate the result against a frozen
|
|
184
|
+
draft-07 [JSON-Schema contract](schemas/datasheet.schema.json) — also exposed
|
|
185
|
+
as the `driverge://schema` resource. Anything that fails validation is rejected
|
|
186
|
+
*before* code generation, so a bad extraction can never silently become a bad
|
|
187
|
+
driver.
|
|
188
|
+
|
|
189
|
+
### The `ref` handle
|
|
190
|
+
|
|
191
|
+
Parsing a datasheet yields a large JSON document; shuttling it through the chat
|
|
192
|
+
context on every call would be slow and lossy. Instead, the parsed model is
|
|
193
|
+
cached server-side under a content-stable `ref`, and the tools pass that handle
|
|
194
|
+
around. The same `ref` with a different `target` re-renders instantly with no
|
|
195
|
+
re-parse, and the full JSON stays readable at `driverge://datasheet/<ref>`.
|
|
196
|
+
|
|
197
|
+
### The thin-HAL seam
|
|
198
|
+
|
|
199
|
+
Generated drivers touch hardware through a tiny per-bus seam — and nothing
|
|
200
|
+
else:
|
|
201
|
+
|
|
202
|
+
| Bus | Seam functions (plus `hal_delay_ms`) |
|
|
203
|
+
|---|---|
|
|
204
|
+
| I²C | `hal_i2c_read`, `hal_i2c_write` |
|
|
205
|
+
| SPI | `hal_spi_transfer` (one call = one CS-framed transaction) |
|
|
206
|
+
| UART | `hal_uart_write`, `hal_uart_read` |
|
|
207
|
+
| CAN | `hal_can_transfer` (one call = one frame exchange) |
|
|
208
|
+
|
|
209
|
+
The driver core is therefore identical across platforms; a native target
|
|
210
|
+
(ESP32, STM32) just pre-fills the seam with the vendor calls.
|
|
211
|
+
`validate_driver` enforces this purity: a driver that calls a vendor peripheral
|
|
212
|
+
API outside the seam fails the lint. Buses with no universal register-access
|
|
213
|
+
primitive (UART, CAN) keep the same discipline — the device-specific framing is
|
|
214
|
+
a marked `TODO(driverge)` reasoning gap (`framing_todo`), completed by the host
|
|
215
|
+
AI and then linted.
|
|
216
|
+
|
|
217
|
+
### The fill-in loop
|
|
218
|
+
|
|
219
|
+
The skeleton marks every reasoning gap with a `TODO(driverge)` comment and
|
|
220
|
+
ships a `fill_in_brief` describing what belongs there. The host AI completes
|
|
221
|
+
the markers using the datasheet resource, then `validate_driver` statically
|
|
222
|
+
checks the result — no leftover TODOs, every register reference real, masks
|
|
223
|
+
matching the JSON — and the loop repeats until it passes.
|
|
224
|
+
|
|
64
225
|
## Installation
|
|
65
226
|
|
|
66
|
-
**Prerequisites:** Node.js LTS (≥ 18).
|
|
227
|
+
**Prerequisites:** Node.js LTS (≥ 18; CI-tested on Node 20 & 22).
|
|
67
228
|
|
|
68
|
-
|
|
229
|
+
Add Driverge to your MCP client (no build step —
|
|
69
230
|
[npx](https://docs.npmjs.com/cli/commands/npx) fetches and runs it):
|
|
70
231
|
|
|
71
232
|
**Claude Desktop** — `claude_desktop_config.json`:
|
|
@@ -98,9 +259,39 @@ Once published, add Driverge to your MCP client (no build step —
|
|
|
98
259
|
Other clients (Codex, Gemini CLI, …) take the same `command` + `args` pair in
|
|
99
260
|
their own MCP config.
|
|
100
261
|
|
|
101
|
-
|
|
262
|
+
**No clone, no global install.** The config above tells your MCP client to
|
|
263
|
+
launch `npx -y driverge-mcp`; npx downloads Driverge from the npm registry on
|
|
264
|
+
first run, caches it, and starts it automatically each time the client does
|
|
265
|
+
(`-y` skips npx's install prompt). You never run it by hand — to confirm it's
|
|
266
|
+
wired up, ask your client to run the `ping` tool, which replies `pong`. Cloning
|
|
267
|
+
the repo (below) is only for development.
|
|
102
268
|
|
|
103
|
-
|
|
269
|
+
### Configuration
|
|
270
|
+
|
|
271
|
+
| Env var | Default | Purpose |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `DRIVERGE_OUT_ROOT` | server's working directory | Root that `generate_driver`'s `out_dir` writes are confined to. Any `out_dir` that resolves outside this root is rejected (`out_dir "…" escapes the allowed root`). Set it to the directory you want drivers written into. |
|
|
274
|
+
|
|
275
|
+
Set it in the MCP config's `env` block, e.g.:
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"mcpServers": {
|
|
280
|
+
"driverge": {
|
|
281
|
+
"command": "npx",
|
|
282
|
+
"args": ["-y", "driverge-mcp"],
|
|
283
|
+
"env": { "DRIVERGE_OUT_ROOT": "C:/work/drivers" }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Without `out_dir` the generated files are returned in the tool result only —
|
|
290
|
+
no disk writes, no configuration needed.
|
|
291
|
+
|
|
292
|
+
### Run from source (development)
|
|
293
|
+
|
|
294
|
+
To contribute, or to run the latest unreleased changes:
|
|
104
295
|
|
|
105
296
|
```bash
|
|
106
297
|
git clone https://github.com/MehmetTopuz/driverge-mcp.git
|
|
@@ -124,8 +315,13 @@ Give your MCP client a datasheet and ask it to build a driver. The typical flow:
|
|
|
124
315
|
|
|
125
316
|
1. **`analyze_datasheet`** — `{ "pdf_path": "/abs/path/bme280.pdf" }` → returns a
|
|
126
317
|
compact summary and a `ref` handle; the full JSON is available as a resource.
|
|
318
|
+
If auto-detection picks the wrong vendor or interface style, the optional
|
|
319
|
+
`manufacturer_hint` (free text) and `interface_kind_hint`
|
|
320
|
+
(`"register_map"` | `"command_set"`) parameters steer it.
|
|
127
321
|
2. **`generate_driver`** — `{ "ref": "…", "target": "portable" }` → returns the
|
|
128
|
-
driver files + a `fill_in_brief`. (`out_dir` also writes them to disk
|
|
322
|
+
driver files + a `fill_in_brief`. (`out_dir` also writes them to disk;
|
|
323
|
+
`language: "cpp"` renders a class-based `.hpp`/`.cpp` driver instead of the
|
|
324
|
+
default C.)
|
|
129
325
|
3. The host AI completes the `TODO(driverge)` markers using the brief and the
|
|
130
326
|
`driverge://datasheet/<ref>` resource.
|
|
131
327
|
4. **`validate_driver`** — `{ "ref": "…", "files": [...] }` → static checks; loop
|
|
@@ -138,11 +334,35 @@ Reusing the same `ref` with a different `target` re-renders with **no re-parse**
|
|
|
138
334
|
> "Analyze the datasheet at `C:/ds/bme280.pdf` with driverge, then generate a
|
|
139
335
|
> portable driver."
|
|
140
336
|
|
|
141
|
-
Driverge parses the BME280 memory map (
|
|
142
|
-
validates it, and emits `bme280.h` / `bme280.c` — register
|
|
143
|
-
`MASK`/`SHIFT` macros, the thin-HAL seam, and
|
|
144
|
-
write_register` stubs
|
|
145
|
-
|
|
337
|
+
Driverge parses the BME280 memory map (16 registers, 19 bit-fields, I²C
|
|
338
|
+
address), validates it, and emits `bme280.h` / `bme280.c` — register
|
|
339
|
+
`#define`s, bit-field `MASK`/`SHIFT` macros, the thin-HAL seam, and the
|
|
340
|
+
`bme280_init/read_register/write_register` stubs:
|
|
341
|
+
|
|
342
|
+
```c
|
|
343
|
+
/* bme280.h — generated by Driverge (excerpt) */
|
|
344
|
+
#define BME280_I2C_ADDR 0x76
|
|
345
|
+
|
|
346
|
+
#define BME280_REG_CTRL_MEAS 0xF4
|
|
347
|
+
#define BME280_REG_STATUS 0xF3
|
|
348
|
+
/* … */
|
|
349
|
+
#define BME280_TEMP_XLSB_TEMP_XLSB_MASK 0xF0
|
|
350
|
+
#define BME280_TEMP_XLSB_TEMP_XLSB_SHIFT 4
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
```c
|
|
354
|
+
/* bme280.c — every reasoning gap is a marked TODO (excerpt) */
|
|
355
|
+
int bme280_init(bme280_t *dev) {
|
|
356
|
+
/* … */
|
|
357
|
+
/* TODO(driverge): implement the power-on / reset init sequence — the
|
|
358
|
+
* correct register write order and values, plus any required startup
|
|
359
|
+
* delay. See fill_in_brief.init_sequence_todo. */
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The host AI then fills the init sequence and compensation docs from the
|
|
365
|
+
datasheet prose, and `validate_driver` checks the result.
|
|
146
366
|
|
|
147
367
|
### MCP surface
|
|
148
368
|
|
|
@@ -152,16 +372,42 @@ docs from the datasheet prose.
|
|
|
152
372
|
| Tool | `generate_driver` | `ref` + `target` → driver skeleton + `fill_in_brief` |
|
|
153
373
|
| Tool | `validate_driver` | static-lint a completed driver against its `ref` |
|
|
154
374
|
| Tool | `validate_datasheet` | re-run the L5 validator over a `ref` or JSON |
|
|
375
|
+
| Tool | `ping` | health check — confirms the server is running |
|
|
155
376
|
| Resource | `driverge://datasheet/<ref>` | full parsed JSON for an analyzed datasheet |
|
|
156
377
|
| Resource | `driverge://schema` | the frozen datasheet JSON-Schema contract |
|
|
157
378
|
| Prompt | `generate-driver` | guided analyze → generate → fill → validate flow |
|
|
158
379
|
|
|
380
|
+
## Troubleshooting
|
|
381
|
+
|
|
382
|
+
- **Is the server even running?** Ask your client to run the `ping` tool — it
|
|
383
|
+
replies `pong`. If npx fails to start, check `node --version` (≥ 18 required).
|
|
384
|
+
- **Scanned / image-only PDFs.** Driverge parses text-based PDFs; scanned or
|
|
385
|
+
mixed PDFs are detected and reported with an explicit warning, and parsing
|
|
386
|
+
will be incomplete (OCR is deferred to a future release).
|
|
387
|
+
- **`extraction: partial` or `deferred` is not a failure.** `partial` means the
|
|
388
|
+
registers were found but detail (e.g. bit-fields) is incomplete; `deferred`
|
|
389
|
+
means the register/command section was detected but not auto-extracted. In
|
|
390
|
+
both cases the generated skeleton tells the host AI exactly what to complete
|
|
391
|
+
from the datasheet resource. Only `none` is a genuine parse failure.
|
|
392
|
+
- **`out_dir "…" escapes the allowed root`.** Disk writes are confined under
|
|
393
|
+
`DRIVERGE_OUT_ROOT` (default: the server's working directory) — see
|
|
394
|
+
[Configuration](#configuration).
|
|
395
|
+
- **`UnsupportedBusError` on a native target.** The target doesn't support that
|
|
396
|
+
part's bus yet (today that means CAN on STM32, or a bus the parser couldn't
|
|
397
|
+
identify). Generate the **portable** target instead and implement its seam.
|
|
398
|
+
|
|
159
399
|
## Roadmap
|
|
160
400
|
|
|
161
401
|
- **v0.x** — one reference sensor (BME280), portable thin-HAL core, MCP surface,
|
|
162
|
-
multiple clients.
|
|
163
|
-
- **v0.y** — native targets: ESP32 ✅, STM32 ✅, Arduino (next)
|
|
164
|
-
|
|
402
|
+
multiple clients. ✅
|
|
403
|
+
- **v0.y** — native targets: ESP32 ✅, STM32 ✅, Arduino (next);
|
|
404
|
+
multi-manufacturer extraction ✅ (12 vendors tested — see
|
|
405
|
+
[Verified parts](#verified-parts)); multi-bus seam families (I²C, SPI, UART,
|
|
406
|
+
CAN) ✅; C or C++ output ✅. *(current)*
|
|
407
|
+
- **v1.0** — broader vendor/part coverage, STM32 CAN (bxCAN/FDCAN), and a
|
|
408
|
+
stable, versioned JSON schema.
|
|
409
|
+
|
|
410
|
+
Day-to-day progress is tracked in the [CHANGELOG](CHANGELOG.md).
|
|
165
411
|
|
|
166
412
|
## Contributing
|
|
167
413
|
|
package/dist/codegen/esp32.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { DatasheetJson } from "../schema/types.js";
|
|
2
|
-
import type { DriverArtifact } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
2
|
+
import type { CodegenLanguage, DriverArtifact } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Portable core + an ESP-IDF seam implementation for the same part. With
|
|
5
|
+
* language "c" (default) the seam is `<slug>_hal_esp32.c`, unchanged from
|
|
6
|
+
* before. With language "cpp" the seam becomes `<slug>_hal_esp32.cpp` and its
|
|
7
|
+
* core include is `#include "<slug>.hpp"` (see coreInclude/seamPath above) —
|
|
8
|
+
* everything else in the seam content is identical either way.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateEsp32Driver(json: DatasheetJson, opts?: {
|
|
11
|
+
language?: CodegenLanguage;
|
|
12
|
+
}): DriverArtifact;
|
package/dist/codegen/esp32.js
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
// ESP32 (ESP-IDF) target. Per platform-code-generation-targets, a native target
|
|
2
2
|
// does NOT replace the thin HAL — it *pre-fills* the seam. So the ESP32 driver is
|
|
3
3
|
// the exact portable core (unchanged, still platform-agnostic) PLUS a
|
|
4
|
-
// <part>_hal_esp32.c that implements
|
|
5
|
-
//
|
|
4
|
+
// <part>_hal_esp32.c that implements the bus's thin-HAL seam + hal_delay_ms with
|
|
5
|
+
// ESP-IDF v5 drivers. The user drops all three files into an ESP-IDF component;
|
|
6
6
|
// only the board's bus/pin bring-up stays a TODO(driverge).
|
|
7
|
+
//
|
|
8
|
+
// I2C/SPI/UART/CAN only: the I2C seam references `${PREFIX}_I2C_ADDR`, a macro
|
|
9
|
+
// the portable core only defines for I2C parts; the SPI seam needs the portable
|
|
10
|
+
// core's hal_spi_transfer declaration; the UART seam (Session B) wraps ESP-IDF's
|
|
11
|
+
// uart driver around hal_uart_write/hal_uart_read; the CAN seam (Session C)
|
|
12
|
+
// wraps ESP-IDF's TWAI (Two-Wire Automotive Interface, ESP-IDF's CAN 2.0
|
|
13
|
+
// controller) driver around hal_can_transfer — any other bus (unknown) has no
|
|
14
|
+
// ESP-IDF seam here, so this target refuses (UnsupportedBusError) rather than
|
|
15
|
+
// emit an uncompilable seam.
|
|
7
16
|
import { prefixOf, slug } from "./ident.js";
|
|
17
|
+
import { generatePortableCppDriver } from "./portable-cpp.js";
|
|
8
18
|
import { generatePortableDriver } from "./portable.js";
|
|
9
|
-
|
|
19
|
+
import { UnsupportedBusError } from "./types.js";
|
|
20
|
+
// Contract amendment (post-GREEN 4b3da14/d576ba6): a language:"cpp" seam
|
|
21
|
+
// cannot stay byte-identical to the "c" seam, because that file
|
|
22
|
+
// #include's "<slug>.h" — which doesn't exist in a cpp bundle (only
|
|
23
|
+
// <slug>.hpp/.cpp). So with language "cpp" the seam file itself becomes
|
|
24
|
+
// `<slug>_hal_<target>.cpp` and its core include becomes `#include
|
|
25
|
+
// "<slug>.hpp"`; everything else in the seam is unchanged. This costs
|
|
26
|
+
// nothing: the .hpp declares the hal_* seam inside `extern "C"`, so compiling
|
|
27
|
+
// this seam as C++ still emits plain C-linkage definitions the class methods
|
|
28
|
+
// can call, and both ESP-IDF and a C++-enabled CubeMX project compile .cpp
|
|
29
|
+
// sources natively.
|
|
30
|
+
function coreInclude(name, language) {
|
|
31
|
+
return `#include "${name}.${language === "cpp" ? "hpp" : "h"}"`;
|
|
32
|
+
}
|
|
33
|
+
function seamPath(name, language) {
|
|
34
|
+
return `${name}_hal_esp32.${language === "cpp" ? "cpp" : "c"}`;
|
|
35
|
+
}
|
|
36
|
+
function halImplI2c(name, prefix, language) {
|
|
10
37
|
const content = [
|
|
11
38
|
"/*",
|
|
12
39
|
` * ${prefix} — ESP-IDF (i2c_master) HAL seam implementation (generated by Driverge).`,
|
|
@@ -16,7 +43,7 @@ function halImpl(name, prefix) {
|
|
|
16
43
|
` * i2c_new_master_bus() for your SDA/SCL pins, then call ${name}_esp32_bind()`,
|
|
17
44
|
" * once before using the driver. The driver core stays byte-for-byte portable.",
|
|
18
45
|
" */",
|
|
19
|
-
|
|
46
|
+
coreInclude(name, language),
|
|
20
47
|
"#include <string.h>",
|
|
21
48
|
'#include "driver/i2c_master.h"',
|
|
22
49
|
'#include "freertos/FreeRTOS.h"',
|
|
@@ -60,18 +87,202 @@ function halImpl(name, prefix) {
|
|
|
60
87
|
"}",
|
|
61
88
|
"",
|
|
62
89
|
].join("\n");
|
|
63
|
-
return { path:
|
|
90
|
+
return { path: seamPath(name, language), content };
|
|
91
|
+
}
|
|
92
|
+
function halImplSpi(name, prefix, language) {
|
|
93
|
+
const content = [
|
|
94
|
+
"/*",
|
|
95
|
+
` * ${prefix} — ESP-IDF (spi_master) HAL seam implementation (generated by Driverge).`,
|
|
96
|
+
" *",
|
|
97
|
+
" * Drop this beside the portable core; it fills the thin-HAL seam with the",
|
|
98
|
+
" * ESP-IDF v5 spi_master driver: one half-duplex, polling transaction per",
|
|
99
|
+
` * hal_spi_transfer() call. In your app: bring up the SPI bus with`,
|
|
100
|
+
" * spi_bus_initialize() for your MOSI/MISO/SCLK pins, then call",
|
|
101
|
+
` * ${name}_esp32_bind() once before using the driver. The driver core stays`,
|
|
102
|
+
" * byte-for-byte portable.",
|
|
103
|
+
" */",
|
|
104
|
+
coreInclude(name, language),
|
|
105
|
+
"#include <string.h>",
|
|
106
|
+
'#include "driver/spi_master.h"',
|
|
107
|
+
'#include "freertos/FreeRTOS.h"',
|
|
108
|
+
'#include "freertos/task.h"',
|
|
109
|
+
"",
|
|
110
|
+
`static spi_device_handle_t s_${name}_dev;`,
|
|
111
|
+
"",
|
|
112
|
+
"/* Add the device to an already-initialized SPI bus (spi_bus_initialize'd",
|
|
113
|
+
" * `host`) and keep the handle for the hal_* calls. Call once at startup;",
|
|
114
|
+
" * see fill_in_brief.hal_setup_todo for the SPI mode this part expects. */",
|
|
115
|
+
`esp_err_t ${name}_esp32_bind(spi_host_device_t host, int cs_gpio, int clock_hz) {`,
|
|
116
|
+
" spi_device_interface_config_t cfg = {",
|
|
117
|
+
" .flags = SPI_DEVICE_HALFDUPLEX,",
|
|
118
|
+
" .spics_io_num = cs_gpio,",
|
|
119
|
+
" .clock_speed_hz = clock_hz,",
|
|
120
|
+
" .mode = 0, /* see fill_in_brief.hal_setup_todo for this part's SPI mode */",
|
|
121
|
+
" .queue_size = 1,",
|
|
122
|
+
" };",
|
|
123
|
+
` return spi_bus_add_device(host, &cfg, &s_${name}_dev);`,
|
|
124
|
+
"}",
|
|
125
|
+
"",
|
|
126
|
+
"void hal_spi_transfer(const uint8_t *tx, uint16_t tx_len, uint8_t *rx, uint16_t rx_len) {",
|
|
127
|
+
" spi_transaction_t t;",
|
|
128
|
+
" memset(&t, 0, sizeof t);",
|
|
129
|
+
" t.length = 8 * tx_len;",
|
|
130
|
+
" t.rxlength = 8 * rx_len;",
|
|
131
|
+
" t.tx_buffer = tx;",
|
|
132
|
+
" t.rx_buffer = rx;",
|
|
133
|
+
` spi_device_polling_transmit(s_${name}_dev, &t);`,
|
|
134
|
+
"}",
|
|
135
|
+
"",
|
|
136
|
+
"void hal_delay_ms(uint32_t ms) {",
|
|
137
|
+
" vTaskDelay(pdMS_TO_TICKS(ms));",
|
|
138
|
+
"}",
|
|
139
|
+
"",
|
|
140
|
+
].join("\n");
|
|
141
|
+
return { path: seamPath(name, language), content };
|
|
142
|
+
}
|
|
143
|
+
function halImplUart(name, prefix, language) {
|
|
144
|
+
const content = [
|
|
145
|
+
"/*",
|
|
146
|
+
` * ${prefix} — ESP-IDF (uart) HAL seam implementation (generated by Driverge).`,
|
|
147
|
+
" *",
|
|
148
|
+
" * Drop this beside the portable core; it fills the thin-HAL seam with the",
|
|
149
|
+
" * ESP-IDF v5 UART driver. In your app: configure and install the UART driver",
|
|
150
|
+
" * (uart_param_config + uart_driver_install) for your TX/RX pins, port, and",
|
|
151
|
+
` * this part's baud rate, then call ${name}_esp32_bind() once before using the`,
|
|
152
|
+
" * driver. The driver core stays byte-for-byte portable. See",
|
|
153
|
+
" * fill_in_brief.hal_setup_todo for the baud rate/config to use.",
|
|
154
|
+
" */",
|
|
155
|
+
coreInclude(name, language),
|
|
156
|
+
'#include "driver/uart.h"',
|
|
157
|
+
'#include "freertos/FreeRTOS.h"',
|
|
158
|
+
'#include "freertos/task.h"',
|
|
159
|
+
"",
|
|
160
|
+
`static uart_port_t s_${name}_port;`,
|
|
161
|
+
"",
|
|
162
|
+
"/* Point the seam at an already-installed UART port (uart_driver_install'd).",
|
|
163
|
+
" * Call once at startup — see fill_in_brief.hal_setup_todo. */",
|
|
164
|
+
`void ${name}_esp32_bind(uart_port_t port) {`,
|
|
165
|
+
` s_${name}_port = port;`,
|
|
166
|
+
"}",
|
|
167
|
+
"",
|
|
168
|
+
"void hal_uart_write(const uint8_t *data, uint16_t len) {",
|
|
169
|
+
` uart_write_bytes(s_${name}_port, (const char *)data, len);`,
|
|
170
|
+
"}",
|
|
171
|
+
"",
|
|
172
|
+
"uint16_t hal_uart_read(uint8_t *data, uint16_t len, uint32_t timeout_ms) {",
|
|
173
|
+
` int n = uart_read_bytes(s_${name}_port, data, len, pdMS_TO_TICKS(timeout_ms));`,
|
|
174
|
+
" if (n < 0) {",
|
|
175
|
+
" return 0;",
|
|
176
|
+
" }",
|
|
177
|
+
" return (uint16_t)n;",
|
|
178
|
+
"}",
|
|
179
|
+
"",
|
|
180
|
+
"void hal_delay_ms(uint32_t ms) {",
|
|
181
|
+
" vTaskDelay(pdMS_TO_TICKS(ms));",
|
|
182
|
+
"}",
|
|
183
|
+
"",
|
|
184
|
+
].join("\n");
|
|
185
|
+
return { path: seamPath(name, language), content };
|
|
186
|
+
}
|
|
187
|
+
function halImplCan(name, prefix, language) {
|
|
188
|
+
const content = [
|
|
189
|
+
"/*",
|
|
190
|
+
` * ${prefix} — ESP-IDF (TWAI) HAL seam implementation (generated by Driverge).`,
|
|
191
|
+
" *",
|
|
192
|
+
" * Drop this beside the portable core; it fills the thin-HAL seam with the",
|
|
193
|
+
" * ESP-IDF v5 TWAI (Two-Wire Automotive Interface — ESP-IDF's CAN 2.0",
|
|
194
|
+
" * controller) driver. In your app: install and start the TWAI driver",
|
|
195
|
+
" * (twai_driver_install + twai_start) for your TX/RX GPIOs and this part's",
|
|
196
|
+
` * bitrate, then call ${name}_esp32_bind() once before using the driver.`,
|
|
197
|
+
" * The driver core stays byte-for-byte portable. See",
|
|
198
|
+
" * fill_in_brief.hal_setup_todo for the bitrate/acceptance-filter config.",
|
|
199
|
+
" *",
|
|
200
|
+
` * ${name}_esp32_bind() deliberately takes NO arguments: unlike I2C/SPI/UART`,
|
|
201
|
+
" * (which bind a specific bus/port handle), ESP-IDF exposes exactly one",
|
|
202
|
+
" * TWAI peripheral per chip, installed once for the whole application — so",
|
|
203
|
+
" * there is nothing instance-specific to store yet. The hook is kept for",
|
|
204
|
+
" * signature parity with the other targets' bind().",
|
|
205
|
+
" */",
|
|
206
|
+
coreInclude(name, language),
|
|
207
|
+
"#include <string.h>",
|
|
208
|
+
'#include "driver/twai.h"',
|
|
209
|
+
'#include "freertos/FreeRTOS.h"',
|
|
210
|
+
'#include "freertos/task.h"',
|
|
211
|
+
"",
|
|
212
|
+
"/* Reserved bind hook (see the file comment above). Call once after",
|
|
213
|
+
" * twai_driver_install()/twai_start(). */",
|
|
214
|
+
`void ${name}_esp32_bind(void) {`,
|
|
215
|
+
"}",
|
|
216
|
+
"",
|
|
217
|
+
"int hal_can_transfer(uint32_t id, const uint8_t *tx, uint8_t tx_len, uint8_t *rx, uint8_t *rx_len, uint32_t timeout_ms) {",
|
|
218
|
+
" twai_message_t tx_msg;",
|
|
219
|
+
" memset(&tx_msg, 0, sizeof tx_msg);",
|
|
220
|
+
" tx_msg.identifier = id;",
|
|
221
|
+
" tx_msg.data_length_code = tx_len;",
|
|
222
|
+
" if (tx != NULL && tx_len > 0) {",
|
|
223
|
+
" memcpy(tx_msg.data, tx, tx_len);",
|
|
224
|
+
" }",
|
|
225
|
+
" if (twai_transmit(&tx_msg, pdMS_TO_TICKS(timeout_ms)) != ESP_OK) {",
|
|
226
|
+
" return -1;",
|
|
227
|
+
" }",
|
|
228
|
+
" if (rx == NULL || rx_len == NULL || *rx_len == 0) {",
|
|
229
|
+
" return 0;",
|
|
230
|
+
" }",
|
|
231
|
+
" twai_message_t rx_msg;",
|
|
232
|
+
" if (twai_receive(&rx_msg, pdMS_TO_TICKS(timeout_ms)) != ESP_OK) {",
|
|
233
|
+
" return -1;",
|
|
234
|
+
" }",
|
|
235
|
+
" uint8_t n = rx_msg.data_length_code;",
|
|
236
|
+
" if (n > *rx_len) {",
|
|
237
|
+
" n = *rx_len;",
|
|
238
|
+
" }",
|
|
239
|
+
" memcpy(rx, rx_msg.data, n);",
|
|
240
|
+
" *rx_len = n;",
|
|
241
|
+
" return 0;",
|
|
242
|
+
"}",
|
|
243
|
+
"",
|
|
244
|
+
"void hal_delay_ms(uint32_t ms) {",
|
|
245
|
+
" vTaskDelay(pdMS_TO_TICKS(ms));",
|
|
246
|
+
"}",
|
|
247
|
+
"",
|
|
248
|
+
].join("\n");
|
|
249
|
+
return { path: seamPath(name, language), content };
|
|
64
250
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Portable core + an ESP-IDF seam implementation for the same part. With
|
|
253
|
+
* language "c" (default) the seam is `<slug>_hal_esp32.c`, unchanged from
|
|
254
|
+
* before. With language "cpp" the seam becomes `<slug>_hal_esp32.cpp` and its
|
|
255
|
+
* core include is `#include "<slug>.hpp"` (see coreInclude/seamPath above) —
|
|
256
|
+
* everything else in the seam content is identical either way.
|
|
257
|
+
*/
|
|
258
|
+
export function generateEsp32Driver(json, opts = {}) {
|
|
259
|
+
const bus = json.protocol.bus;
|
|
260
|
+
if (bus !== "I2C" && bus !== "SPI" && bus !== "UART" && bus !== "CAN") {
|
|
261
|
+
throw new UnsupportedBusError("esp32", bus);
|
|
262
|
+
}
|
|
67
263
|
const name = slug(json.metadata.part);
|
|
68
264
|
const prefix = prefixOf(json.metadata.part);
|
|
69
|
-
const
|
|
265
|
+
const language = opts.language ?? "c";
|
|
266
|
+
const base = language === "cpp" ? generatePortableCppDriver(json) : generatePortableDriver(json);
|
|
267
|
+
const hal = bus === "SPI"
|
|
268
|
+
? halImplSpi(name, prefix, language)
|
|
269
|
+
: bus === "UART"
|
|
270
|
+
? halImplUart(name, prefix, language)
|
|
271
|
+
: bus === "CAN"
|
|
272
|
+
? halImplCan(name, prefix, language)
|
|
273
|
+
: halImplI2c(name, prefix, language);
|
|
274
|
+
const hal_setup_todo = bus === "SPI"
|
|
275
|
+
? `Bring up the ESP-IDF SPI bus (spi_bus_initialize with your MOSI/MISO/SCLK pins and host), then call ${name}_esp32_bind(host, cs_gpio, clock_hz) before the first driver call. Confirm this part's SPI mode (0-3) and maximum clock speed from the datasheet's SPI timing section.`
|
|
276
|
+
: bus === "UART"
|
|
277
|
+
? `Install the ESP-IDF UART driver (uart_param_config + uart_driver_install) on your chosen port with this part's baud rate/parity/stop-bits (see the datasheet's serial interface section), then call ${name}_esp32_bind(port) before the first driver call.`
|
|
278
|
+
: bus === "CAN"
|
|
279
|
+
? `Install and start the ESP-IDF TWAI driver (twai_driver_install + twai_start) on your chosen TX/RX GPIOs with this part's bitrate and an acceptance filter covering its message IDs (see the datasheet's CAN bus timing/message-ID section), then call ${name}_esp32_bind() before the first driver call.`
|
|
280
|
+
: `Bring up the ESP-IDF I2C master bus (i2c_new_master_bus with your SDA/SCL GPIOs and port), then call ${name}_esp32_bind(bus, 400000) before the first driver call. Tune ${prefix}_I2C_TIMEOUT_MS if needed.`;
|
|
70
281
|
return {
|
|
71
|
-
files: [...base.files,
|
|
282
|
+
files: [...base.files, hal],
|
|
72
283
|
fill_in_brief: {
|
|
73
284
|
...base.fill_in_brief,
|
|
74
|
-
hal_setup_todo
|
|
285
|
+
hal_setup_todo,
|
|
75
286
|
},
|
|
76
287
|
};
|
|
77
288
|
}
|