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/dist/codegen/portable.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
// Portable thin-HAL codegen. Renders a deterministic C driver skeleton from the
|
|
2
2
|
// frozen DatasheetJson: register/command constants, bit-field mask+shift macros,
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
// the per-bus thin-HAL seam (I2C: hal_i2c_read/write + hal_delay_ms; SPI: one
|
|
4
|
+
// combined hal_spi_transfer + hal_delay_ms — see BUS_SEAM below and
|
|
5
|
+
// thin-hal-non-negotiable) and NOTHING platform-specific, function stubs, and
|
|
6
|
+
// TODO(driverge) markers at every spot needing host-AI reasoning. Output is
|
|
7
|
+
// reproducible: no timestamps, stable ordering. The host AI fills the brief and
|
|
8
|
+
// re-validates via validate_driver.
|
|
9
|
+
import { registerWidth } from "../pdf/types.js";
|
|
10
|
+
import { HEX_ONLY, commentSafe, fieldMask, macro, maskHex, slug } from "./ident.js";
|
|
11
|
+
// Exported: the "cpp" language flavor (see portable-cpp.ts) renders the SAME
|
|
12
|
+
// #define macro / TODO blocks and thin-HAL seam declarations as this C
|
|
13
|
+
// generator — reusing these pure helpers instead of re-deriving them is how the
|
|
14
|
+
// two flavors stay byte-for-byte consistent on everything except the
|
|
15
|
+
// struct-vs-class/free-function-vs-method shape (see Session D: cpp language
|
|
16
|
+
// option).
|
|
17
|
+
// part/mfr/busLine are free text lifted verbatim from the parsed datasheet
|
|
18
|
+
// (metadata.part/manufacturer; busLine's only free-text ingredient is the I2C
|
|
19
|
+
// address, which is hex-validated, but commentSafe-ing the whole trio here —
|
|
20
|
+
// once, in the one place every AUTOGEN caller shares — is simpler and safer
|
|
21
|
+
// than re-deriving which piece is "safe enough" at each call site) — see B2:
|
|
22
|
+
// comment-escape injection. commentSafe neutralizes any embedded `*/`/`/*` so
|
|
23
|
+
// none of the three can ever prematurely close (or spoof) this block comment.
|
|
24
|
+
export const AUTOGEN = (part, mfr, busLine) => [
|
|
9
25
|
"/*",
|
|
10
|
-
` * ${part} driver — portable thin-HAL skeleton (generated by Driverge).`,
|
|
11
|
-
` * Manufacturer: ${mfr}`,
|
|
12
|
-
` * ${busLine}`,
|
|
26
|
+
` * ${commentSafe(part)} driver — portable thin-HAL skeleton (generated by Driverge).`,
|
|
27
|
+
` * Manufacturer: ${commentSafe(mfr)}`,
|
|
28
|
+
` * ${commentSafe(busLine)}`,
|
|
13
29
|
" *",
|
|
14
30
|
" * This is a deterministic skeleton. Every TODO(driverge) marks a spot the",
|
|
15
31
|
" * host AI must complete (init sequence, vendor quirks, docs) using the",
|
|
@@ -22,7 +38,7 @@ const AUTOGEN = (part, mfr, busLine) => [
|
|
|
22
38
|
// ---------------------------------------------------------------------------
|
|
23
39
|
// register_map
|
|
24
40
|
// ---------------------------------------------------------------------------
|
|
25
|
-
function registerConstants(prefix, registers) {
|
|
41
|
+
export function registerConstants(prefix, registers) {
|
|
26
42
|
const lines = ["/* Register addresses. */"];
|
|
27
43
|
const seen = new Set();
|
|
28
44
|
for (const r of registers) {
|
|
@@ -36,28 +52,178 @@ function registerConstants(prefix, registers) {
|
|
|
36
52
|
}
|
|
37
53
|
return lines;
|
|
38
54
|
}
|
|
39
|
-
|
|
55
|
+
/** Where the host AI must enumerate a register map that Driverge couldn't extract. */
|
|
56
|
+
export function registerMapTodo(prefix, pages) {
|
|
57
|
+
const where = pages.length
|
|
58
|
+
? ` (register section detected on page(s) ${pages.join(", ")})`
|
|
59
|
+
: "";
|
|
60
|
+
return [
|
|
61
|
+
"/* Register map. */",
|
|
62
|
+
"/* TODO(driverge): Driverge could not auto-extract this device's register map",
|
|
63
|
+
` * from the datasheet${where}. Enumerate the registers from the datasheet (read`,
|
|
64
|
+
" * the driverge://datasheet resource) and add one line per register:",
|
|
65
|
+
` * #define ${prefix}_REG_<NAME> 0x..`,
|
|
66
|
+
` * plus ${prefix}_<REG>_<FIELD>_MASK / _SHIFT macros for any bit fields. */`,
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
/** Where the host AI must enumerate a command set that Driverge couldn't extract. */
|
|
70
|
+
export function commandSetTodo(prefix, pages) {
|
|
71
|
+
const where = pages.length
|
|
72
|
+
? ` (command section detected on page(s) ${pages.join(", ")})`
|
|
73
|
+
: "";
|
|
74
|
+
return [
|
|
75
|
+
"/* Command codes. */",
|
|
76
|
+
"/* TODO(driverge): Driverge could not auto-extract this device's command set",
|
|
77
|
+
` * from the datasheet${where}. Enumerate the commands from the datasheet (read`,
|
|
78
|
+
" * the driverge://datasheet resource) and add one line per command:",
|
|
79
|
+
` * #define ${prefix}_CMD_<NAME> 0x.. */`,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
/** fill_in_brief.register_map_todo text — shared by the C and cpp generators. */
|
|
83
|
+
export function registerMapTodoText(json, name, prefix) {
|
|
84
|
+
return `Enumerate ${json.metadata.part || name}'s register map from the datasheet — Driverge could not auto-extract it. Add a "#define ${prefix}_REG_<NAME> 0x.." for each register address (and ${prefix}_<REG>_<FIELD>_MASK/_SHIFT for bit fields) using the driverge://datasheet resource, then wire the read/write helpers accordingly.`;
|
|
85
|
+
}
|
|
86
|
+
/** fill_in_brief.command_set_todo text — shared by the C and cpp generators. */
|
|
87
|
+
export function commandSetTodoText(json, name, prefix) {
|
|
88
|
+
return `Enumerate ${json.metadata.part || name}'s command set from the datasheet — Driverge could not auto-extract it. Add a "#define ${prefix}_CMD_<NAME> 0x.." for each command using the driverge://datasheet resource.`;
|
|
89
|
+
}
|
|
90
|
+
export const BUS_SEAM = {
|
|
91
|
+
I2C: {
|
|
92
|
+
decl: [
|
|
93
|
+
"void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
|
|
94
|
+
"void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
|
|
95
|
+
],
|
|
96
|
+
handleField: " uint8_t i2c_addr;",
|
|
97
|
+
readBody: [" hal_i2c_read(dev->i2c_addr, reg, value, 1);"],
|
|
98
|
+
writeBody: [" hal_i2c_write(dev->i2c_addr, reg, &value, 1);"],
|
|
99
|
+
},
|
|
100
|
+
SPI: {
|
|
101
|
+
decl: [
|
|
102
|
+
"/* One call = one CS-framed transaction: CS is asserted, tx_len bytes are",
|
|
103
|
+
" * clocked out, then rx_len bytes are clocked in, then CS is deasserted.",
|
|
104
|
+
" * tx/rx may be NULL when their respective length is 0. CS is handled",
|
|
105
|
+
" * entirely inside hal_spi_transfer — callers never touch it. */",
|
|
106
|
+
"void hal_spi_transfer(const uint8_t *tx, uint16_t tx_len, uint8_t *rx, uint16_t rx_len);",
|
|
107
|
+
],
|
|
108
|
+
handleField: " uint8_t _reserved; /* platform CS handled in hal_spi_transfer */",
|
|
109
|
+
readBody: [" hal_spi_transfer(®, 1, value, 1);"],
|
|
110
|
+
writeBody: [
|
|
111
|
+
" uint8_t frame[2];",
|
|
112
|
+
" frame[0] = reg;",
|
|
113
|
+
" frame[1] = value;",
|
|
114
|
+
" hal_spi_transfer(frame, 2, NULL, 0);",
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
UART: {
|
|
118
|
+
decl: [
|
|
119
|
+
"/* hal_uart_write is a blocking write of len bytes. */",
|
|
120
|
+
"/* hal_uart_read reads up to len bytes and returns the count actually read",
|
|
121
|
+
" * within timeout_ms. */",
|
|
122
|
+
"void hal_uart_write(const uint8_t *data, uint16_t len);",
|
|
123
|
+
"uint16_t hal_uart_read(uint8_t *data, uint16_t len, uint32_t timeout_ms);",
|
|
124
|
+
],
|
|
125
|
+
handleField: " uint8_t _reserved; /* platform UART handle is bound inside the hal_uart_* implementation */",
|
|
126
|
+
readBody: [],
|
|
127
|
+
writeBody: [],
|
|
128
|
+
},
|
|
129
|
+
// Session C — CAN. Unlike I2C's two-function read/write pair or SPI's single
|
|
130
|
+
// transfer, one hal_can_transfer() call both sends a frame AND (optionally)
|
|
131
|
+
// waits for the one response frame it provokes — CAN has no separate
|
|
132
|
+
// read/write primitive, only "put a frame on the bus, addressed by
|
|
133
|
+
// arbitration id, and maybe get one back". rx_len is in/out (capacity in,
|
|
134
|
+
// actual byte count out) because a CAN payload is 0-8 bytes, not a fixed
|
|
135
|
+
// size.
|
|
136
|
+
CAN: {
|
|
137
|
+
decl: [
|
|
138
|
+
"/* A call sends one CAN frame to arbitration id `id` and optionally waits",
|
|
139
|
+
" * for one response frame (pass rx/rx_len NULL/0 to only transmit).",
|
|
140
|
+
" * Returns 0 on success. rx_len is in/out: in = buffer capacity, out =",
|
|
141
|
+
" * bytes actually received. */",
|
|
142
|
+
"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);",
|
|
143
|
+
],
|
|
144
|
+
handleField: " uint8_t _reserved; /* platform CAN peripheral/arbitration-id binding is handled inside the hal_can_transfer implementation */",
|
|
145
|
+
readBody: [],
|
|
146
|
+
writeBody: [],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
/** Which BUS_SEAM row a protocol.bus resolves to. "unknown" defaults to the I2C
|
|
150
|
+
* seam, matching the pre-existing (pre-Session-B) fallback behavior. */
|
|
151
|
+
export function seamBusFor(bus) {
|
|
152
|
+
if (bus === "SPI")
|
|
153
|
+
return "SPI";
|
|
154
|
+
if (bus === "UART")
|
|
155
|
+
return "UART";
|
|
156
|
+
if (bus === "CAN")
|
|
157
|
+
return "CAN";
|
|
158
|
+
return "I2C";
|
|
159
|
+
}
|
|
160
|
+
// UART and CAN have no universal register/command framing (start bytes, IDs,
|
|
161
|
+
// checksums, arbitration ids are all device-specific — see decisions:
|
|
162
|
+
// thin-hal-non-negotiable), so rather than fabricate a wire format, the
|
|
163
|
+
// generated body is a deliberate TODO(driverge) reasoning gap over the raw
|
|
164
|
+
// seam call(s): a compilable placeholder (params voided, returns -1) that
|
|
165
|
+
// names the seam function(s) so the host AI knows exactly what to build on
|
|
166
|
+
// top of. See fill_in_brief.framing_todo.
|
|
167
|
+
export function uartFramingBody(params) {
|
|
168
|
+
return [
|
|
169
|
+
" /* TODO(driverge): this device has no universal UART register/command",
|
|
170
|
+
" * framing — build the request frame and call hal_uart_write(), then",
|
|
171
|
+
" * parse the response via hal_uart_read(). See fill_in_brief.framing_todo. */",
|
|
172
|
+
...params.map((p) => ` (void)${p};`),
|
|
173
|
+
" return -1;",
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
export function canFramingBody(params) {
|
|
177
|
+
return [
|
|
178
|
+
" /* TODO(driverge): this device has no universal CAN register/command",
|
|
179
|
+
" * framing — build the CAN frame and call hal_can_transfer() with this",
|
|
180
|
+
" * part's arbitration id to send it (and receive any response). See",
|
|
181
|
+
" * fill_in_brief.framing_todo. */",
|
|
182
|
+
...params.map((p) => ` (void)${p};`),
|
|
183
|
+
" return -1;",
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
// CAN-specific reasoning gap shared by both interface kinds: which arbitration
|
|
187
|
+
// id / SDO index / message ID maps to which register or command is
|
|
188
|
+
// device-specific and belongs in fill_in_brief.quirks_todo, NOT a generic
|
|
189
|
+
// checksum note reused from UART.
|
|
190
|
+
export const CAN_QUIRKS_NOTE = "Confirm the arbitration IDs / SDO message schema this part uses for register/command access from the datasheet; CAN device interfaces are device-specific.";
|
|
191
|
+
export function bitFieldMacros(prefix, registers) {
|
|
40
192
|
const lines = [];
|
|
41
193
|
for (const r of registers) {
|
|
42
194
|
if (r.bitFields.length === 0)
|
|
43
195
|
continue;
|
|
44
|
-
|
|
196
|
+
const width = registerWidth(r);
|
|
197
|
+
const widthNote = width > 8 ? ` (${width}-bit register)` : "";
|
|
198
|
+
// r.name is free text lifted verbatim from the parsed datasheet — see B2:
|
|
199
|
+
// comment-escape injection — so it must not be allowed to close this
|
|
200
|
+
// block comment early via an embedded `*/`.
|
|
201
|
+
lines.push("", `/* ${commentSafe(r.name)} bit fields${widthNote}. */`);
|
|
45
202
|
for (const f of r.bitFields) {
|
|
46
203
|
const base = `${prefix}_${macro(r.name)}_${macro(f.name)}`;
|
|
47
|
-
lines.push(`#define ${base}_MASK ${
|
|
204
|
+
lines.push(`#define ${base}_MASK ${maskHex(fieldMask(f.msb, f.lsb, width), width)}`);
|
|
48
205
|
lines.push(`#define ${base}_SHIFT ${f.lsb}`);
|
|
49
206
|
}
|
|
50
207
|
}
|
|
51
208
|
return lines;
|
|
52
209
|
}
|
|
53
|
-
function registerDriver(json, name, prefix) {
|
|
54
|
-
const
|
|
55
|
-
const spi =
|
|
210
|
+
function registerDriver(json, registers, name, prefix) {
|
|
211
|
+
const busKind = seamBusFor(json.protocol.bus);
|
|
212
|
+
const spi = busKind === "SPI";
|
|
213
|
+
const uart = busKind === "UART";
|
|
214
|
+
const can = busKind === "CAN";
|
|
215
|
+
const seam = BUS_SEAM[busKind];
|
|
56
216
|
const addr = json.protocol.addresses?.[0];
|
|
57
217
|
const guard = `${prefix}_H`;
|
|
58
218
|
const type = `${name}_t`;
|
|
59
219
|
const header = [
|
|
60
|
-
AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, spi
|
|
220
|
+
AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, spi
|
|
221
|
+
? "Bus: SPI"
|
|
222
|
+
: uart
|
|
223
|
+
? "Bus: UART"
|
|
224
|
+
: can
|
|
225
|
+
? "Bus: CAN"
|
|
226
|
+
: `Bus: I2C, address ${addr ?? "(unknown — see TODO)"}`),
|
|
61
227
|
"",
|
|
62
228
|
`#ifndef ${guard}`,
|
|
63
229
|
`#define ${guard}`,
|
|
@@ -70,7 +236,7 @@ function registerDriver(json, name, prefix) {
|
|
|
70
236
|
"#endif",
|
|
71
237
|
"",
|
|
72
238
|
];
|
|
73
|
-
if (!spi) {
|
|
239
|
+
if (!spi && !uart && !can) {
|
|
74
240
|
if (addr) {
|
|
75
241
|
header.push("/* I2C device address. */", `#define ${prefix}_I2C_ADDR ${addr}`, "");
|
|
76
242
|
}
|
|
@@ -78,25 +244,28 @@ function registerDriver(json, name, prefix) {
|
|
|
78
244
|
header.push("/* TODO(driverge): set the I2C device address (not found in the datasheet parse). */", `#define ${prefix}_I2C_ADDR 0x00`, "");
|
|
79
245
|
}
|
|
80
246
|
}
|
|
81
|
-
|
|
247
|
+
const regLines = registerConstants(prefix, registers);
|
|
248
|
+
const hasRegs = regLines.some((l) => l.startsWith("#define"));
|
|
249
|
+
header.push(...(hasRegs
|
|
250
|
+
? regLines
|
|
251
|
+
: registerMapTodo(prefix, json.extraction?.detectedPages ?? [])));
|
|
252
|
+
if (registers.some((r) => registerWidth(r) > 8)) {
|
|
253
|
+
header.push("", "/* NOTE: some registers here are wider than 8 bits (see the per-register", " * annotations). Bit-field masks are register-width correct; multi-byte access", " * framing (byte order / transfer size) is device-specific and belongs on the", " * hal_* seam. */");
|
|
254
|
+
}
|
|
82
255
|
const bits = bitFieldMacros(prefix, registers);
|
|
83
256
|
if (bits.length > 0)
|
|
84
257
|
header.push("", "/* Bit-field mask/shift accessors. */", ...bits);
|
|
85
|
-
header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
: [" hal_i2c_read(dev->i2c_addr, reg, value, 1);"];
|
|
97
|
-
const writeBody = spi
|
|
98
|
-
? [" uint8_t frame[2];", " frame[0] = reg;", " frame[1] = value;", " hal_spi_write(frame, 2);"]
|
|
99
|
-
: [" hal_i2c_write(dev->i2c_addr, reg, &value, 1);"];
|
|
258
|
+
header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...seam.decl, "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", `typedef struct {`, seam.handleField, `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_read_register(${type} *dev, uint8_t reg, uint8_t *value);`, `int ${name}_write_register(${type} *dev, uint8_t reg, uint8_t value);`, "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
|
|
259
|
+
const readBody = uart
|
|
260
|
+
? uartFramingBody(["dev", "reg", "value"])
|
|
261
|
+
: can
|
|
262
|
+
? canFramingBody(["dev", "reg", "value"])
|
|
263
|
+
: [" if (dev == NULL || value == NULL) {", " return -1;", " }", ...seam.readBody, " return 0;"];
|
|
264
|
+
const writeBody = uart
|
|
265
|
+
? uartFramingBody(["dev", "reg", "value"])
|
|
266
|
+
: can
|
|
267
|
+
? canFramingBody(["dev", "reg", "value"])
|
|
268
|
+
: [" if (dev == NULL) {", " return -1;", " }", ...seam.writeBody, " return 0;"];
|
|
100
269
|
const source = [
|
|
101
270
|
`#include "${name}.h"`,
|
|
102
271
|
"",
|
|
@@ -104,55 +273,79 @@ function registerDriver(json, name, prefix) {
|
|
|
104
273
|
" if (dev == NULL) {",
|
|
105
274
|
" return -1;",
|
|
106
275
|
" }",
|
|
107
|
-
...(spi ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
|
|
108
|
-
|
|
109
|
-
" * correct register write order and values, plus any required startup",
|
|
110
|
-
` * delay. See fill_in_brief.init_sequence_todo. Use ${name}_write_register()`,
|
|
111
|
-
" * and hal_delay_ms(). */",
|
|
276
|
+
...(spi || uart || can ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
|
|
277
|
+
...registerInitTodo(uart, can, `${name}_write_register()`),
|
|
112
278
|
" return 0;",
|
|
113
279
|
"}",
|
|
114
280
|
"",
|
|
115
281
|
`int ${name}_read_register(${type} *dev, uint8_t reg, uint8_t *value) {`,
|
|
116
|
-
" if (dev == NULL || value == NULL) {",
|
|
117
|
-
" return -1;",
|
|
118
|
-
" }",
|
|
119
282
|
...readBody,
|
|
120
|
-
" return 0;",
|
|
121
283
|
"}",
|
|
122
284
|
"",
|
|
123
285
|
`int ${name}_write_register(${type} *dev, uint8_t reg, uint8_t value) {`,
|
|
124
|
-
" if (dev == NULL) {",
|
|
125
|
-
" return -1;",
|
|
126
|
-
" }",
|
|
127
286
|
...writeBody,
|
|
128
|
-
" return 0;",
|
|
129
287
|
"}",
|
|
130
288
|
"",
|
|
131
289
|
];
|
|
290
|
+
const brief = registerBrief(json);
|
|
291
|
+
if (!hasRegs) {
|
|
292
|
+
brief.register_map_todo = registerMapTodoText(json, name, prefix);
|
|
293
|
+
}
|
|
132
294
|
return {
|
|
133
295
|
files: makeFiles(name, header, source),
|
|
134
|
-
fill_in_brief:
|
|
296
|
+
fill_in_brief: brief,
|
|
135
297
|
};
|
|
136
298
|
}
|
|
137
|
-
function registerBrief(json) {
|
|
299
|
+
export function registerBrief(json, calls) {
|
|
138
300
|
const part = json.metadata.part || "the device";
|
|
139
|
-
|
|
301
|
+
const name = slug(part);
|
|
302
|
+
const c = {
|
|
303
|
+
init: calls?.init ?? `${name}_init`,
|
|
304
|
+
readRegister: calls?.readRegister ?? "read_register",
|
|
305
|
+
writeRegister: calls?.writeRegister ?? "write_register",
|
|
306
|
+
};
|
|
307
|
+
const quirks = [
|
|
308
|
+
`Capture vendor timing/behavior quirks for ${part} (e.g. mandatory power-on delay, soft-reset requirement, read-back constraints) and enforce them in ${c.init} using hal_delay_ms.`,
|
|
309
|
+
];
|
|
310
|
+
if (json.protocol.bus === "SPI") {
|
|
311
|
+
quirks.push(`Verify the register address bit convention: many SPI chips set the address MSB for reads (e.g. 0x80 | reg) — check the datasheet's SPI framing and adjust ${c.readRegister}/${c.writeRegister}.`);
|
|
312
|
+
}
|
|
313
|
+
if (json.protocol.bus === "UART") {
|
|
314
|
+
quirks.push("Verify the response checksum algorithm from the datasheet before trusting frame data — vendor checksum/CRC methods for UART command-and-response parts vary.");
|
|
315
|
+
}
|
|
316
|
+
if (json.protocol.bus === "CAN") {
|
|
317
|
+
quirks.push(CAN_QUIRKS_NOTE);
|
|
318
|
+
}
|
|
319
|
+
const brief = {
|
|
140
320
|
init_sequence_todo: `Determine the correct power-on/reset init sequence for ${part}: which registers to write, in what order, with what values (mode/config), and any required startup delay. Read the datasheet init/operation section via the driverge://datasheet resource.`,
|
|
141
|
-
quirks_todo:
|
|
321
|
+
quirks_todo: quirks.join(" "),
|
|
142
322
|
doc_todo: "Add Doxygen comments for all public functions and any device-specific compensation/conversion formulas.",
|
|
143
323
|
};
|
|
324
|
+
if (json.protocol.bus === "UART") {
|
|
325
|
+
brief.framing_todo = `Implement ${name}'s UART frame protocol in ${c.readRegister}/${c.writeRegister}: build request frames with hal_uart_write and parse responses with hal_uart_read, per the datasheet's communication/frame-format section.`;
|
|
326
|
+
}
|
|
327
|
+
if (json.protocol.bus === "CAN") {
|
|
328
|
+
brief.framing_todo = `Implement ${name}'s CAN frame protocol in ${c.readRegister}/${c.writeRegister}: build request frames and call hal_can_transfer() with this part's arbitration id(s), per the datasheet's CAN communication/message-ID section.`;
|
|
329
|
+
}
|
|
330
|
+
return brief;
|
|
144
331
|
}
|
|
145
332
|
// ---------------------------------------------------------------------------
|
|
146
333
|
// command_set
|
|
147
334
|
// ---------------------------------------------------------------------------
|
|
148
|
-
function commandDriver(json, name, prefix) {
|
|
149
|
-
const
|
|
335
|
+
function commandDriver(json, commands, name, prefix) {
|
|
336
|
+
const uart = json.protocol.bus === "UART";
|
|
337
|
+
const can = json.protocol.bus === "CAN";
|
|
338
|
+
const noAddrBus = uart || can; // neither UART nor CAN has a bus device address
|
|
150
339
|
const addr = json.protocol.addresses?.[0];
|
|
151
340
|
const crc = commands.find((c) => c.crc)?.crc;
|
|
152
341
|
const guard = `${prefix}_H`;
|
|
153
342
|
const type = `${name}_t`;
|
|
154
343
|
const header = [
|
|
155
|
-
AUTOGEN(json.metadata.part || name, json.metadata.manufacturer,
|
|
344
|
+
AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, uart
|
|
345
|
+
? "Bus: UART · command-set device"
|
|
346
|
+
: can
|
|
347
|
+
? "Bus: CAN · command-set device"
|
|
348
|
+
: `Bus: I2C, address ${addr ?? "(unknown — see TODO)"} · command-set device`),
|
|
156
349
|
"",
|
|
157
350
|
`#ifndef ${guard}`,
|
|
158
351
|
`#define ${guard}`,
|
|
@@ -165,25 +358,48 @@ function commandDriver(json, name, prefix) {
|
|
|
165
358
|
"#endif",
|
|
166
359
|
"",
|
|
167
360
|
];
|
|
168
|
-
if (
|
|
361
|
+
if (noAddrBus) {
|
|
362
|
+
// No bus device address on UART/CAN — only I2C command-set parts get an
|
|
363
|
+
// *_I2C_ADDR macro (see fill_in_brief.framing_todo for the UART/CAN
|
|
364
|
+
// reasoning gap this leaves instead).
|
|
365
|
+
}
|
|
366
|
+
else if (addr) {
|
|
169
367
|
header.push("/* I2C device address. */", `#define ${prefix}_I2C_ADDR ${addr}`, "");
|
|
170
368
|
}
|
|
171
369
|
else {
|
|
172
370
|
header.push("/* TODO(driverge): set the I2C device address. */", `#define ${prefix}_I2C_ADDR 0x00`, "");
|
|
173
371
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
372
|
+
if (commands.length === 0) {
|
|
373
|
+
header.push(...commandSetTodo(prefix, json.extraction?.detectedPages ?? []));
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
header.push("/* Command codes. */");
|
|
377
|
+
const seen = new Set();
|
|
378
|
+
for (const c of commands) {
|
|
379
|
+
const macroName = `${prefix}_CMD_${macro(c.name)}`;
|
|
380
|
+
if (seen.has(macroName))
|
|
381
|
+
continue;
|
|
382
|
+
seen.add(macroName);
|
|
383
|
+
const line = `#define ${macroName} ${c.code.toUpperCase().replace("0X", "0x")}`;
|
|
384
|
+
// c.params is currently always hex-derived (prose-commands.ts) and thus
|
|
385
|
+
// safe as-is, but the Command type doesn't guarantee that — commentSafe
|
|
386
|
+
// defensively, same reasoning as AUTOGEN/bitFieldMacros above (B2).
|
|
387
|
+
header.push(c.params && c.params.length > 0
|
|
388
|
+
? `${line} /* params: ${commentSafe(c.params.join(", "))} */`
|
|
389
|
+
: line);
|
|
390
|
+
}
|
|
182
391
|
}
|
|
183
392
|
if (crc) {
|
|
184
393
|
header.push("", `/* CRC-${crc.width} checksum parameters. */`, `#define ${prefix}_CRC_POLY ${crc.poly}`, `#define ${prefix}_CRC_INIT ${crc.init}`);
|
|
185
394
|
}
|
|
186
|
-
header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */",
|
|
395
|
+
header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...(uart
|
|
396
|
+
? BUS_SEAM.UART.decl
|
|
397
|
+
: can
|
|
398
|
+
? BUS_SEAM.CAN.decl
|
|
399
|
+
: [
|
|
400
|
+
"void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
|
|
401
|
+
"void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
|
|
402
|
+
]), "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", "typedef struct {", uart ? BUS_SEAM.UART.handleField : can ? BUS_SEAM.CAN.handleField : " uint8_t i2c_addr;", `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_send_command(${type} *dev, uint16_t command);`, `int ${name}_read_data(${type} *dev, uint8_t *buffer, uint16_t len);`, ...(crc ? [`uint8_t ${name}_crc8(const uint8_t *data, uint16_t len);`] : []), "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
|
|
187
403
|
const source = [
|
|
188
404
|
`#include "${name}.h"`,
|
|
189
405
|
"",
|
|
@@ -191,68 +407,172 @@ function commandDriver(json, name, prefix) {
|
|
|
191
407
|
" if (dev == NULL) {",
|
|
192
408
|
" return -1;",
|
|
193
409
|
" }",
|
|
194
|
-
` dev->i2c_addr = ${prefix}_I2C_ADDR
|
|
195
|
-
|
|
196
|
-
" * fill_in_brief.init_sequence_todo. */",
|
|
410
|
+
...(noAddrBus ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
|
|
411
|
+
...COMMAND_INIT_TODO,
|
|
197
412
|
" return 0;",
|
|
198
413
|
"}",
|
|
199
414
|
"",
|
|
200
415
|
`int ${name}_send_command(${type} *dev, uint16_t command) {`,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
416
|
+
...(uart
|
|
417
|
+
? uartFramingBody(["dev", "command"])
|
|
418
|
+
: can
|
|
419
|
+
? canFramingBody(["dev", "command"])
|
|
420
|
+
: [
|
|
421
|
+
" uint8_t msb;",
|
|
422
|
+
" uint8_t lsb;",
|
|
423
|
+
" if (dev == NULL) {",
|
|
424
|
+
" return -1;",
|
|
425
|
+
" }",
|
|
426
|
+
" msb = (uint8_t)(command >> 8);",
|
|
427
|
+
" lsb = (uint8_t)(command & 0xFF);",
|
|
428
|
+
...I2C_COMMAND_FRAME_NOTE,
|
|
429
|
+
" hal_i2c_write(dev->i2c_addr, msb, &lsb, 1);",
|
|
430
|
+
" return 0;",
|
|
431
|
+
]),
|
|
212
432
|
"}",
|
|
213
433
|
"",
|
|
214
434
|
`int ${name}_read_data(${type} *dev, uint8_t *buffer, uint16_t len) {`,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
435
|
+
...(uart
|
|
436
|
+
? uartFramingBody(["dev", "buffer", "len"])
|
|
437
|
+
: can
|
|
438
|
+
? canFramingBody(["dev", "buffer", "len"])
|
|
439
|
+
: [
|
|
440
|
+
" if (dev == NULL || buffer == NULL) {",
|
|
441
|
+
" return -1;",
|
|
442
|
+
" }",
|
|
443
|
+
...commandReadDataTodo(`${name}_crc8`),
|
|
444
|
+
" (void)dev;",
|
|
445
|
+
" (void)buffer;",
|
|
446
|
+
" (void)len;",
|
|
447
|
+
" return 0;",
|
|
448
|
+
]),
|
|
226
449
|
"}",
|
|
227
450
|
"",
|
|
228
451
|
];
|
|
229
452
|
if (crc) {
|
|
230
|
-
source.push(
|
|
453
|
+
source.push(...crc8Body(`${name}_crc8`, crc, prefix));
|
|
231
454
|
}
|
|
455
|
+
const brief = commandBrief(json, commands, crc, uart, can, name, prefix);
|
|
456
|
+
return { files: makeFiles(name, header, source), fill_in_brief: brief };
|
|
457
|
+
}
|
|
458
|
+
export function commandBrief(json, commands, crc, uart, can, name, prefix, calls) {
|
|
459
|
+
const c = {
|
|
460
|
+
sendCommand: calls?.sendCommand ?? `${name}_send_command`,
|
|
461
|
+
readData: calls?.readData ?? `${name}_read_data`,
|
|
462
|
+
crc8: calls?.crc8 ?? `${name}_crc8`,
|
|
463
|
+
};
|
|
232
464
|
const brief = {
|
|
233
465
|
init_sequence_todo: `Determine the power-up / soft-reset sequence and any startup delay for ${json.metadata.part || "the device"}.`,
|
|
234
|
-
quirks_todo:
|
|
466
|
+
quirks_todo: uart
|
|
467
|
+
? `Verify the response checksum algorithm from the datasheet before trusting ${c.readData}'s output — vendor checksum/CRC methods vary by part.`
|
|
468
|
+
: can
|
|
469
|
+
? CAN_QUIRKS_NOTE
|
|
470
|
+
: `Implement ${c.readData} for this part's actual read framing (clock stretching vs. fixed delay, number of data+CRC words per command) using the driverge://datasheet resource.`,
|
|
235
471
|
doc_todo: "Add Doxygen comments for all public functions and document each command's response layout.",
|
|
236
472
|
};
|
|
237
473
|
if (crc) {
|
|
238
|
-
brief.crc_todo = `Implement ${
|
|
474
|
+
brief.crc_todo = `Implement ${c.crc8}: CRC-${crc.width}, polynomial ${crc.poly}, init ${crc.init}, per the datasheet checksum section.`;
|
|
239
475
|
}
|
|
240
|
-
|
|
476
|
+
if (commands.length === 0) {
|
|
477
|
+
brief.command_set_todo = commandSetTodoText(json, name, prefix);
|
|
478
|
+
}
|
|
479
|
+
if (uart) {
|
|
480
|
+
brief.framing_todo = `Implement ${name}'s UART frame protocol in ${c.sendCommand}/${c.readData}: build request frames with hal_uart_write and parse responses with hal_uart_read, per the datasheet's communication/frame-format section.`;
|
|
481
|
+
}
|
|
482
|
+
if (can) {
|
|
483
|
+
brief.framing_todo = `Implement ${name}'s CAN frame protocol in ${c.sendCommand}/${c.readData}: build request frames and call hal_can_transfer() with this part's arbitration id(s), per the datasheet's CAN communication/message-ID section.`;
|
|
484
|
+
}
|
|
485
|
+
return brief;
|
|
241
486
|
}
|
|
242
487
|
// ---------------------------------------------------------------------------
|
|
243
|
-
|
|
488
|
+
/**
|
|
489
|
+
* Static prose block shared by the C and cpp command_set init() bodies
|
|
490
|
+
* (mentions no function names, so there is nothing to parameterize).
|
|
491
|
+
*/
|
|
492
|
+
export const COMMAND_INIT_TODO = [
|
|
493
|
+
" /* TODO(driverge): power-up / soft-reset sequence and startup delay. See",
|
|
494
|
+
" * fill_in_brief.init_sequence_todo. */",
|
|
495
|
+
];
|
|
496
|
+
/** The I2C command-frame comment in send_command — no callable names, shared verbatim. */
|
|
497
|
+
export const I2C_COMMAND_FRAME_NOTE = [
|
|
498
|
+
" /* Command frame = [MSB][LSB]. hal_i2c_write emits the reg byte then the",
|
|
499
|
+
" * data bytes, so MSB-as-reg + LSB-as-data yields the correct 2-byte frame. */",
|
|
500
|
+
];
|
|
501
|
+
/**
|
|
502
|
+
* read_data's "implement the actual read framing" TODO comment. `crc8Name` is
|
|
503
|
+
* the bare callable name (no parens) — `<name>_crc8` for C, `crc8` for cpp.
|
|
504
|
+
*/
|
|
505
|
+
export function commandReadDataTodo(crc8Name) {
|
|
506
|
+
return [
|
|
507
|
+
" /* TODO(driverge): read `len` response bytes (data words each followed by a",
|
|
508
|
+
" * CRC byte). Most command-set parts use a plain I2C read with no pointer",
|
|
509
|
+
" * byte — adapt the HAL call to your platform, then check each word with",
|
|
510
|
+
` * ${crc8Name}(). See fill_in_brief.quirks_todo. */`,
|
|
511
|
+
];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Renders a CRC-8 stub function/method body. `qualifiedName` is the full
|
|
515
|
+
* callable name to emit — a free function (`sht3x_crc8`) for C or a qualified
|
|
516
|
+
* method (`Sht3x::crc8`) for cpp — so this single builder covers both without
|
|
517
|
+
* duplicating the TODO prose (Session D: cpp language option).
|
|
518
|
+
*/
|
|
519
|
+
export function crc8Body(qualifiedName, crc, prefix) {
|
|
520
|
+
return [
|
|
521
|
+
`uint8_t ${qualifiedName}(const uint8_t *data, uint16_t len) {`,
|
|
522
|
+
" (void)data;",
|
|
523
|
+
" (void)len;",
|
|
524
|
+
` /* TODO(driverge): implement CRC-${crc.width} (poly ${prefix}_CRC_POLY, init`,
|
|
525
|
+
` * ${prefix}_CRC_INIT) per the datasheet checksum section. See`,
|
|
526
|
+
" * fill_in_brief.crc_todo. */",
|
|
527
|
+
" return 0;",
|
|
528
|
+
"}",
|
|
529
|
+
"",
|
|
530
|
+
];
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* register_map init()'s "implement the power-on/reset sequence" TODO comment.
|
|
534
|
+
* The uart/can branch mentions no callable names (just the seam functions), so
|
|
535
|
+
* it is identical for C and cpp; the register-access branch takes the write
|
|
536
|
+
* call as a parameter (`<name>_write_register()` for C, `writeRegister()` for
|
|
537
|
+
* cpp) so both flavors share this one builder (Session D: cpp language
|
|
538
|
+
* option).
|
|
539
|
+
*/
|
|
540
|
+
export function registerInitTodo(uart, can, writeRegisterCall) {
|
|
541
|
+
if (uart || can) {
|
|
542
|
+
return [
|
|
543
|
+
" /* TODO(driverge): implement the power-on / reset init sequence — the",
|
|
544
|
+
" * correct command/frame sequence and any required startup delay. See",
|
|
545
|
+
" * fill_in_brief.init_sequence_todo and fill_in_brief.framing_todo. Use",
|
|
546
|
+
` * ${uart ? "hal_uart_write()/hal_uart_read()" : "hal_can_transfer()"} and hal_delay_ms(). */`,
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
return [
|
|
550
|
+
" /* TODO(driverge): implement the power-on / reset init sequence — the",
|
|
551
|
+
" * correct register write order and values, plus any required startup",
|
|
552
|
+
` * delay. See fill_in_brief.init_sequence_todo. Use ${writeRegisterCall}`,
|
|
553
|
+
" * and hal_delay_ms(). */",
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
export function makeFiles(name, header, source, ext = { header: "h", source: "c" }) {
|
|
244
557
|
return [
|
|
245
|
-
{ path: `${name}.
|
|
246
|
-
{ path: `${name}.
|
|
558
|
+
{ path: `${name}.${ext.header}`, content: `${header.join("\n").replace(/\n{3,}/g, "\n\n")}` },
|
|
559
|
+
{ path: `${name}.${ext.source}`, content: `${source.join("\n").replace(/\n{3,}/g, "\n\n")}` },
|
|
247
560
|
];
|
|
248
561
|
}
|
|
249
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Render the portable thin-HAL driver skeleton for a validated datasheet.
|
|
564
|
+
* DeviceInterface is a 2-member discriminated union, so narrowing on `kind`
|
|
565
|
+
* here — rather than casting inside registerDriver/commandDriver — lets both
|
|
566
|
+
* take an explicit, correctly-typed array with no cast. Any kind other than
|
|
567
|
+
* "command_set" is treated as register_map, matching buildInterface's
|
|
568
|
+
* register-first fallback (see schema/assemble) exactly.
|
|
569
|
+
*/
|
|
250
570
|
export function generatePortableDriver(json) {
|
|
251
571
|
const part = json.metadata.part;
|
|
252
572
|
const name = slug(part);
|
|
253
573
|
const prefix = macro(name);
|
|
254
574
|
return json.interface.kind === "command_set"
|
|
255
|
-
? commandDriver(json, name, prefix)
|
|
256
|
-
: registerDriver(json, name, prefix);
|
|
575
|
+
? commandDriver(json, json.interface.commands, name, prefix)
|
|
576
|
+
: registerDriver(json, json.interface.registers, name, prefix);
|
|
257
577
|
}
|
|
258
578
|
//# sourceMappingURL=portable.js.map
|