embedcalc-mcp 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +104 -0
- package/dist/index.js +320 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
EmbedCalc MCP — License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EmbedCalc (www.embedcalc.com). All rights reserved.
|
|
4
|
+
|
|
5
|
+
This license applies to embedcalc-mcp version 0.6.6 and later.
|
|
6
|
+
|
|
7
|
+
1. Use. You may install and run the unmodified software, for personal or
|
|
8
|
+
commercial work, including through any MCP client (Claude Desktop,
|
|
9
|
+
Claude Code, Cursor and others), on as many machines as you like.
|
|
10
|
+
|
|
11
|
+
2. Restrictions. Except as expressly allowed by applicable law, you may not:
|
|
12
|
+
(a) copy the software or any part of it other than as needed to install
|
|
13
|
+
and run it;
|
|
14
|
+
(b) modify, decompile, de-minify, disassemble or reverse-engineer it, or
|
|
15
|
+
extract its source code, rules or data;
|
|
16
|
+
(c) redistribute, sell, rent, sublicense or publish it or any part of it,
|
|
17
|
+
including as part of another product or service;
|
|
18
|
+
(d) create derivative works based on it.
|
|
19
|
+
|
|
20
|
+
3. Ownership. The software is licensed, not sold. All rights not expressly
|
|
21
|
+
granted are reserved.
|
|
22
|
+
|
|
23
|
+
4. Earlier versions. Versions 0.6.5 and earlier were published under the MIT
|
|
24
|
+
License; this license does not change the terms of those copies.
|
|
25
|
+
|
|
26
|
+
5. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
27
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
28
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
29
|
+
Results (calculations, generated code, schematic findings) must be checked
|
|
30
|
+
by the user before use in a design.
|
|
31
|
+
|
|
32
|
+
6. Limitation of liability. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
33
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
34
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
35
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# embedcalc-mcp
|
|
2
|
+
|
|
3
|
+
**Exact embedded-engineering math for AI agents.** An MCP server with 40 deterministic calculators, code generators, and schematic checks — so Claude, Cursor, or any MCP-enabled agent computes CRC check values, CAN timing registers, and struct padding, and checks KiCad netlists and PADS PCB files for common MCU board wiring mistakes, instead of hallucinating them.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/embedcalc-mcp)
|
|
6
|
+
[](https://registry.modelcontextprotocol.io)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
LLMs are confidently wrong about exactly the math that bricks boards. Ask any model for a CRC-16/MODBUS check value, an STM32 `CAN_BTR` register for 500 kbit/s @ 36 MHz, or the padded size of a struct — you'll get a plausible-looking wrong answer often enough that you can't trust any of them. The failure mode is the worst kind: the answer *looks* right.
|
|
12
|
+
|
|
13
|
+
This server gives the model tools instead. Every tool is a pure, deterministic implementation pinned with test vectors (standard CRC check values, oracle fixtures, IPC/ASTM published numbers — 192 tests).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
**Claude Code:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude mcp add embedcalc -- npx -y embedcalc-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Claude Desktop / Cursor / any MCP client** (JSON config):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"embedcalc": { "command": "npx", "args": ["-y", "embedcalc-mcp"] }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
No API key, no account. Runs locally over stdio. (The optional `find_component` tool queries embedcalc.com over the network; everything else is offline.) Requires Node.js (`npx`).
|
|
34
|
+
|
|
35
|
+
**Claude Desktop, step by step:**
|
|
36
|
+
|
|
37
|
+
1. Claude menu (the app's menu bar, not the settings inside the chat window) → **Settings…** → **Developer** → **Edit Config**. The file is
|
|
38
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
39
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
40
|
+
2. Add the `embedcalc` entry above under `mcpServers` (keep any servers already there) and save. To pin a version, use `"embedcalc-mcp@0.6.6"` instead of `"embedcalc-mcp"`.
|
|
41
|
+
3. Quit Claude Desktop completely and start it again — the config is only read at start-up.
|
|
42
|
+
4. Check: click **Add files, connectors, and more** (bottom-left of the message box) → **Connectors** → **Manage connectors** → **embedcalc** should list the tools. If it does not, the server's log is `mcp-server-embedcalc.log` in `~/Library/Logs/Claude` (macOS) or `%APPDATA%\Claude\logs` (Windows).
|
|
43
|
+
|
|
44
|
+
## Tools (40)
|
|
45
|
+
|
|
46
|
+
| Domain | Tools |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **Code generation** | `crc_to_c` (7 presets + custom Rocksoft params → compilable table-driven C with check value) · `regmap_to_c` (LSB-first fields → shift/mask defines + bit-field struct) · `bitfield_macro` |
|
|
49
|
+
| **Timing** | `can_bit_timing` (bxCAN `BTR` / FDCAN `NBTP` / MCP2515 `CNF1-3`) · `uart_baud` (STM32 `BRR` + error %) · `i2c_timing` (pull-up range + STM32 `TIMINGR`) · `spi_timing` · `i2s_clocks` · `lin_timing` · `rs485_bus` · `modbus_rtu_timing` (t1.5/t3.5) · `ring_buffer_size` |
|
|
50
|
+
| **Data representation** | `qformat_convert` (Qm.n fixed point) · `ieee754_convert` (float/double, hex↔decimal↔bin) · `number_base_convert` (32-bit two's complement) · `endian_swap` (incl. Modbus BADC/CDAB) · `struct_layout` (C parser → offsets/padding/size, 32-bit MCU model) |
|
|
51
|
+
| **Electronics** | `eseries_nearest` (E6/E12/E24/E96) · `resistor_code` (color bands + SMD/EIA-96) · `crystal_load_caps` · `lm317` · `attenuator_pad` (T/π) · `junction_temp` · `microstrip_impedance` (IPC-2141) · `pcb_trace_width` (IPC-2221) · `awg_wire` · `ne555` · `dcdc_converter` (buck/boost/inverting) · `op_amp` (gain + bandwidth) · `rc_filter` (1st-order LP/HP cutoff) · `bjt_bias` (voltage-divider operating point) · `mosfet_switch` (low-side conduction + gate drive) · `zener_reg` (shunt regulator) |
|
|
52
|
+
| **Components** | `find_component` — real, in-stock MPNs + buy links from a computed value (parametric search) |
|
|
53
|
+
| **Schematic review** (see [limits](#schematic-review--what-it-can-and-cannot-do)) | `review_schematic` (deterministic MCU board-rule check on a netlist: decoupling, bulk cap, floating pins, I2C pull-up, reset, output conflict, power short + interface packs auto-detected from net names — USB ESD/VBUS, CAN termination/transceiver, RS-485 termination/bias, SWD connector/reset, crystal load caps — + polarity & voltage/power derating rules (schema-optional ratings) + STM32F4/STM32F1/ESP32/NuMicro family rules; unevaluable rules reported as `skipped`) · `parse_kicad` (KiCad netlist export → standard schema, auto-parses voltage ratings) · `parse_pads` (PADS PowerPCB ASCII `.asc` or PADS netlist + BOM `.xlsx`/`.xls`/`.csv` → standard schema; real board connectivity, values and fitted/not-fitted from the BOM) · `get_extraction_guide` (**experimental** — schematic image/PDF → schema, transcribed by the AI client) · `check_transcription` (**experimental** — flags contradictions and unwired parts in such a transcription) · `list_schematic_rules` |
|
|
54
|
+
|
|
55
|
+
All tool names are prefixed `embedcalc_` (e.g. `embedcalc_crc_to_c`).
|
|
56
|
+
|
|
57
|
+
## Example prompts
|
|
58
|
+
|
|
59
|
+
> "Give me MODBUS CRC-16 as a C function" → table-driven C, check("123456789") = `0x4B37` embedded for on-target verification
|
|
60
|
+
>
|
|
61
|
+
> "CAN 500 kbit/s on STM32F103, APB1 36 MHz, sample point 87.5%" → `CAN_BTR = 0x00050008` (BRP=9, TS1=6, TS2=1)
|
|
62
|
+
>
|
|
63
|
+
> "How big is `struct { uint8_t flags; uint32_t ts; uint16_t id; }` on a Cortex-M?" → 12 bytes, 5 of them padding, with the layout table
|
|
64
|
+
>
|
|
65
|
+
> "Encode −0.25 in Q1.14" / "What float is 0x7FC00000?" / "Nearest E96 to 12.3 kΩ?"
|
|
66
|
+
|
|
67
|
+
**Schematic review** — give the absolute paths of the design files and the MCU family; the model calls `parse_pads` / `parse_kicad`, then `review_schematic`:
|
|
68
|
+
|
|
69
|
+
> "Review this board with embedcalc: PCB file `C:\boards\NuTiny-N76E885\NUTINY-SDK-N76E885_V21.asc`, BOM `C:\boards\NuTiny-N76E885\NUTINY-SDK-N76E885_V21_BOM.xls`, MCU family numicro" → findings such as no capacitor on the 5V regulator input and no ESD part on USB D+/D-, plus `notFitted` (parts on the PCB but not in the BOM) to check
|
|
70
|
+
>
|
|
71
|
+
> "Review my KiCad netlist `D:\proj\board.net`, MCU family stm32f4" → same review on a KiCad netlist export
|
|
72
|
+
|
|
73
|
+
- Use the PADS `.asc` (PCB or netlist ASCII) and the BOM that vendor reference designs ship next to the PDF. Without a BOM the review still runs, but values and fitted/not-fitted status are unknown.
|
|
74
|
+
- Read `skipped` in the result: those rules could not run on the given data. A finding means the netlist matches a rule, not that the board is wrong — see the limits below.
|
|
75
|
+
|
|
76
|
+
## Accuracy
|
|
77
|
+
|
|
78
|
+
- CRC: all 7 presets verified against standard check values for `"123456789"`
|
|
79
|
+
- CAN: solver output byte-identical to the interactive calculators at [embedcalc.com](https://www.embedcalc.com)
|
|
80
|
+
- IEEE-754 / AWG / IPC: pinned to reference constants and published standard values
|
|
81
|
+
- `struct_layout` uses a 32-bit MCU model (pointers 4 B; `double`/`int64` size 8, align 4) — stated in the tool description so the model can caveat it
|
|
82
|
+
|
|
83
|
+
Every release passes 192 tests, including a stdio end-to-end pass that spawns the built server and calls the tools.
|
|
84
|
+
|
|
85
|
+
## Schematic review — what it can and cannot do
|
|
86
|
+
|
|
87
|
+
- **Reliable input: a design file.** A KiCad netlist (`parse_kicad`) or a PADS PCB ASCII file plus its BOM (`parse_pads`) gives the real connectivity; `review_schematic` then runs the rules on it. Many vendor reference designs ship the PADS `.asc` and BOM next to the PDF. Net names come from the PCB and can differ from the schematic labels; parts missing from the BOM are treated as not fitted and listed in `notFitted`.
|
|
88
|
+
- **Image / PDF input is experimental.** The server does not read images. The AI client transcribes the drawing into a parts/nets list and the rules run on that list, so the result is only as good as the transcription. On an 8-page real-board schematic, three independent transcriptions by AI agents were wrong — invented pin numbers, capacitors and resistors left unconnected, MCU pin names misread — and the reviews built on them were wrong as well. `check_transcription` and the `netlist_consistency` rule flag lists that contradict themselves or are half-finished, but they cannot catch a pin that was misread consistently. Treat an image-based result as a list of things to check on the drawing, not as a review.
|
|
89
|
+
- **Scope.** The rules catch common board mistakes (decoupling and bulk caps, pull-ups, reset, USB/CAN/RS-485/SWD/crystal wiring, MCU-family requirements). They are not a full ERC/DRC and do not read part datasheets; "skipped" in the result lists what was not checked.
|
|
90
|
+
- **Known limitations** (found on a 30-board test run of vendor PADS designs):
|
|
91
|
+
- A rail with no capacitor at all is reported twice: a missing-decoupling error and a missing-bulk warning.
|
|
92
|
+
- A ferrite bead whose BOM value is only a part number (e.g. `GZ1608D601TF`) is not recognised as a bead, so the nets on either side are judged — and reported — as separate rails.
|
|
93
|
+
- Without a BOM, resistor and inductor values are unknown, so 0Ω links and beads are not followed; a crystal or capacitor behind one can be missed.
|
|
94
|
+
- Decoupling and bulk checks only run on rails that feed an IC/MCU pin. A rail that only feeds a part the parser could not classify (a module typed `other`) is not checked.
|
|
95
|
+
- On-board debugger detection relies on the Nuvoton convention of `ICE`-prefixed references for the Nu-Link section; other vendors' on-board debuggers are recognised only when the debug nets link two ICs directly or through a series resistor.
|
|
96
|
+
- A finding means the netlist matches the rule, not that the board is wrong: evaluation boards often leave ESD parts or I2C pull-ups to the attached board on purpose.
|
|
97
|
+
|
|
98
|
+
## Related
|
|
99
|
+
|
|
100
|
+
- [embedcalc.com](https://www.embedcalc.com) — the same 60+ calculators as free interactive tools, no AI required
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
Free to install and use; copying, modifying, reverse-engineering or redistributing the code is not permitted — see [LICENSE](LICENSE). Versions 0.6.5 and earlier were published under MIT.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{McpServer as jr}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Hr}from"@modelcontextprotocol/sdk/server/stdio.js";import{createRequire as Gr}from"node:module";import{z as w}from"zod";var se={"CRC-8":{bits:8,poly:7,init:0,refIn:!1,refOut:!1,xorOut:0},"CRC-8/MAXIM":{bits:8,poly:49,init:0,refIn:!0,refOut:!0,xorOut:0},"CRC-16/CCITT":{bits:16,poly:4129,init:65535,refIn:!1,refOut:!1,xorOut:0},"CRC-16/ARC":{bits:16,poly:32773,init:0,refIn:!0,refOut:!0,xorOut:0},"CRC-16/MODBUS":{bits:16,poly:32773,init:65535,refIn:!0,refOut:!0,xorOut:0},"CRC-32":{bits:32,poly:79764919,init:4294967295,refIn:!0,refOut:!0,xorOut:4294967295},"CRC-32/BZIP2":{bits:32,poly:79764919,init:4294967295,refIn:!1,refOut:!1,xorOut:4294967295}},V=Object.keys(se);function Te(n,t){let e=0;for(let r=0;r<t;r++)n>>>r&1&&(e|=1<<t-1-r);return e>>>0}function z(n){return n===32?4294967295:(1<<n>>>0)-1}function $e(n,t,e){let r=z(t),o=[];if(e){let s=Te(n,t);for(let i=0;i<256;i++){let l=i;for(let a=0;a<8;a++)l=l&1?l>>>1^s:l>>>1;o[i]=l>>>0&r}}else{let s=1<<t-1>>>0;for(let i=0;i<256;i++){let l=i<<t-8>>>0&r;for(let a=0;a<8;a++)l=l&s?(l<<1^n)>>>0:l<<1>>>0,l&=r;o[i]=l>>>0&r}}return o}function pn(n,t,e){let r=z(t.bits),o=e??$e(t.poly,t.bits,t.refIn),s=t.init>>>0&r;if(t.refIn)for(let i=0;i<n.length;i++)s=(s>>>8^o[(s^n[i])&255])>>>0&r;else for(let i=0;i<n.length;i++)s=(s<<8>>>0^o[(s>>>t.bits-8^n[i])&255])>>>0&r;return t.refOut!==t.refIn&&(s=Te(s,t.bits)&r),(s^t.xorOut)>>>0&r}function Ne(n){let t=$e(n.poly,n.bits,n.refIn),e=n.bits,r=e/4,o="uint"+e+"_t",s=b=>"0x"+((b>>>0&z(e))>>>0).toString(16).toUpperCase().padStart(r,"0"),i=[];for(let b=0;b<256;b++)i.push(s(t[b]));let l="";for(let b=0;b<256;b+=8)l+=" "+i.slice(b,b+8).join(", ")+(b+8<256?",":"")+`
|
|
3
|
+
`;let a;e===8?a="crc = crc_table[crc ^ *data++];":n.refIn?a="crc = (crc >> 8) ^ crc_table[(crc ^ *data++) & 0xFF];":a="crc = (crc << 8) ^ crc_table[((crc >> "+(e-8)+") ^ *data++) & 0xFF];";let c=(n.xorOut&z(e))!==0?"return crc ^ "+s(n.xorOut)+";":"return crc;",u=pn([49,50,51,52,53,54,55,56,57],n,t);return{code:"/* CRC-"+e+" poly="+s(n.poly)+" init="+s(n.init)+" refin="+n.refIn+" refout="+n.refOut+" xorout="+s(n.xorOut)+`
|
|
4
|
+
* Generated by EmbedCalc (embedcalc.com). check("123456789") = `+s(u)+` */
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
|
|
8
|
+
static const `+o+` crc_table[256] = {
|
|
9
|
+
`+l+`};
|
|
10
|
+
|
|
11
|
+
`+o+" crc"+e+`_compute(const uint8_t *data, size_t len) {
|
|
12
|
+
`+o+" crc = "+s(n.init)+`;
|
|
13
|
+
while (len--) {
|
|
14
|
+
`+a+`
|
|
15
|
+
}
|
|
16
|
+
`+c+`
|
|
17
|
+
}`,check:s(u)}}function Pe(n){return n.trim().toUpperCase().replace(/[^A-Z0-9_]/g,"_")}function ke(n,t){let e=Pe(n||"REG"),r=t.reduce((a,c)=>a+c.width,0);if(r>32)throw new Error(`Fields total ${r} bits \u2014 must fit in a 32-bit register (uint32_t container).`);let o=0,s="",i=`typedef struct {
|
|
18
|
+
`,l=[];for(let a of t){let c=Pe(a.name),u=Math.pow(2,a.width)-1>>>0<<o>>>0;s+="#define "+e+"_"+c+"_SHIFT "+o+`u
|
|
19
|
+
`,s+="#define "+e+"_"+c+"_MASK 0x"+u.toString(16).toUpperCase()+`u
|
|
20
|
+
`,i+=" uint32_t "+c.toLowerCase()+" : "+a.width+`;
|
|
21
|
+
`,l.push({name:c,width:a.width,shift:o,maskHex:"0x"+u.toString(16).toUpperCase()}),o+=a.width}return i+="} "+e+"_t;",{code:s+`
|
|
22
|
+
`+i,totalBits:o,fields:l}}function Ie(n,t="REG_VAL"){let e=n.replace(/^0x/i,"").replace(/[^0-9a-fA-F]/g,"");e.length>8&&(e=e.slice(-8));let r=parseInt(e||"0",16)>>>0,o;if(r===0)o=`#define ${t} (0U)`;else{let s=[];for(let i=0;i<32;i++)r>>i&1&&s.push(`(1U << ${i})`);o=`#define ${t} (${s.join(" | ")})`}return{dec:r.toString(),hex:"0x"+r.toString(16).toUpperCase().padStart(8,"0"),bin:"0b"+r.toString(2).padStart(32,"0").replace(/(.{8})/g,"$1 ").trim(),onesCount:r.toString(2).split("").filter(s=>s==="1").length,cMacro:o}}function De(n){let t=n.trim();if(t==="")throw new Error("Empty input.");let e;if(/^0x/i.test(t))e=BigInt("0x"+t.slice(2))&0xFFFFFFFFn;else if(/^0b/i.test(t))e=BigInt("0b"+t.slice(2).replace(/\s+/g,""))&0xFFFFFFFFn;else if(/^0o/i.test(t))e=BigInt("0o"+t.slice(2))&0xFFFFFFFFn;else if(/^-?\d+$/.test(t)){let s=BigInt(t);s<0n&&(s=(1n<<32n)+s),e=s&0xFFFFFFFFn}else throw new Error(`Cannot parse '${n}' \u2014 use decimal, 0x hex, 0b binary, or 0o octal.`);let r=e>=0x80000000n?e-0x100000000n:e,o=e.toString(2).padStart(32,"0");return{unsigned:e.toString(),signed:r.toString(),hex:"0x"+e.toString(16).toUpperCase(),bin:o.replace(/(.{4})/g,"$1 ").trim(),oct:"0o"+e.toString(8),onesCount:e.toString(2).replace(/0/g,"").length}}function dn(n){let t=n.replace(/^0x/i,"").replace(/[^0-9A-Fa-f]/g,"");if(t.length===0)throw new Error("Insufficient bytes");return t.length%2!==0&&(t="0"+t),t}function fn(n){let t=[];for(let e=0;e<n.length;e+=2)t.push(parseInt(n.substring(e,e+2),16));return t}function G(n){return n.map(t=>t.toString(16).padStart(2,"0").toUpperCase()).join("")}function bn(n){return[...n].reverse()}function hn(n){if(n.length<4)throw new Error("Requires 32-bit or higher");let t=[];for(let e=0;e<n.length;e+=2)e+1<n.length?t.push(n[e+1],n[e]):t.push(n[e]);return t}function gn(n){if(n.length<4)throw new Error("Requires 32-bit or higher");let t=[];for(let e=0;e<n.length;e+=4)if(e+3<n.length)t.push(n[e+2],n[e+3],n[e],n[e+1]);else for(let r=e;r<n.length;r++)t.push(n[r]);return t}function ze(n){let t=fn(dn(n)),e=null,r=null;return t.length>=4&&(e=G(hn(t)),r=G(gn(t))),{big:G(t),little:G(bn(t)),midLittleBADC:e,midBigCDAB:r,bytes:t}}var U=/^(0x)?[0-9a-fA-F]{1,8}$/;function W(n){return parseInt(n.replace(/^0x/i,"").trim(),16)>>>0}function v(n){return{content:[{type:"text",text:n}],isError:!0}}function y(n,t){return{content:[{type:"text",text:n}],structuredContent:t}}function p(n,t=3){return parseFloat(n.toFixed(t))}function B(n,t=6){return!isFinite(n)||n===0?n===0?0:n:parseFloat(n.toPrecision(t))}var x={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1};function Be(n){n.registerTool("embedcalc_crc_to_c",{title:"CRC \u2192 C Code Generator",description:`Generate a complete, compilable C implementation (256-entry lookup table + compute function) for any CRC-8/16/32, plus its standard check value.
|
|
23
|
+
|
|
24
|
+
Use a preset for common CRCs, or specify the Rocksoft model parameters (width/poly/init/reflect-in/reflect-out/xor-out) for a custom CRC. The generated code embeds check("123456789") so the result can be verified on target.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
- preset (optional): one of ${V.join(", ")}. Overrides all other args.
|
|
28
|
+
- bits (8|16|32), poly_hex (e.g. '0x1021'): required for a custom CRC.
|
|
29
|
+
- init_hex, reflect_in, reflect_out, xor_out_hex: optional custom parameters (defaults: 0x0, false, false, 0x0).
|
|
30
|
+
|
|
31
|
+
Returns (structured): { check_hex, config, c_code }. The text content is the C source itself.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
- "MODBUS RTU CRC in C" -> preset='CRC-16/MODBUS'
|
|
35
|
+
- "CRC-16 poly 0x1021 init 0xFFFF, no reflection" -> bits=16, poly_hex='0x1021', init_hex='0xFFFF'
|
|
36
|
+
Check values are exact (e.g. CRC-16/CCITT -> 0x29B1, CRC-32 -> 0xCBF43926); do not estimate CRCs \u2014 call this tool.`,inputSchema:{preset:w.enum(V).optional().describe("Standard CRC preset. If given, all other parameters are ignored. One of: "+V.join(", ")),bits:w.union([w.literal(8),w.literal(16),w.literal(32)]).optional().describe("CRC width in bits (8, 16, or 32). Required when no preset is given."),poly_hex:w.string().regex(U,"poly_hex must be a hex value like '0x1021'").optional().describe("Generator polynomial in normal (non-reflected) hex form, e.g. '0x1021'. Required when no preset is given."),init_hex:w.string().regex(U,"init_hex must be a hex value like '0xFFFF'").optional().describe("Initial CRC register value in hex (default 0x0)."),reflect_in:w.boolean().optional().describe("Reflect each input byte (LSB-first processing). Default false."),reflect_out:w.boolean().optional().describe("Reflect the final CRC before XOR-out. Default false."),xor_out_hex:w.string().regex(U,"xor_out_hex must be a hex value like '0xFFFFFFFF'").optional().describe("Final XOR value in hex (default 0x0).")},outputSchema:{check_hex:w.string().describe('CRC of the ASCII string "123456789" \u2014 the standard check value for verifying the parameters'),config:w.object({bits:w.number(),poly_hex:w.string(),init_hex:w.string(),reflect_in:w.boolean(),reflect_out:w.boolean(),xor_out_hex:w.string()}),c_code:w.string().describe("Complete, compilable C source: 256-entry lookup table + crcN_compute() function")},annotations:x},async t=>{let e;if(t.preset)e=se[t.preset];else{if(t.bits===void 0||t.poly_hex===void 0)return v("Error: provide either 'preset' or both 'bits' and 'poly_hex'. Available presets: "+V.join(", "));e={bits:t.bits,poly:W(t.poly_hex),init:t.init_hex!==void 0?W(t.init_hex):0,refIn:t.reflect_in??!1,refOut:t.reflect_out??!1,xorOut:t.xor_out_hex!==void 0?W(t.xor_out_hex):0};let l=z(e.bits);for(let[a,c]of[["poly_hex",e.poly],["init_hex",e.init],["xor_out_hex",e.xorOut]])if(c>l)return v(`Error: ${a} 0x${c.toString(16).toUpperCase()} does not fit in ${e.bits} bits (max 0x${l.toString(16).toUpperCase()}).`)}let{code:r,check:o}=Ne(e),s=e.bits/4,i=l=>"0x"+(l>>>0).toString(16).toUpperCase().padStart(s,"0");return y(r,{check_hex:o,config:{bits:e.bits,poly_hex:i(e.poly),init_hex:i(e.init),reflect_in:e.refIn,reflect_out:e.refOut,xor_out_hex:i(e.xorOut)},c_code:r})}),n.registerTool("embedcalc_regmap_to_c",{title:"Register Map \u2192 C Generator",description:`Generate C shift/mask #defines and a bit-field typedef struct for a hardware register from an LSB-first field list.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
- register_name (string): e.g. 'CTRL'. Sanitized to [A-Z0-9_].
|
|
40
|
+
- fields (array of {name, width}): fields from bit 0 (LSB) upward, e.g. [{name:'EN',width:1},{name:'MODE',width:2},{name:'DIV',width:5}]. Total width must fit in 32 bits.
|
|
41
|
+
|
|
42
|
+
Returns (structured): { c_code, total_bits, fields: [{name, width, shift, mask_hex}] }. Text content is the C source (defines + typedef struct with uint32_t bit-fields).
|
|
43
|
+
|
|
44
|
+
Example: EN:1, MODE:2, DIV:5 -> CTRL_EN_SHIFT 0u / CTRL_EN_MASK 0x1u, CTRL_MODE_MASK 0x6u, CTRL_DIV_MASK 0xF8u. Do not compute shifts/masks by hand \u2014 call this tool.`,inputSchema:{register_name:w.string().min(1).max(64).describe("Register name, e.g. 'CTRL'"),fields:w.array(w.object({name:w.string().min(1).max(64).describe("Field name (LSB-first order)"),width:w.number().int().min(1).max(32).describe("Field width in bits")})).min(1).max(32).describe("Fields from LSB upward; total width <= 32 bits")},outputSchema:{c_code:w.string(),total_bits:w.number(),fields:w.array(w.object({name:w.string(),width:w.number(),shift:w.number(),mask_hex:w.string()}))},annotations:x},async t=>{try{let e=ke(t.register_name,t.fields);return y(e.code,{c_code:e.code,total_bits:e.totalBits,fields:e.fields.map(r=>({name:r.name,width:r.width,shift:r.shift,mask_hex:r.maskHex}))})}catch(e){return v(`Error: ${e instanceof Error?e.message:String(e)}`)}}),n.registerTool("embedcalc_bitfield_macro",{title:"Bit Field Value \u2192 C Macro",description:`Decompose a 32-bit register value into set bits and generate a C macro of OR'd (1U << n) terms, plus decimal/binary/popcount views.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
- value_hex (string): 32-bit value, e.g. '0x8001'.
|
|
48
|
+
- macro_name (string, optional): default 'REG_VAL'.
|
|
49
|
+
|
|
50
|
+
Returns (structured): { dec, hex, bin, ones_count, c_macro }.
|
|
51
|
+
Example: '0x8001' -> #define REG_VAL ((1U << 0) | (1U << 15)), 2 bits set.`,inputSchema:{value_hex:w.string().regex(U,"value_hex must be a hex value like '0x8001'").describe("32-bit register value in hex"),macro_name:w.string().min(1).max(64).optional().describe("C macro name (default REG_VAL)")},outputSchema:{dec:w.string(),hex:w.string(),bin:w.string(),ones_count:w.number(),c_macro:w.string()},annotations:x},async t=>{let e=Ie(t.value_hex,t.macro_name??"REG_VAL");return y(`${e.cMacro}
|
|
52
|
+
|
|
53
|
+
// value: ${e.hex} = ${e.dec} = ${e.bin} (${e.onesCount} bits set)`,{dec:e.dec,hex:e.hex,bin:e.bin,ones_count:e.onesCount,c_macro:e.cMacro})})}import{z as f}from"zod";function ae(n,t){return!t||n.err<t.err||n.err===t.err&&n.totalTq>t.totalTq||n.err===t.err&&n.totalTq===t.totalTq&&n.brp<t.brp}function Oe(n,t,e){let r=null;for(let o=1;o<=1024;o++){let s=n/(t*o);if(Math.abs(s-Math.round(s))<1e-6){let i=Math.round(s);if(i>=4&&i<=25)for(let l=1;l<=8;l++){let a=i-1-l;if(a>=1&&a<=16){let c=(1+a)/i*100,u=Math.abs(c-e),d=Math.min(4,l),b={brp:o,ts1:a,ts2:l,sjw:d,sp:c,err:u,totalTq:i};ae(b,r)&&(r=b)}}}}if(r){let o=r.brp-1,s=r.ts1-1,i=r.ts2-1,a=(r.sjw-1&3)<<24|(i&7)<<20|(s&15)<<16|o&1023;return{brp:r.brp,ts1:r.ts1,ts2:r.ts2,sjw:r.sjw,sp:parseFloat(r.sp.toFixed(2)),totalTq:r.totalTq,btrHex:"0x"+(a>>>0).toString(16).toUpperCase().padStart(8,"0")}}return null}function Le(n,t,e){let r=null;for(let o=1;o<=512;o++){let s=n/(t*o);if(Math.abs(s-Math.round(s))<1e-6){let i=Math.round(s);if(i>=4&&i<=385)for(let l=2;l<=128;l++){let a=i-1-l;if(a>=2&&a<=256){let c=(1+a)/i*100,u=Math.abs(c-e),d=Math.min(128,l),b={brp:o,ts1:a,ts2:l,sjw:d,sp:c,err:u,totalTq:i};ae(b,r)&&(r=b)}}}}if(r){let o=r.brp-1,s=r.ts1-1,i=r.ts2-1,a=(r.sjw-1&127)<<25|(o&511)<<16|(s&255)<<8|i&127;return{brp:r.brp,ts1:r.ts1,ts2:r.ts2,sjw:r.sjw,sp:parseFloat(r.sp.toFixed(2)),totalTq:r.totalTq,nbtpHex:"0x"+(a>>>0).toString(16).toUpperCase().padStart(8,"0")}}return null}function Ve(n,t,e){let r=null;for(let o=1;o<=64;o++){let s=n/(2*t*o);if(Math.abs(s-Math.round(s))<1e-6){let i=Math.round(s);if(i>=5&&i<=25)for(let l=2;l<=8;l++){let a=i-1-l;if(a>=2&&a<=16)for(let c=1;c<=8;c++){let u=a-c;if(u>=1&&u<=8&&l>=u){let d=(1+c+u)/i*100,b=Math.abs(d-e),_=Math.min(4,l,u),h={brp:o,propSeg:c,phaseSeg1:u,phaseSeg2:l,sjw:_,sp:d,err:b,totalTq:i};ae(h,r)&&(r=h)}}}}}if(r){let o=r.brp-1,s=r.sjw-1,i=r.propSeg-1,l=r.phaseSeg1-1,a=r.phaseSeg2-1,c=(s&3)<<6|o&63,u=128|(l&7)<<3|i&7,d=a&7;return{brp:r.brp,propSeg:r.propSeg,phaseSeg1:r.phaseSeg1,phaseSeg2:r.phaseSeg2,sjw:r.sjw,sp:parseFloat(r.sp.toFixed(2)),totalTq:r.totalTq,cnf1Hex:"0x"+c.toString(16).toUpperCase().padStart(2,"0"),cnf2Hex:"0x"+u.toString(16).toUpperCase().padStart(2,"0"),cnf3Hex:"0x"+d.toString(16).toUpperCase().padStart(2,"0")}}return null}function Ue(n,t,e=16){let r=n/(e*t),o=Math.floor(r),s,i,l;e===16?(s=Math.round((r-o)*16),s>=16&&(o+=1,s=0),i=n/(16*(o+s/16)),l=o<<4|s&15):(s=Math.round((r-o)*8),s>=8&&(o+=1,s=0),i=n/(8*(o+s/8)),l=o<<4|s&7);let a=(i-t)/t*100;return{exact:r,mantissa:o,fraction:s,brr:l,brrHex:"0x"+l.toString(16).toUpperCase().padStart(4,"0"),actualBaud:i,errorPercent:a}}function je(n,t,e){let r=t==="none"?0:1;return 1+n+r+e}function He(n){return 1e9/n}function Ge(n,t){return t/n*1e6}function We(n){return n/1e6}function ce(n,t,e){return n*t*e}function qe(n,t,e){return 1e9/ce(n,t,e)}function Ke(n){return 1e6/n}function Je(n){return 13/n*1e6}function Ze(n,t){return(43+10*t)/n*1e6}function Xe(n){return Math.min(1200,1e8/n)}function Qe(n){return 150*n-30}function Ye(n,t){return n*60/(60+2*t)}function et(n){return 11/n*1e6}function tt(n){return n>19200?750:1.5*11/n*1e6}function nt(n){return n>19200?1750:3.5*11/n*1e6}function rt(n,t,e){return n*(t/1e3)*e}function it(n){return Math.pow(2,Math.ceil(Math.log2(n)))}var _n={ls:{targetFreq:1e4,tRiseMax:1e-6,tFallMax:3e-7,isink:.003,sclHighMin:4e-5,sclLowMin:47e-6},sm:{targetFreq:1e5,tRiseMax:1e-6,tFallMax:3e-7,isink:.003,sclHighMin:4e-6,sclLowMin:47e-7},fm:{targetFreq:4e5,tRiseMax:3e-7,tFallMax:3e-7,isink:.003,sclHighMin:6e-7,sclLowMin:13e-7},fmp:{targetFreq:1e6,tRiseMax:12e-8,tFallMax:12e-8,isink:.02,sclHighMin:26e-8,sclLowMin:5e-7},hs:{targetFreq:34e5,tRiseMax:8e-8,tFallMax:8e-8,isink:.003,sclHighMin:6e-8,sclLowMin:16e-8}};function ot(n,t,e,r){let o=_n[n],{targetFreq:s,tRiseMax:i,tFallMax:l,isink:a}=o,c=.4,u=t>c?(t-c)/a:0,d=e*1e-12,b=d>0?i/(.8473*d):0,_=1/s,h=_*.4,C=_*.6,E=1/(r*1e6),F=0;r>32?F=3:r>=16?F=1:F=0;let M=E*(F+1),D=Math.max(0,Math.round(C/M)-1),H=Math.max(0,Math.round(h/M)-1),Ae=Math.max(0,Math.round(i/2/M)),Me=Math.min(15,Math.max(0,Math.ceil((i+(n==="sm"||n==="ls"?25e-8:1e-7))/M)-1)),mn=(F&15)<<28|(Me&15)<<20|(Ae&15)<<16|(H&255)<<8|D&255;return{targetFreqHz:s,sclPeriodUs:_*1e6,sclHighUs:h*1e6,sclLowUs:C*1e6,sclHighMinSpecUs:o.sclHighMin*1e6,sclLowMinSpecUs:o.sclLowMin*1e6,tRiseMaxNs:i*1e9,tFallMaxNs:l*1e9,rpMinOhm:u,rpMaxOhm:b,stm32:{presc:F,scll:D,sclh:H,sdadel:Ae,scldel:Me,timingrHex:"0x"+(mn>>>0).toString(16).toUpperCase().padStart(8,"0")}}}function st(n){let t={brp:f.number().describe("Baud rate prescaler (1-based, as written in reference manuals)"),sjw:f.number().describe("Synchronization jump width in TQ"),sp:f.number().describe("Achieved sample point in percent"),totalTq:f.number().describe("Time quanta per bit")};n.registerTool("embedcalc_can_bit_timing",{title:"CAN Bit Timing Calculator",description:`Compute exact CAN bit timing (BRP, segments, SJW, register values) for STM32 bxCAN, STM32 FDCAN (nominal/arbitration phase), and MCP2515 from clock, bitrate, and target sample point.
|
|
54
|
+
|
|
55
|
+
Searches all valid prescaler/segment combinations and returns, per controller, the configuration whose sample point is closest to the target (ties: more TQ per bit, then smaller BRP). Register values are ready to write: bxCAN CAN_BTR, FDCAN FDCAN_NBTP, MCP2515 CNF1/CNF2/CNF3 (BTLMODE=1). All values follow the controller datasheets; BRP/TS values are 1-based (register encoding = value - 1 is already applied inside the hex words).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
- clock_hz (int): controller input clock in Hz. bxCAN: APB1; FDCAN: kernel clock; MCP2515: crystal Fosc (MCP2515 uses TQ = 2*BRP/Fosc).
|
|
59
|
+
- bitrate_bps (int): nominal bitrate in bit/s (classic CAN: up to 1000000).
|
|
60
|
+
- sample_point_percent (50-95, default 87.5): CiA 301 recommends 87.5.
|
|
61
|
+
|
|
62
|
+
Returns (structured): { bxcan, fdcan, mcp2515 } \u2014 each a timing object or null when the clock/bitrate ratio has no exact integer divider for that controller (a null means: change the clock or bitrate, do not approximate).
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
- "500 kbit/s on STM32F103 (APB1 36 MHz)" -> clock_hz=36000000, bitrate_bps=500000
|
|
66
|
+
- "1 Mbit/s, 16 MHz MCP2515, sample point 75%" -> clock_hz=16000000, bitrate_bps=1000000, sample_point_percent=75
|
|
67
|
+
Do not guess CAN timing registers \u2014 call this tool.`,inputSchema:{clock_hz:f.number().int().positive().describe("CAN controller input clock in Hz (bxCAN: APB1 clock; FDCAN: kernel clock; MCP2515: crystal Fosc). E.g. 36000000 for 36 MHz."),bitrate_bps:f.number().int().positive().describe("Target nominal bitrate in bit/s, e.g. 500000 for 500 kbit/s."),sample_point_percent:f.number().min(50).max(95).default(87.5).describe("Target sample point in percent. CiA 301 recommends 87.5 (the default).")},outputSchema:{bxcan:f.object({...t,ts1:f.number(),ts2:f.number(),btrHex:f.string().describe("CAN_BTR register value (SILM/LBKM bits zero)")}).nullable().describe("STM32 bxCAN solution, or null if no exact divider exists"),fdcan:f.object({...t,ts1:f.number(),ts2:f.number(),nbtpHex:f.string().describe("FDCAN_NBTP register value")}).nullable().describe("STM32 FDCAN nominal (arbitration phase) solution, or null"),mcp2515:f.object({...t,propSeg:f.number(),phaseSeg1:f.number(),phaseSeg2:f.number(),cnf1Hex:f.string(),cnf2Hex:f.string(),cnf3Hex:f.string()}).nullable().describe("MCP2515 solution (BTLMODE=1, SAM=0), or null")},annotations:x},async e=>{let r=e.sample_point_percent,o=Oe(e.clock_hz,e.bitrate_bps,r),s=Le(e.clock_hz,e.bitrate_bps,r),i=Ve(e.clock_hz,e.bitrate_bps,r),l=[`# CAN bit timing \u2014 clock ${e.clock_hz/1e6} MHz, ${e.bitrate_bps/1e3} kbit/s, target SP ${r}%`,"","## STM32 bxCAN",o?`BRP=${o.brp}, TS1=${o.ts1}, TS2=${o.ts2}, SJW=${o.sjw} \u2014 SP ${o.sp}%, ${o.totalTq} TQ \u2014 CAN_BTR = ${o.btrHex}`:"No solution (clock/bitrate has no exact divider in bxCAN's range).","","## STM32 FDCAN (nominal)",s?`BRP=${s.brp}, TS1=${s.ts1}, TS2=${s.ts2}, SJW=${s.sjw} \u2014 SP ${s.sp}%, ${s.totalTq} TQ \u2014 FDCAN_NBTP = ${s.nbtpHex}`:"No solution (clock/bitrate has no exact divider in FDCAN's range).","","## MCP2515",i?`BRP=${i.brp}, PropSeg=${i.propSeg}, PS1=${i.phaseSeg1}, PS2=${i.phaseSeg2}, SJW=${i.sjw} \u2014 SP ${i.sp}%, ${i.totalTq} TQ \u2014 CNF1/2/3 = ${i.cnf1Hex} / ${i.cnf2Hex} / ${i.cnf3Hex}`:"No solution (remember MCP2515 TQ = 2*BRP/Fosc \u2014 the effective clock is Fosc/2)."];return y(l.join(`
|
|
68
|
+
`),{bxcan:o,fdcan:s,mcp2515:i})}),n.registerTool("embedcalc_uart_baud",{title:"UART Baud Rate / BRR Calculator",description:`Compute the STM32 USART BRR register (mantissa + fraction), the actual achieved baud rate, and the baud error for a given peripheral clock \u2014 plus frame size/character time for the given format.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
- clock_hz (int): USART peripheral clock in Hz (e.g. 72000000).
|
|
72
|
+
- baud (int): target baud rate (e.g. 115200).
|
|
73
|
+
- oversampling (8|16, default 16): USART OVER8 setting.
|
|
74
|
+
- data_bits (5-9, default 8), parity ('none'|'even'|'odd', default 'none'), stop_bits (1|1.5|2, default 1).
|
|
75
|
+
|
|
76
|
+
Returns (structured): { mantissa, fraction, brr_hex, actual_baud, error_percent, frame_bits, char_time_us }.
|
|
77
|
+
Rule of thumb: |error| < 2% total link budget; keep your side < 0.5% when possible.
|
|
78
|
+
Example: 72 MHz, 115200, over16 -> BRR=0x0271 (mantissa 39, fraction 1), 0.00% error.`,inputSchema:{clock_hz:f.number().int().positive().describe("USART peripheral clock in Hz"),baud:f.number().int().positive().describe("Target baud rate in bit/s"),oversampling:f.union([f.literal(8),f.literal(16)]).default(16).describe("Oversampling (OVER8): 16 or 8"),data_bits:f.number().int().min(5).max(9).default(8).describe("Data bits per frame"),parity:f.enum(["none","even","odd"]).default("none").describe("Parity"),stop_bits:f.union([f.literal(1),f.literal(1.5),f.literal(2)]).default(1).describe("Stop bits")},outputSchema:{mantissa:f.number(),fraction:f.number(),brr_hex:f.string().describe("USART_BRR register value"),actual_baud:f.number(),error_percent:f.number(),frame_bits:f.number().describe("start + data + parity + stop bits"),char_time_us:f.number().describe("Time per character at actual baud")},annotations:x},async e=>{let r=Ue(e.clock_hz,e.baud,e.oversampling),o=je(e.data_bits,e.parity,e.stop_bits),s=o/r.actualBaud*1e6,i={mantissa:r.mantissa,fraction:r.fraction,brr_hex:r.brrHex,actual_baud:p(r.actualBaud,2),error_percent:p(r.errorPercent,4),frame_bits:o,char_time_us:p(s,3)},l=`UART ${e.baud} baud @ ${e.clock_hz/1e6} MHz (over${e.oversampling}): BRR = ${r.brrHex} (mantissa ${r.mantissa}, fraction ${r.fraction}), actual ${i.actual_baud} baud, error ${i.error_percent}%
|
|
79
|
+
Frame ${e.data_bits}${e.parity[0].toUpperCase()}${e.stop_bits}: ${o} bits, ${i.char_time_us} \xB5s/char`;return y(l,i)}),n.registerTool("embedcalc_i2c_timing",{title:"I2C Timing & Pull-Up Calculator",description:`Compute I2C pull-up resistor range (Rp min from sink current, Rp max from rise time vs bus capacitance), SCL high/low budget, and an STM32 I2C TIMINGR register estimate.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
- mode: 'ls' (10k), 'sm' (100k), 'fm' (400k), 'fmp' (1M, 20mA sink), 'hs' (3.4M).
|
|
83
|
+
- vdd (V, default 3.3), bus_capacitance_pf (default 100).
|
|
84
|
+
- stm32_clock_mhz (default 16): I2CCLK kernel clock for the TIMINGR estimate.
|
|
85
|
+
|
|
86
|
+
Returns (structured): rp_min_ohm / rp_max_ohm, scl high/low vs spec minimums, rise/fall limits, stm32 { presc, scll, sclh, sdadel, scldel, timingr_hex }.
|
|
87
|
+
The TIMINGR value is an estimate per RM formulas \u2014 verify on scope for production.
|
|
88
|
+
Example: Fast-mode, 3.3V, 100pF -> Rp between ~967\u03A9 and ~3.5k\u03A9 (use 2.2k).`,inputSchema:{mode:f.enum(["ls","sm","fm","fmp","hs"]).default("sm").describe("Bus speed mode: ls=10kHz, sm=100kHz, fm=400kHz, fmp=1MHz, hs=3.4MHz"),vdd:f.number().positive().max(6).default(3.3).describe("Bus supply voltage in volts"),bus_capacitance_pf:f.number().positive().max(1e3).default(100).describe("Total bus capacitance in pF (spec max 400 pF)"),stm32_clock_mhz:f.number().positive().max(200).default(16).describe("STM32 I2C kernel clock (I2CCLK) in MHz for TIMINGR estimate")},outputSchema:{target_freq_hz:f.number(),rp_min_ohm:f.number(),rp_max_ohm:f.number(),scl_high_us:f.number(),scl_low_us:f.number(),scl_high_min_spec_us:f.number(),scl_low_min_spec_us:f.number(),t_rise_max_ns:f.number(),t_fall_max_ns:f.number(),stm32:f.object({presc:f.number(),scll:f.number(),sclh:f.number(),sdadel:f.number(),scldel:f.number(),timingr_hex:f.string()})},annotations:x},async e=>{let r=ot(e.mode,e.vdd,e.bus_capacitance_pf,e.stm32_clock_mhz),o={target_freq_hz:r.targetFreqHz,rp_min_ohm:p(r.rpMinOhm,1),rp_max_ohm:p(r.rpMaxOhm,1),scl_high_us:p(r.sclHighUs,3),scl_low_us:p(r.sclLowUs,3),scl_high_min_spec_us:p(r.sclHighMinSpecUs,3),scl_low_min_spec_us:p(r.sclLowMinSpecUs,3),t_rise_max_ns:r.tRiseMaxNs,t_fall_max_ns:r.tFallMaxNs,stm32:{presc:r.stm32.presc,scll:r.stm32.scll,sclh:r.stm32.sclh,sdadel:r.stm32.sdadel,scldel:r.stm32.scldel,timingr_hex:r.stm32.timingrHex}},s=`I2C ${e.mode} (${r.targetFreqHz/1e3} kHz), Vdd ${e.vdd} V, Cb ${e.bus_capacitance_pf} pF:
|
|
89
|
+
Pull-up Rp: ${o.rp_min_ohm} \u03A9 (min, sink limit) \u2026 ${o.rp_max_ohm} \u03A9 (max, rise time)
|
|
90
|
+
SCL high/low: ${o.scl_high_us}/${o.scl_low_us} \xB5s (spec min ${o.scl_high_min_spec_us}/${o.scl_low_min_spec_us} \xB5s)
|
|
91
|
+
STM32 @ ${e.stm32_clock_mhz} MHz: TIMINGR \u2248 ${r.stm32.timingrHex} (PRESC=${r.stm32.presc}, SCLL=${r.stm32.scll}, SCLH=${r.stm32.sclh}, SDADEL=${r.stm32.sdadel}, SCLDEL=${r.stm32.scldel}) \u2014 verify on scope.`;return y(s,o)}),n.registerTool("embedcalc_spi_timing",{title:"SPI Timing Calculator",description:`SPI clock/frame timing: bit period, frame time, raw throughput.
|
|
92
|
+
Args: sck_hz (SPI clock in Hz), bits_per_frame (default 8).
|
|
93
|
+
Returns (structured): { bit_period_ns, frame_time_us, throughput_mbps }.
|
|
94
|
+
Example: 10 MHz, 8 bits -> 100 ns/bit, 0.8 \xB5s/frame, 10 Mbit/s.`,inputSchema:{sck_hz:f.number().int().positive().describe("SCK clock frequency in Hz"),bits_per_frame:f.number().int().min(1).max(64).default(8).describe("Bits per frame/transfer")},outputSchema:{bit_period_ns:f.number(),frame_time_us:f.number(),throughput_mbps:f.number()},annotations:x},async e=>{let r={bit_period_ns:p(He(e.sck_hz),3),frame_time_us:p(Ge(e.sck_hz,e.bits_per_frame),4),throughput_mbps:p(We(e.sck_hz),3)};return y(`SPI @ ${e.sck_hz/1e6} MHz: ${r.bit_period_ns} ns/bit, ${r.frame_time_us} \xB5s per ${e.bits_per_frame}-bit frame, ${r.throughput_mbps} Mbit/s raw`,r)}),n.registerTool("embedcalc_i2s_clocks",{title:"I2S Clock Calculator",description:`I2S bit clock (BCLK/SCK) from audio format: BCLK = Fs x bits x channels.
|
|
95
|
+
Args: sample_rate_hz (e.g. 48000), bits_per_sample (e.g. 16/24/32), channels (default 2).
|
|
96
|
+
Returns (structured): { bclk_hz, bclk_period_ns, lrclk_hz }.
|
|
97
|
+
Example: 48 kHz, 24-bit, stereo -> BCLK 2.304 MHz (434.03 ns), LRCLK 48 kHz.`,inputSchema:{sample_rate_hz:f.number().positive().describe("Sample rate Fs in Hz (44100, 48000, ...)"),bits_per_sample:f.number().int().min(8).max(32).describe("Bits per sample per channel (16/24/32)"),channels:f.number().int().min(1).max(8).default(2).describe("Channel count (2 = stereo)")},outputSchema:{bclk_hz:f.number(),bclk_period_ns:f.number(),lrclk_hz:f.number()},annotations:x},async e=>{let r={bclk_hz:p(ce(e.sample_rate_hz,e.bits_per_sample,e.channels),1),bclk_period_ns:p(qe(e.sample_rate_hz,e.bits_per_sample,e.channels),2),lrclk_hz:e.sample_rate_hz};return y(`I2S ${e.sample_rate_hz/1e3} kHz \xD7 ${e.bits_per_sample}-bit \xD7 ${e.channels}ch: BCLK = ${r.bclk_hz} Hz (${r.bclk_period_ns} ns), LRCLK = ${r.lrclk_hz} Hz`,r)}),n.registerTool("embedcalc_lin_timing",{title:"LIN Bus Timing Calculator",description:`LIN bus timing: bit time, break field duration (13 bits), and worst-case frame time per LIN 2.x nominal (header 34 + response 10*(n+1) bits with 1.4x budget = (43+10n)/baud).
|
|
98
|
+
Args: baud (typ. 9600/19200), data_bytes (1-8).
|
|
99
|
+
Returns (structured): { bit_time_us, break_time_us, frame_time_us }.
|
|
100
|
+
Example: 19200 baud, 8 bytes -> bit 52.08 \xB5s, break 677 \xB5s, frame 6406 \xB5s.`,inputSchema:{baud:f.number().int().positive().max(2e4).default(19200).describe("LIN baud rate (max 20 kbit/s)"),data_bytes:f.number().int().min(1).max(8).default(8).describe("Response data bytes")},outputSchema:{bit_time_us:f.number(),break_time_us:f.number(),frame_time_us:f.number()},annotations:x},async e=>{let r={bit_time_us:p(Ke(e.baud),3),break_time_us:p(Je(e.baud),2),frame_time_us:p(Ze(e.baud,e.data_bytes),2)};return y(`LIN @ ${e.baud} baud, ${e.data_bytes} data bytes: bit ${r.bit_time_us} \xB5s, break ${r.break_time_us} \xB5s, worst-case frame ${r.frame_time_us} \xB5s`,r)}),n.registerTool("embedcalc_rs485_bus",{title:"RS-485 Bus Calculator",description:`RS-485 cable length limit (rule of thumb: length_m x bitrate_bps <= 10^8, capped 1200 m) and fail-safe bias resistors for >= 200 mV idle differential across 60 \u03A9 (two 120 \u03A9 terminators).
|
|
101
|
+
Args: baud_bps, vcc (default 5), bias_ohm (optional \u2014 defaults to the computed maximum).
|
|
102
|
+
Returns (structured): { max_cable_m, max_bias_ohm, bias_used_ohm, idle_vdiff_v, meets_200mv }.
|
|
103
|
+
Example: 100 kbit/s -> 1000 m; Vcc=5 -> bias <= 720 \u03A9 per side for 0.2 V idle.`,inputSchema:{baud_bps:f.number().int().positive().describe("Signaling rate in bit/s"),vcc:f.number().positive().max(12).default(5).describe("Bias supply voltage"),bias_ohm:f.number().positive().optional().describe("Chosen bias resistor per side in \u03A9 (default: computed maximum)")},outputSchema:{max_cable_m:f.number(),max_bias_ohm:f.number().describe("Maximum bias resistor per side for 200 mV idle differential"),bias_used_ohm:f.number(),idle_vdiff_v:f.number(),meets_200mv:f.boolean()},annotations:x},async e=>{let r=Qe(e.vcc),o=e.bias_ohm??r,s=Ye(e.vcc,o),i={max_cable_m:p(Xe(e.baud_bps),1),max_bias_ohm:p(r,1),bias_used_ohm:p(o,1),idle_vdiff_v:p(s,4),meets_200mv:s>=.2-1e-9};return y(`RS-485 @ ${e.baud_bps} bit/s: max cable \u2248 ${i.max_cable_m} m.
|
|
104
|
+
Fail-safe bias @ Vcc ${e.vcc} V: \u2264 ${i.max_bias_ohm} \u03A9 per side; with ${i.bias_used_ohm} \u03A9 idle Vdiff = ${i.idle_vdiff_v} V (${i.meets_200mv?"OK":"BELOW"} 200 mV)`,i)}),n.registerTool("embedcalc_modbus_rtu_timing",{title:"Modbus RTU Timing Calculator",description:`Modbus RTU inter-character (t1.5) and inter-frame (t3.5) silence times. Per spec: 11 bits/char; above 19200 baud fixed t1.5=750 \xB5s and t3.5=1750 \xB5s.
|
|
105
|
+
Args: baud.
|
|
106
|
+
Returns (structured): { char_time_us, t15_us, t35_us }.
|
|
107
|
+
Example: 9600 -> char 1145.8 \xB5s, t1.5 1718.8 \xB5s, t3.5 4010.4 \xB5s; 115200 -> 750/1750 \xB5s fixed.`,inputSchema:{baud:f.number().int().positive().describe("Baud rate in bit/s")},outputSchema:{char_time_us:f.number(),t15_us:f.number(),t35_us:f.number()},annotations:x},async e=>{let r={char_time_us:p(et(e.baud),2),t15_us:p(tt(e.baud),2),t35_us:p(nt(e.baud),2)};return y(`Modbus RTU @ ${e.baud} baud: char ${r.char_time_us} \xB5s, t1.5 = ${r.t15_us} \xB5s, t3.5 = ${r.t35_us} \xB5s`,r)}),n.registerTool("embedcalc_ring_buffer_size",{title:"Ring Buffer / DMA Size Calculator",description:`Size a ring buffer (or DMA buffer) from data rate, worst-case consumer latency, and a safety factor, rounded up to a power of two.
|
|
108
|
+
Args: data_rate_bytes_per_sec, latency_ms (worst-case time before the consumer drains), safety_factor (default 2).
|
|
109
|
+
Returns (structured): { bytes_needed, recommended_pow2 }.
|
|
110
|
+
Example: 102400 B/s, 10 ms, \xD72 -> 2048 bytes -> use 2048 (2^11).`,inputSchema:{data_rate_bytes_per_sec:f.number().positive().describe("Incoming data rate in bytes/second"),latency_ms:f.number().positive().describe("Worst-case consumer latency in milliseconds"),safety_factor:f.number().min(1).max(16).default(2).describe("Headroom multiplier (default 2)")},outputSchema:{bytes_needed:f.number(),recommended_pow2:f.number().describe("Next power of two >= bytes_needed (fast masking index math)")},annotations:x},async e=>{let r=rt(e.data_rate_bytes_per_sec,e.latency_ms,e.safety_factor),o={bytes_needed:p(r,1),recommended_pow2:it(Math.max(1,Math.ceil(r)))};return y(`Ring buffer: ${o.bytes_needed} bytes needed (${e.data_rate_bytes_per_sec} B/s \xD7 ${e.latency_ms} ms \xD7 ${e.safety_factor}) \u2192 recommend ${o.recommended_pow2} bytes (power of two)`,o)})}import{z as g}from"zod";function yn(n,t){return t>=32?n>>>0:(n&(1<<t)-1)>>>0}function le(n,t,e){let r=t+e+1;if(r>32)throw new Error(`Q${t}.${e} needs ${r} bits \u2014 max 32 (1 sign + m + n).`);let o=Math.pow(2,e),s=(Math.pow(2,t+e)-1)/o,i=-Math.pow(2,t+e)/o,l=Math.max(i,Math.min(s,n)),a=Math.round(l*o),c=a/o,u=yn(a,r);return{format:`Q${t}.${e} (${r}-bit signed)`,totalBits:r,scale:o,rangeMin:i,rangeMax:s,rawInt:a,hex:"0x"+u.toString(16).toUpperCase().padStart(Math.ceil(r/4),"0"),bin:"0b"+u.toString(2).padStart(r,"0"),actual:c,error:Math.abs(n-c),clamped:l!==n}}function at(n,t,e){let r=n/Math.pow(2,e);return le(r,t,e)}function O(n,t,e,r){if(r){let o=new Float64Array(1),s=new Uint32Array(o.buffer),i=e&4294967295,l=Math.floor(e/4294967296)&1048575,a=((n&1)<<31|(t&2047)<<20|l)>>>0,c=i>>>0;return s[0]=c,s[1]=a,{sign:n,exponent:t,fraction:e,hexString:a.toString(16).toUpperCase().padStart(8,"0")+c.toString(16).toUpperCase().padStart(8,"0"),binString:a.toString(2).padStart(32,"0")+c.toString(2).padStart(32,"0"),decimal:o[0],type:"double"}}else{let o=new Float32Array(1),s=new Uint32Array(o.buffer),i=((n&1)<<31|(t&255)<<23|e&8388607)>>>0;return s[0]=i,{sign:n,exponent:t,fraction:e,hexString:i.toString(16).toUpperCase().padStart(8,"0"),binString:i.toString(2).padStart(32,"0"),decimal:o[0],type:"float"}}}function ct(n,t){let e=Number(n);if(t){let r=new Float64Array(1),o=new Uint32Array(r.buffer);r[0]=e;let s=o[0],i=o[1];return O(i>>>31&1,i>>>20&2047,(i&1048575)*4294967296+(s>>>0),!0)}else{let r=new Float32Array(1),o=new Uint32Array(r.buffer);r[0]=e;let s=o[0];return O(s>>>31&1,s>>>23&255,s&8388607,!1)}}function lt(n,t){let e=n.replace(/^0x/i,"").padStart(t?16:8,"0");if(t){let r=parseInt(e.substring(0,8),16)>>>0,o=parseInt(e.substring(8,16),16)>>>0;return O(r>>>31&1,r>>>20&2047,(r&1048575)*4294967296+(o>>>0),!0)}else{let r=parseInt(e,16)>>>0;return O(r>>>31&1,r>>>23&255,r&8388607,!1)}}function ut(n,t){let e=n.replace(/[^01]/g,"").padStart(t?64:32,"0");if(t){let r=parseInt(e.substring(0,32),2)>>>0,o=parseInt(e.substring(32,64),2)>>>0;return O(r>>>31&1,r>>>20&2047,(r&1048575)*4294967296+(o>>>0),!0)}else{let r=parseInt(e,2)>>>0;return O(r>>>31&1,r>>>23&255,r&8388607,!1)}}function mt(n){let t=n.type==="double"?2047:255;return n.exponent===t?n.fraction===0?"infinity":"nan":n.exponent===0?n.fraction===0?"zero":"subnormal":"normal"}var xn={char:{size:1,align:1},int8_t:{size:1,align:1},uint8_t:{size:1,align:1},short:{size:2,align:2},int16_t:{size:2,align:2},uint16_t:{size:2,align:2},int:{size:4,align:4},float:{size:4,align:4},int32_t:{size:4,align:4},uint32_t:{size:4,align:4},double:{size:8,align:4},uint64_t:{size:8,align:4},int64_t:{size:8,align:4},pointer:{size:4,align:4},"*":{size:4,align:4}};function q(n,t){if(t)return{size:4,align:4};let e=n.replace(/^(unsigned|signed)\s+/,"").trim();return xn[e]??{size:4,align:4}}function vn(n){n=n.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm," ");let t=/\s*([a-zA-Z0-9_]+|[{};:*[\]:])\s*/g,e=[],r;for(;(r=t.exec(n))!==null;)e.push(r[1]);return e}function wn(n){let t=0;function e(){let i=[];for(;t<n.length&&n[t]!=="}";){let l=r();l&&i.push(l)}return i}function r(){if(t>=n.length)return null;if(n[t]==="struct"||n[t]==="union"){let i=n[t];t++;let l=null;if(t<n.length&&/^[a-zA-Z0-9_]+$/.test(n[t])&&n[t]!=="{"&&(l=n[t],t++),t<n.length&&n[t]==="{"){t++;let a=e();t<n.length&&n[t]==="}"&&t++;let c="",u=null;return t<n.length&&n[t]!==";"&&(c=n[t],t++,t<n.length&&n[t]==="["&&(t++,t<n.length&&/^\d+$/.test(n[t])&&(u=parseInt(n[t],10),t++),t<n.length&&n[t]==="]"&&t++)),t<n.length&&n[t]===";"&&t++,{type:i,name:c,tag:l,isBlock:!0,members:a,isArray:u!==null,arrayCount:u||1}}else{let a=i+(l?" "+l:"");return o(a)}}else{let i=[];for(;t<n.length&&n[t]!==";"&&n[t]!=="}";)i.push(n[t]),t++;return t<n.length&&n[t]===";"&&t++,s(i)}}function o(i){let l=[i];for(;t<n.length&&n[t]!==";"&&n[t]!=="}";)l.push(n[t]),t++;return t<n.length&&n[t]===";"&&t++,s(l)}function s(i){if(i.length===0)return null;let l=i.indexOf(":");if(l!==-1){let C=parseInt(i[l+1],10),E="",F=[];l===1?(F=[i[0]],E=""):(F=i.slice(0,l-1),E=i[l-1]);let M=F.includes("*");return{type:F.filter(H=>H!=="*").join(" "),name:E,isBitField:!0,bitWidth:C,isPointer:M}}let a=i.indexOf("["),c=null,u=i.length-1;a!==-1&&(u=a-1,c=parseInt(i[a+1],10));let d=i[u]||"",b=i.slice(0,u),_=b.includes("*");return{type:b.filter(C=>C!=="*").join(" ")+(_?"*":""),name:d,isBitField:!1,isArray:c!==null,arrayCount:c||1,isPointer:_}}return e()}function ue(n,t=0,e=!1){let r=t,o=1,s=[],i=null;function l(){i&&(s.push({type:i.type,name:i.fields.map(a=>a.name||"<anonymous>").join(", "),offset:i.offset,size:i.size,isPadding:!1,isBitFieldContainer:!0,bitFields:i.fields}),r=i.offset+i.size,i=null)}for(let a of n)if(e){let c=0,u=1,d=[];if(a.isBlock){let b=ue(a.members??[],t,a.type==="union");c=b.totalSize*(a.arrayCount??1),u=b.alignment,d=b.layout}else if(a.isBitField){let b=q(a.type,a.isPointer);c=b.size,u=b.align}else{let b=q(a.type,a.isPointer);c=b.size*(a.isArray?a.arrayCount??1:1),u=b.align}s.push({type:a.type,name:a.name+(a.isArray?`[${a.arrayCount}]`:"")+(a.isBitField?` : ${a.bitWidth}`:""),offset:t,size:c,isPadding:!1,isBlock:a.isBlock,nestedLayout:d,isBitField:a.isBitField,bitWidth:a.isBitField?a.bitWidth:null}),u>o&&(o=u)}else if(a.isBitField){let c=q(a.type,a.isPointer),u=c.size,d=c.align,b=u*8,_=a.bitWidth??0;if(i&&i.size===u&&_>0&&i.remainingBits>=_)i.fields.push({name:a.name,bitOffset:b-i.remainingBits,bitWidth:_}),i.remainingBits-=_;else{l();let h=(d-r%d)%d;h>0&&(s.push({type:"padding",name:"padding",offset:r,size:h,isPadding:!0}),r+=h),_>0&&(i={type:a.type,size:u,align:d,offset:r,remainingBits:b-_,fields:[{name:a.name,bitOffset:0,bitWidth:_}]}),d>o&&(o=d)}}else{l();let c=0,u=1,d=[];if(a.isBlock){let _=ue(a.members??[],0,a.type==="union");c=_.totalSize*(a.arrayCount??1),u=_.alignment,d=_.layout}else{let _=q(a.type,a.isPointer);c=_.size*(a.isArray?a.arrayCount??1:1),u=_.align}let b=(u-r%u)%u;b>0&&(s.push({type:"padding",name:"padding",offset:r,size:b,isPadding:!0}),r+=b),s.push({type:a.type,name:a.name+(a.isArray?`[${a.arrayCount}]`:""),offset:r,size:c,isPadding:!1,isBlock:a.isBlock,nestedLayout:d}),r+=c,u>o&&(o=u)}if(e){let a=0;s.forEach(u=>{u.size>a&&(a=u.size)});let c=Math.ceil(a/o)*o;r=t+c}else{l();let a=(o-r%o)%o;a>0&&(s.push({type:"padding",name:"tail_padding",offset:r,size:a,isPadding:!0}),r+=a)}return{layout:s,totalSize:r-t,alignment:o}}function pt(n){return ue(wn(vn(n)))}function me(n){let t=0;for(let e of n)e.isPadding?t+=e.size:e.nestedLayout&&e.nestedLayout.length>0&&(t+=me(e.nestedLayout));return t}function dt(n){n.registerTool("embedcalc_qformat_convert",{title:"Q-Format Fixed-Point Converter",description:`Convert between float and signed Qm.n fixed point (1 sign + m integer + n fractional bits; e.g. Q0.15 = 16-bit "Q15").
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
- m (int >= 0), n (int >= 0): format; m+n+1 <= 32.
|
|
114
|
+
- value (float) OR raw_int (stored integer): provide exactly one.
|
|
115
|
+
|
|
116
|
+
Returns (structured): { format, total_bits, scale, range_min, range_max, raw_int, hex, bin, actual, error, clamped }. hex/bin are two's complement in the format's bit width.
|
|
117
|
+
Examples: 0.5 in Q0.15 -> 16384 = 0x4000; raw 0x8000 (-32768) in Q0.15 -> -1.0. Do not round by hand \u2014 quantization error matters.`,inputSchema:{m:g.number().int().min(0).max(31).describe("Integer bits (excluding sign)"),n:g.number().int().min(0).max(31).describe("Fractional bits"),value:g.number().optional().describe("Float value to encode (provide this OR raw_int)"),raw_int:g.number().int().optional().describe("Stored fixed-point integer to decode (provide this OR value)")},outputSchema:{format:g.string(),total_bits:g.number(),scale:g.number().describe("2^n"),range_min:g.number(),range_max:g.number(),raw_int:g.number(),hex:g.string(),bin:g.string(),actual:g.number().describe("Value actually represented after quantization"),error:g.number().describe("|input - actual| (0 when decoding raw_int)"),clamped:g.boolean()},annotations:x},async e=>{if(e.value===void 0==(e.raw_int===void 0))return v("Error: provide exactly one of 'value' (float to encode) or 'raw_int' (integer to decode).");try{let r=e.value!==void 0?le(e.value,e.m,e.n):at(e.raw_int,e.m,e.n),o={format:r.format,total_bits:r.totalBits,scale:r.scale,range_min:r.rangeMin,range_max:r.rangeMax,raw_int:r.rawInt,hex:r.hex,bin:r.bin,actual:r.actual,error:r.error,clamped:r.clamped};return y(`${r.format}: raw ${r.rawInt} = ${r.hex}, actual value ${r.actual}`+(e.value!==void 0?`, quantization error ${r.error}${r.clamped?" (CLAMPED to range)":""}`:"")+`
|
|
118
|
+
Range [${r.rangeMin}, ${r.rangeMax}], scale 2^${e.n} = ${r.scale}`,o)}catch(r){return v(`Error: ${r instanceof Error?r.message:String(r)}`)}}),n.registerTool("embedcalc_ieee754_convert",{title:"IEEE 754 Float Converter",description:`Encode/decode IEEE 754 single (32-bit) or double (64-bit) precision: decimal <-> hex <-> binary, with sign/exponent/fraction breakdown and classification.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
- format (32|64, default 32).
|
|
122
|
+
- Provide exactly one of: decimal (number), hex (string like '0x3F800000'), bin (bit string).
|
|
123
|
+
|
|
124
|
+
Returns (structured): { sign, exponent_raw, exponent_unbiased, fraction, hex, bin, decimal (string \u2014 may be 'NaN'/'Infinity'), class ('normal'|'subnormal'|'zero'|'infinity'|'nan') }.
|
|
125
|
+
Examples: 1.0 float -> 0x3F800000; 0.1 double -> 0x3FB999999999999A (not exact!). Do not eyeball float bit patterns \u2014 call this tool.`,inputSchema:{format:g.union([g.literal(32),g.literal(64)]).default(32).describe("32 = float, 64 = double"),decimal:g.number().optional().describe("Decimal value to encode (one of decimal/hex/bin)"),hex:g.string().optional().describe("Hex bit pattern to decode, e.g. '0x3F800000'"),bin:g.string().optional().describe("Binary bit pattern to decode (32 or 64 chars, spaces allowed)")},outputSchema:{sign:g.number(),exponent_raw:g.number(),exponent_unbiased:g.number().nullable().describe("exponent - bias; null for zero/subnormal/special"),fraction:g.number(),hex:g.string(),bin:g.string(),decimal:g.string().describe("Decimal value as string (may be 'NaN', 'Infinity', '-Infinity')"),class:g.enum(["normal","subnormal","zero","infinity","nan"])},annotations:x},async e=>{if([e.decimal!==void 0,e.hex!==void 0,e.bin!==void 0].filter(Boolean).length!==1)return v("Error: provide exactly one of 'decimal', 'hex', or 'bin'.");let o=e.format===64,s;e.decimal!==void 0?s=ct(e.decimal,o):e.hex!==void 0?s=lt(e.hex,o):s=ut(e.bin,o);let i=mt(s),l=o?1023:127,a={sign:s.sign,exponent_raw:s.exponent,exponent_unbiased:i==="normal"?s.exponent-l:null,fraction:s.fraction,hex:"0x"+s.hexString,bin:s.binString,decimal:String(s.decimal),class:i};return y(`IEEE 754 ${o?"double":"float"}: ${a.decimal} = ${a.hex} [${i}]
|
|
126
|
+
sign=${s.sign}, exponent=${s.exponent}${i==="normal"?` (unbiased ${s.exponent-l})`:""}, fraction=${s.fraction}`,a)}),n.registerTool("embedcalc_number_base_convert",{title:"Number Base Converter (32-bit)",description:`Convert a number between decimal/hex/binary/octal with 32-bit two's complement semantics (negative decimals wrap; MSB-set values get a signed reading).
|
|
127
|
+
|
|
128
|
+
Args: value (string) \u2014 decimal ('-1', '42'), hex ('0xFF'), binary ('0b1010'), or octal ('0o17').
|
|
129
|
+
Returns (structured): { unsigned, signed, hex, bin (4-bit groups), oct, ones_count }.
|
|
130
|
+
Example: '-1' -> 0xFFFFFFFF, unsigned 4294967295, 32 ones.`,inputSchema:{value:g.string().min(1).max(64).describe("Number in any base: decimal, 0x hex, 0b binary, 0o octal")},outputSchema:{unsigned:g.string(),signed:g.string(),hex:g.string(),bin:g.string(),oct:g.string(),ones_count:g.number()},annotations:x},async e=>{try{let r=De(e.value);return y(`${e.value} (32-bit): unsigned ${r.unsigned}, signed ${r.signed}
|
|
131
|
+
hex ${r.hex}, oct ${r.oct}, ${r.onesCount} bits set
|
|
132
|
+
bin ${r.bin}`,{unsigned:r.unsigned,signed:r.signed,hex:r.hex,bin:r.bin,oct:r.oct,ones_count:r.onesCount})}catch(r){return v(`Error: ${r instanceof Error?r.message:String(r)}`)}}),n.registerTool("embedcalc_endian_swap",{title:"Endianness / Byte Swap Converter",description:`Show a hex byte sequence in all endianness orderings: big (ABCD), little (DCBA), mid-little/byte-swapped halfwords (BADC), mid-big/halfword-swapped (CDAB).
|
|
133
|
+
|
|
134
|
+
Args: hex (string) \u2014 e.g. '0x12345678' or 'DEADBEEF'; odd nibble counts get a leading zero.
|
|
135
|
+
Returns (structured): { big, little, mid_little_badc, mid_big_cdab (null when < 4 bytes), bytes }.
|
|
136
|
+
Example: 12345678 -> little 78563412, BADC 34127856, CDAB 56781234. Useful for Modbus register order and mixed-endian protocol debugging.`,inputSchema:{hex:g.string().min(1).max(64).describe("Hex byte sequence (with or without 0x)")},outputSchema:{big:g.string(),little:g.string(),mid_little_badc:g.string().nullable(),mid_big_cdab:g.string().nullable(),bytes:g.array(g.number()).describe("Original bytes, big-endian order")},annotations:x},async e=>{try{let r=ze(e.hex);return y(`Big (ABCD): 0x${r.big}
|
|
137
|
+
Little (DCBA): 0x${r.little}
|
|
138
|
+
Mid-little (BADC): ${r.midLittleBADC?"0x"+r.midLittleBADC:"N/A (needs >= 4 bytes)"}
|
|
139
|
+
Mid-big (CDAB): ${r.midBigCDAB?"0x"+r.midBigCDAB:"N/A (needs >= 4 bytes)"}`,{big:r.big,little:r.little,mid_little_badc:r.midLittleBADC,mid_big_cdab:r.midBigCDAB,bytes:r.bytes})}catch(r){return v(`Error: ${r instanceof Error?r.message:String(r)} \u2014 supply at least one full hex byte.`)}});function t(e,r=0){let o=[];for(let s of e)if(s.isPadding)o.push({offset:s.offset,size:s.size,type:"padding",name:s.name,kind:"padding",depth:r});else if(s.isBitFieldContainer&&s.bitFields){o.push({offset:s.offset,size:s.size,type:s.type+" (bit-field container)",name:s.name,kind:"bitfield-container",depth:r});for(let i of s.bitFields)o.push({offset:s.offset,size:0,type:`bits ${i.bitOffset}..${i.bitOffset+i.bitWidth-1}`,name:i.name||"<anonymous>",kind:"bitfield",depth:r+1})}else s.nestedLayout&&s.nestedLayout.length>0?(o.push({offset:s.offset,size:s.size,type:s.type,name:s.name,kind:"block",depth:r}),o.push(...t(s.nestedLayout,r+1))):o.push({offset:s.offset,size:s.size,type:s.type,name:s.name,kind:"field",depth:r});return o}n.registerTool("embedcalc_struct_layout",{title:"C Struct Memory Layout Calculator",description:`Compute the memory layout of a C struct/union \u2014 member offsets, inserted padding, bit-field packing, total size, and alignment \u2014 for a 32-bit MCU model (ARM Cortex-M style: pointers 4 bytes; double/int64 size 8 with 4-byte alignment, matching embedcalc.com).
|
|
140
|
+
|
|
141
|
+
Args: code (string) \u2014 C member declarations or a full struct, e.g. 'char a; int b;' or 'struct s { uint8_t a; uint32_t b; };'. Supports nested struct/union, arrays, pointers, and bit-fields.
|
|
142
|
+
|
|
143
|
+
Returns (structured): { total_size, alignment, padding_bytes, rows: [{offset, size, type, name, kind, depth}] }. Nested block rows show offsets relative to their parent block.
|
|
144
|
+
Example: 'char a; int b;' -> total 8 bytes, 3 bytes padding after 'a'. Do not guess struct sizes/offsets \u2014 padding rules are exactly what LLMs get wrong.`,inputSchema:{code:g.string().min(1).max(1e4).describe("C struct member declarations (or full struct/union definition)")},outputSchema:{total_size:g.number(),alignment:g.number(),padding_bytes:g.number(),rows:g.array(g.object({offset:g.number().describe("Byte offset (relative to parent block for nested rows)"),size:g.number(),type:g.string(),name:g.string(),kind:g.enum(["field","padding","bitfield-container","bitfield","block"]),depth:g.number()}))},annotations:x},async e=>{try{let r=pt(e.code);if(r.totalSize===0&&r.layout.length===0)return v("Error: no members parsed. Provide C declarations like 'uint8_t a; uint32_t b;'.");let o=me(r.layout),s=t(r.layout),i=[`Total size: ${r.totalSize} bytes, alignment: ${r.alignment}, padding: ${o} bytes (${r.totalSize>0?Math.round(o/r.totalSize*100):0}%)`,"","| offset | size | type | name |","|---|---|---|---|"];for(let l of s){let a=" ".repeat(l.depth);i.push(`| ${l.offset} | ${l.size||""} | ${l.type} | ${a}${l.name}${l.kind==="padding"?" (PAD)":""} |`)}return i.push("","(32-bit MCU model: pointer=4B; double/int64 align 4)"),y(i.join(`
|
|
145
|
+
`),{total_size:r.totalSize,alignment:r.alignment,padding_bytes:o,rows:s})}catch(r){return v(`Error: ${r instanceof Error?r.message:String(r)}`)}})}import{z as m}from"zod";var Sn={E6:[1,1.5,2.2,3.3,4.7,6.8],E12:[1,1.2,1.5,1.8,2.2,2.7,3.3,3.9,4.7,5.6,6.8,8.2],E24:[1,1.1,1.2,1.3,1.5,1.6,1.8,2,2.2,2.4,2.7,3,3.3,3.6,3.9,4.3,4.7,5.1,5.6,6.2,6.8,7.5,8.2,9.1],E96:[1,1.02,1.05,1.07,1.1,1.13,1.15,1.18,1.21,1.24,1.27,1.3,1.33,1.37,1.4,1.43,1.47,1.5,1.54,1.58,1.62,1.65,1.69,1.74,1.78,1.82,1.87,1.91,1.96,2,2.05,2.1,2.15,2.21,2.26,2.32,2.37,2.43,2.49,2.55,2.61,2.67,2.74,2.8,2.87,2.94,3.01,3.09,3.16,3.24,3.32,3.4,3.48,3.57,3.65,3.74,3.83,3.92,4.02,4.12,4.22,4.32,4.42,4.53,4.64,4.75,4.87,4.99,5.11,5.23,5.36,5.49,5.62,5.76,5.9,6.04,6.19,6.34,6.49,6.65,6.81,6.98,7.15,7.32,7.5,7.68,7.87,8.06,8.25,8.45,8.66,8.87,9.09,9.31,9.53,9.76]};function gt(n,t){if(typeof n!="number"||!isFinite(n)||n<=0)return null;let e=Sn[t];if(!e)return null;let r=Math.floor(Math.log10(n)),o=null,s=1/0;for(let i=r;i<=r+1;i++){let l=Math.pow(10,i);for(let a=0;a<e.length;a++){let c=e[a]*l,u=Math.abs(c-n);u<s&&(s=u,o=c)}}return{standard:o,errorPct:(o-n)/n*100,series:t}}var ft={black:{val:0,mult:1,tol:null},brown:{val:1,mult:10,tol:1},red:{val:2,mult:100,tol:2},orange:{val:3,mult:1e3,tol:null},yellow:{val:4,mult:1e4,tol:null},green:{val:5,mult:1e5,tol:.5},blue:{val:6,mult:1e6,tol:.25},violet:{val:7,mult:1e7,tol:.1},grey:{val:8,mult:1e8,tol:.05},gray:{val:8,mult:1e8,tol:.05},white:{val:9,mult:1e9,tol:null},gold:{val:null,mult:.1,tol:5},silver:{val:null,mult:.01,tol:10},none:{val:null,mult:null,tol:20}};function _t(n){let t=n.map(o=>{let s=ft[o.trim().toLowerCase()];if(!s)throw new Error(`Unknown color '${o}'. Valid: ${Object.keys(ft).join(", ")}`);return s}),e,r;if(n.length===4){if(t[0].val===null||t[1].val===null||t[2].mult===null)throw new Error("Invalid band colors for a 4-band resistor (digit, digit, multiplier, tolerance).");e=(t[0].val*10+t[1].val)*t[2].mult,r=t[3].tol}else if(n.length===5){if(t[0].val===null||t[1].val===null||t[2].val===null||t[3].mult===null)throw new Error("Invalid band colors for a 5-band resistor (digit, digit, digit, multiplier, tolerance).");e=(t[0].val*100+t[1].val*10+t[2].val)*t[3].mult,r=t[4].tol}else throw new Error("Provide 4 or 5 color bands.");return{ohms:e,tolerancePct:r,minOhms:r!==null?e*(1-r/100):null,maxOhms:r!==null?e*(1+r/100):null}}var bt={"01":100,"02":102,"03":105,"04":107,"05":110,"06":113,"07":115,"08":118,"09":121,10:124,11:127,12:130,13:133,14:137,15:140,16:143,17:147,18:150,19:154,20:158,21:162,22:165,23:169,24:174,25:178,26:182,27:187,28:191,29:196,30:200,31:205,32:210,33:215,34:221,35:226,36:232,37:237,38:243,39:249,40:255,41:261,42:267,43:274,44:280,45:287,46:294,47:301,48:309,49:316,50:324,51:332,52:340,53:348,54:357,55:365,56:374,57:383,58:392,59:402,60:412,61:422,62:432,63:442,64:453,65:464,66:475,67:487,68:499,69:511,70:523,71:536,72:549,73:562,74:576,75:590,76:604,77:619,78:634,79:649,80:665,81:681,82:698,83:715,84:732,85:750,86:768,87:787,88:806,89:825,90:845,91:866,92:887,93:909,94:931,95:953,96:976},ht={Z:.001,Y:.01,R:.01,X:.1,S:.1,A:1,B:10,H:10,C:100,D:1e3,E:1e4,F:1e5};function yt(n){let t=n.trim().toUpperCase(),e=t.match(/^([0-9]{2})([A-Z])$/);if(e&&bt[e[1]]!==void 0&&ht[e[2]]!==void 0){let r=bt[e[1]]*ht[e[2]];return{ohms:r,tolerancePct:1,minOhms:r*.99,maxOhms:r*1.01}}if(t.includes("R")&&/^[0-9]*R[0-9]*$/.test(t)){let r=parseFloat(t.replace("R","."));if(isFinite(r))return{ohms:r,tolerancePct:null,minOhms:null,maxOhms:null}}if(/^[0-9]{3}$/.test(t))return{ohms:parseInt(t.substring(0,2),10)*Math.pow(10,parseInt(t[2],10)),tolerancePct:null,minOhms:null,maxOhms:null};if(/^[0-9]{4}$/.test(t))return{ohms:parseInt(t.substring(0,3),10)*Math.pow(10,parseInt(t[3],10)),tolerancePct:null,minOhms:null,maxOhms:null};throw new Error(`Cannot parse SMD code '${n}' \u2014 expected 3/4-digit (472), R-notation (4R7), or EIA-96 (01C).`)}function xt(n,t){return 2*(n-t)}function vt(n,t,e,r){return n*(1+e/t)+r*e}function wt(n,t,e,r){return(n-t)/(t/e+r)}function St(n,t){let e=Math.pow(10,n/20),r=e*e;return{tR1:t*(e-1)/(e+1),tR2:t*(2*e)/(r-1),piR1:t*(e+1)/(e-1),piR2:t*(r-1)/(2*e)}}function Ct(n,t,e){return n+t*e}function Rt(n,t,e){return(n-t)/e}function Ft(n,t,e,r){if(n<=0||t<=0||e<=0||r<=0)return 0;let o=5.98*t/(.8*n+e);return o<=0?0:Math.max(0,87/Math.sqrt(r+1.41)*Math.log(o))}function Et(n,t,e,r){if(n<=0||t<=0||e<=0)return null;let o=r?.048:.024,s=e*1.378,i=Math.pow(n/(o*Math.pow(t,.44)),1/.725),l=i/s;return{widthMil:l,widthMm:l*.0254,areaSqMil:i}}function At(n,t,e,r,o){let s=.127*Math.pow(92,(36-n)/39),i=Math.PI/4*Math.pow(s,2),u=17241e-12*(1+.00393*(o-20))*(r*2)/(i*1e-6),d=e*u,b=d/t*100,_=b<3?"PASS":b<=5?"WARNING":"FAIL";return{diameterMm:s,areaMm2:i,resistanceOhm:u,voltageDropV:d,dropPercent:b,verdict:_}}function Mt(n,t,e){let r=.693*(n+t)*e,o=.693*t*e,s=r+o;return{tHigh:r,tLow:o,tTotal:s,freq:1/s,duty:r/s*100}}function Tt(n,t){return 1.1*n*t}function $t(n,t,e,r,o,s,i){let l=i*.001,a=r*(s/100),c=0,u=0,d=0,b=0,_=0;return n==="buck"?(c=e/t,c>.99&&(c=.99),u=(t-e)*c/(o*a),d=a/(8*o*l),b=r*c,_=r+a/2):n==="boost"?(c=1-t/e,c<0&&(c=0),c>.95&&(c=.95),u=t*c/(o*a),d=r*c/(o*l),b=r/(1-c),_=b+a/2):(c=e/(t+e),c>.95&&(c=.95),u=t*c/(o*a),d=r*c/(o*l),b=r*(c/(1-c)),_=b+a/2),{duty:c,lMinH:u,cMinF:d,iinA:b,iPeakA:_}}function Nt(n,t,e){let r=e.rfOhm!==void 0,o=e.targetGain!==void 0;if(r===o)throw new Error("Provide exactly one of rfOhm or targetGain.");let s;if(o){let u=e.targetGain;if(n==="noninverting"){if(u<1)throw new Error("Non-inverting gain must be >= 1.");s=(u-1)*t}else s=u*t}else s=e.rfOhm;let i=1+s/t,l=n==="noninverting"?1+s/t:-(s/t),a=20*Math.log10(Math.abs(l)),c=e.gbwHz!==void 0?e.gbwHz/i:null;return{gainVV:l,gainDb:a,noiseGain:i,rfOhm:s,bandwidthHz:c}}function Pt(n,t){if([t.rOhm,t.cFarad,t.fcHz].filter(l=>l!==void 0).length!==2)throw new Error("Provide exactly two of rOhm, cFarad, fcHz.");let r=2*Math.PI,{rOhm:o,cFarad:s,fcHz:i}=t;return i===void 0?i=1/(r*o*s):o===void 0?o=1/(r*i*s):s=1/(r*i*o),{fcHz:i,rOhm:o,cFarad:s,tauS:o*s}}var Cn=.2;function kt(n,t,e,r,o,s,i){let l=n*e/(t+e),a=t*e/(t+e),c=(l-i)/(a+(s+1)*o);if(c<=0){let F=n;return{ibA:0,icA:0,ieA:0,vbV:l,veV:0,vcV:F,vceV:F,region:"cutoff"}}let u=s*c,d=(s+1)*c,b=l-c*a,_=d*o,h=n-u*r,C=h-_,E=C<=Cn?"saturation":"active";return{ibA:c,icA:u,ieA:d,vbV:b,veV:_,vcV:h,vceV:C,region:E}}function It(n,t,e,r){let o=n*n*t*e,s=r.qgC!==void 0&&r.fswHz!==void 0,i=s?r.qgC*r.fswHz:null,l=s&&r.vdriveV!==void 0?r.qgC*r.vdriveV*r.fswHz:null,c=r.thetaJa!==void 0&&r.ambientC!==void 0?r.ambientC+o*r.thetaJa:null,u=r.tjMaxC!==void 0?r.tjMaxC:150,d=c!==null?u-c:null,b=d!==null?d>=0:null;return{pConductionW:o,gateDriveAvgA:i,gateDrivePowerW:l,tjC:c,marginC:d,ok:b}}function Dt(n,t,e,r){if(n<=t)throw new Error("vinV must be greater than vzV.");let o=r.rsOhm!==void 0,s=r.izMinA!==void 0;if(o===s)throw new Error("Provide exactly one of rsOhm or izMinA.");let i;s?i=(n-t)/(r.izMinA+e):i=r.rsOhm;let l=(n-t)/i,a=l-e;if(a<=0)throw new Error("Load current too high: Zener current would be <= 0 (regulation lost).");let c=t*a,u=(n-t)*l,d=r.pzMaxW!==void 0?c<=r.pzMaxW:null;return{rsOhm:i,isA:l,izA:a,pzW:c,prsW:u,zenerOk:d}}function zt(n){n.registerTool("embedcalc_eseries_nearest",{title:"E-Series Nearest Standard Value",description:`Snap a computed resistor/capacitor/inductor value to the nearest purchasable E-series standard value (E6/E12/E24/E96) with the resulting error.
|
|
146
|
+
|
|
147
|
+
Args: value (any unit \u2014 ohms, farads, henries), series ('E24' default; E96 for 1% resistors).
|
|
148
|
+
Returns (structured): { standard, error_percent, series }.
|
|
149
|
+
Example: 4.67e-6 H, E24 -> 4.7e-6 (+0.64%). LLMs frequently pick non-existent "standard" values \u2014 always snap computed values with this tool before choosing a part.
|
|
150
|
+
|
|
151
|
+
To source a real, in-stock part for the standard value, follow up with embedcalc_find_component (type=resistor for ohms / capacitor for farads / inductor for henries; value=standard) \u2014 it returns manufacturer part numbers + buy links.`,inputSchema:{value:m.number().positive().describe("Computed value in base units (ohms/farads/henries)"),series:m.enum(["E6","E12","E24","E96"]).default("E24").describe("E-series (E24=5%, E96=1% resistors)")},outputSchema:{standard:m.number(),error_percent:m.number(),series:m.string()},annotations:x},async t=>{let e=gt(t.value,t.series);return e?y(`Nearest ${e.series} standard: ${e.standard} (error ${p(e.errorPct,3)}%)`,{standard:e.standard,error_percent:p(e.errorPct,4),series:e.series}):v("Error: value must be a positive finite number.")}),n.registerTool("embedcalc_resistor_code",{title:"Resistor Color Code / SMD Code Decoder",description:`Decode a through-hole resistor color code (4 or 5 bands) or an SMD resistor marking into ohms + tolerance.
|
|
152
|
+
|
|
153
|
+
Args (one of):
|
|
154
|
+
- bands: array of 4 or 5 color names in order (black, brown, red, orange, yellow, green, blue, violet, grey, white, gold, silver, none).
|
|
155
|
+
- smd_code: 3-digit ('472'=4.7k), 4-digit ('4702'=47k), R-notation ('4R7'=4.7\u03A9), or EIA-96 ('01C'=10k \xB11%).
|
|
156
|
+
|
|
157
|
+
Returns (structured): { ohms, tolerance_percent, min_ohms, max_ohms }.
|
|
158
|
+
Example: ['brown','black','red','gold'] -> 1000 \u03A9 \xB15%.
|
|
159
|
+
|
|
160
|
+
To find a real resistor to buy for the decoded value, follow up with embedcalc_find_component (type=resistor, value=ohms, tolerance_pct=tolerance_percent) \u2014 returns MPN + buy link.`,inputSchema:{bands:m.array(m.string()).min(4).max(5).optional().describe("Color band names in order (4 or 5 bands)"),smd_code:m.string().min(2).max(6).optional().describe("SMD marking, e.g. '472', '4R7', '01C'")},outputSchema:{ohms:m.number(),tolerance_percent:m.number().nullable(),min_ohms:m.number().nullable(),max_ohms:m.number().nullable()},annotations:x},async t=>{if(t.bands===void 0==(t.smd_code===void 0))return v("Error: provide exactly one of 'bands' (color array) or 'smd_code'.");try{let e=t.bands?_t(t.bands):yt(t.smd_code),r=e.tolerancePct!==null?` \xB1${e.tolerancePct}% (${p(e.minOhms,3)}\u2026${p(e.maxOhms,3)} \u03A9)`:"";return y(`${e.ohms} \u03A9${r}`,{ohms:e.ohms,tolerance_percent:e.tolerancePct,min_ohms:e.minOhms,max_ohms:e.maxOhms})}catch(e){return v(`Error: ${e instanceof Error?e.message:String(e)}`)}}),n.registerTool("embedcalc_crystal_load_caps",{title:"Crystal Load Capacitor Calculator",description:`Compute the two external load capacitors for a crystal from its specified load capacitance: CL = C/2 + Cstray -> C = 2\xB7(CL \u2212 Cstray).
|
|
161
|
+
|
|
162
|
+
Args: load_capacitance_pf (from crystal datasheet, e.g. 18), stray_pf (PCB+pin stray, typ. 3-5).
|
|
163
|
+
Returns (structured): { cap_each_pf }.
|
|
164
|
+
Example: CL=18pF, stray=5pF -> 26 pF each side. Wrong load caps = frequency pulling; don't guess.
|
|
165
|
+
|
|
166
|
+
To source the load capacitor, follow up with embedcalc_find_component (type=capacitor, value=cap_each_pf \xD7 1e-12 farads) \u2014 returns MPN + buy link.`,inputSchema:{load_capacitance_pf:m.number().positive().describe("Crystal's specified CL in pF"),stray_pf:m.number().min(0).default(5).describe("Stray capacitance (PCB traces + MCU pins) in pF, typ. 3-5")},outputSchema:{cap_each_pf:m.number()},annotations:x},async t=>{let e=xt(t.load_capacitance_pf,t.stray_pf);return e<=0?v("Error: stray capacitance >= CL \u2014 no external caps needed (or CL spec is wrong)."):y(`Each load capacitor: ${p(e,2)} pF (CL ${t.load_capacitance_pf} pF, stray ${t.stray_pf} pF)`,{cap_each_pf:p(e,3)})}),n.registerTool("embedcalc_lm317",{title:"LM317 Resistor Calculator",description:`LM317 adjustable regulator: Vout = Vref\xB7(1 + R2/R1) + Iadj\xB7R2. Compute R2 for a target Vout, or Vout from R1/R2.
|
|
167
|
+
|
|
168
|
+
Args: r1_ohm (default 240), and exactly one of target_vout (-> solves R2) or r2_ohm (-> computes Vout). vref default 1.25 V, iadj_ua default 50 \xB5A (datasheet typical).
|
|
169
|
+
Returns (structured): { vout, r1_ohm, r2_ohm }.
|
|
170
|
+
Example: target 5.0 V, R1=240, Iadj=0 -> R2=720 \u03A9. Snap R2 with embedcalc_eseries_nearest afterwards.`,inputSchema:{r1_ohm:m.number().positive().default(240).describe("R1 (Vref across it), datasheet recommends 240 \u03A9"),target_vout:m.number().positive().optional().describe("Desired output voltage (solves R2)"),r2_ohm:m.number().min(0).optional().describe("R2 value (computes Vout)"),vref:m.number().positive().default(1.25).describe("Reference voltage (1.25 V typ)"),iadj_ua:m.number().min(0).default(50).describe("Adjust pin current in \xB5A (50 typ)")},outputSchema:{vout:m.number(),r1_ohm:m.number(),r2_ohm:m.number()},annotations:x},async t=>{if(t.target_vout===void 0==(t.r2_ohm===void 0))return v("Error: provide exactly one of 'target_vout' (solve R2) or 'r2_ohm' (compute Vout).");let e=t.iadj_ua*1e-6,r;if(t.target_vout!==void 0){if(t.target_vout<t.vref)return v(`Error: target_vout must be >= Vref (${t.vref} V).`);r=wt(t.target_vout,t.vref,t.r1_ohm,e)}else r=t.r2_ohm;let o=vt(t.vref,t.r1_ohm,r,e);return y(`LM317: R1=${t.r1_ohm} \u03A9, R2=${p(r,1)} \u03A9 -> Vout=${p(o,3)} V (Iadj ${t.iadj_ua} \xB5A included)`,{vout:p(o,4),r1_ohm:t.r1_ohm,r2_ohm:p(r,2)})}),n.registerTool("embedcalc_attenuator_pad",{title:"Resistive Attenuator Pad (T / Pi)",description:`Design T-pad and Pi-pad resistive attenuators for a given attenuation and system impedance.
|
|
171
|
+
|
|
172
|
+
Args: attenuation_db, impedance_ohm (default 50).
|
|
173
|
+
Returns (structured): { t_series_ohm, t_shunt_ohm, pi_shunt_ohm, pi_series_ohm }.
|
|
174
|
+
Example: 3 dB @ 50 \u03A9 -> T: 8.55 \u03A9 series \xD72 + 141.9 \u03A9 shunt; Pi: 292.4 \u03A9 shunt \xD72 + 17.61 \u03A9 series. Snap values with embedcalc_eseries_nearest.`,inputSchema:{attenuation_db:m.number().positive().max(60).describe("Attenuation in dB"),impedance_ohm:m.number().positive().default(50).describe("System impedance (50/75 \u03A9)")},outputSchema:{t_series_ohm:m.number().describe("T-pad: each of the two series resistors"),t_shunt_ohm:m.number().describe("T-pad: center shunt resistor"),pi_shunt_ohm:m.number().describe("Pi-pad: each of the two shunt resistors"),pi_series_ohm:m.number().describe("Pi-pad: center series resistor")},annotations:x},async t=>{let e=St(t.attenuation_db,t.impedance_ohm);return y(`${t.attenuation_db} dB pad @ ${t.impedance_ohm} \u03A9:
|
|
175
|
+
T-pad: series ${p(e.tR1,2)} \u03A9 \xD72, shunt ${p(e.tR2,2)} \u03A9
|
|
176
|
+
Pi-pad: shunt ${p(e.piR1,2)} \u03A9 \xD72, series ${p(e.piR2,2)} \u03A9`,{t_series_ohm:p(e.tR1,3),t_shunt_ohm:p(e.tR2,3),pi_shunt_ohm:p(e.piR1,3),pi_series_ohm:p(e.piR2,3)})}),n.registerTool("embedcalc_junction_temp",{title:"Junction Temperature / Max Power",description:`Thermal check: Tj = Ta + P\xB7\u03B8JA, and max dissipable power for a Tj limit.
|
|
177
|
+
|
|
178
|
+
Args: ambient_c, power_w, theta_ja (\xB0C/W from datasheet), tj_max_c (default 150).
|
|
179
|
+
Returns (structured): { tj_c, p_max_w, margin_c, ok }.
|
|
180
|
+
Example: Ta=25, P=2W, \u03B8JA=50 -> Tj=125\xB0C, Pmax=2.5W @ Tj_max 150.`,inputSchema:{ambient_c:m.number().describe("Ambient temperature in \xB0C"),power_w:m.number().positive().describe("Dissipated power in W"),theta_ja:m.number().positive().describe("Junction-to-ambient thermal resistance \xB0C/W"),tj_max_c:m.number().positive().default(150).describe("Maximum junction temperature rating in \xB0C")},outputSchema:{tj_c:m.number(),p_max_w:m.number(),margin_c:m.number().describe("tj_max - tj (negative = over limit)"),ok:m.boolean()},annotations:x},async t=>{let e=Ct(t.ambient_c,t.power_w,t.theta_ja),r=Rt(t.tj_max_c,t.ambient_c,t.theta_ja),o=t.tj_max_c-e;return y(`Tj = ${p(e,1)} \xB0C (limit ${t.tj_max_c} \xB0C, margin ${p(o,1)} \xB0C \u2014 ${o>=0?"OK":"OVER LIMIT"})
|
|
181
|
+
P_max @ Ta ${t.ambient_c} \xB0C = ${p(r,3)} W`,{tj_c:p(e,2),p_max_w:p(r,3),margin_c:p(o,2),ok:o>=0})}),n.registerTool("embedcalc_microstrip_impedance",{title:"Microstrip Impedance (IPC-2141)",description:`Surface microstrip characteristic impedance: Z0 = (87/\u221A(\u03B5r+1.41))\xB7ln(5.98h/(0.8w+t)). Valid roughly for 0.1 < w/h < 2.0 and \u03B5r < 15.
|
|
182
|
+
|
|
183
|
+
Args (all in mm): trace_width_mm, dielectric_height_mm, trace_thickness_mm (1oz \u2248 0.035), er (FR-4 \u2248 4.2-4.6).
|
|
184
|
+
Returns (structured): { z0_ohm }.
|
|
185
|
+
Example: w=3.0, h=1.6, t=0.035, \u03B5r=4.5 -> \u224849 \u03A9.`,inputSchema:{trace_width_mm:m.number().positive().describe("Trace width w in mm"),dielectric_height_mm:m.number().positive().describe("Dielectric height h in mm"),trace_thickness_mm:m.number().positive().default(.035).describe("Copper thickness t in mm (1 oz = 0.035)"),er:m.number().positive().default(4.5).describe("Dielectric constant (FR-4 \u2248 4.5)")},outputSchema:{z0_ohm:m.number()},annotations:x},async t=>{let e=Ft(t.trace_width_mm,t.dielectric_height_mm,t.trace_thickness_mm,t.er);return e<=0?v("Error: inputs outside the IPC-2141 model's validity (check w/h ratio)."):y(`Microstrip Z0 \u2248 ${p(e,2)} \u03A9 (w=${t.trace_width_mm}, h=${t.dielectric_height_mm}, t=${t.trace_thickness_mm} mm, \u03B5r=${t.er})`,{z0_ohm:p(e,3)})}),n.registerTool("embedcalc_pcb_trace_width",{title:"PCB Trace Width for Current (IPC-2221)",description:`Minimum trace width to carry a current with a given temperature rise, per IPC-2221 (I = k\xB7\u0394T^0.44\xB7A^0.725; k=0.048 external / 0.024 internal).
|
|
186
|
+
|
|
187
|
+
Args: current_a, temp_rise_c (default 10), copper_oz (default 1), layer ('external'|'internal').
|
|
188
|
+
Returns (structured): { width_mil, width_mm, cross_section_sq_mil }.
|
|
189
|
+
Example: 1 A, 10 \xB0C rise, 1 oz external -> \u224811.8 mil (0.30 mm). Internal layers need ~2x the width. Do not guess trace widths \u2014 this curve is not linear.`,inputSchema:{current_a:m.number().positive().describe("Continuous current in amps"),temp_rise_c:m.number().positive().default(10).describe("Allowed temperature rise in \xB0C (typ. 10)"),copper_oz:m.number().positive().default(1).describe("Copper weight in oz (1 oz = 35 \xB5m)"),layer:m.enum(["external","internal"]).default("external").describe("Trace layer")},outputSchema:{width_mil:m.number(),width_mm:m.number(),cross_section_sq_mil:m.number()},annotations:x},async t=>{let e=Et(t.current_a,t.temp_rise_c,t.copper_oz,t.layer==="external");return e?y(`Min trace width: ${p(e.widthMil,2)} mil = ${p(e.widthMm,3)} mm (${t.current_a} A, \u0394T ${t.temp_rise_c} \xB0C, ${t.copper_oz} oz ${t.layer})`,{width_mil:p(e.widthMil,3),width_mm:p(e.widthMm,4),cross_section_sq_mil:p(e.areaSqMil,3)}):v("Error: current, temp rise, and copper weight must be positive.")}),n.registerTool("embedcalc_awg_wire",{title:"AWG Wire Gauge & Voltage Drop",description:`AWG wire properties (ASTM B258) and round-trip voltage drop for a copper pair: diameter, area, loop resistance (2\xD7 length, go & return), drop %, and a PASS/WARNING/FAIL verdict (<3% / 3-5% / >5%).
|
|
190
|
+
|
|
191
|
+
Args: awg (e.g. 20), supply_v, current_a, length_m (one-way), temp_c (default 20).
|
|
192
|
+
Returns (structured): { diameter_mm, area_mm2, loop_resistance_ohm, voltage_drop_v, drop_percent, verdict }.
|
|
193
|
+
Example: AWG20, 5 V, 2 A, 1 m -> 0.067 \u03A9 loop, 2.7% drop, PASS.`,inputSchema:{awg:m.number().min(0).max(40).describe("Wire gauge AWG number"),supply_v:m.number().positive().describe("Supply voltage in V"),current_a:m.number().positive().describe("Load current in A"),length_m:m.number().positive().describe("One-way cable length in m (loop = 2\xD7)"),temp_c:m.number().default(20).describe("Conductor temperature in \xB0C")},outputSchema:{diameter_mm:m.number(),area_mm2:m.number(),loop_resistance_ohm:m.number(),voltage_drop_v:m.number(),drop_percent:m.number(),verdict:m.enum(["PASS","WARNING","FAIL"])},annotations:x},async t=>{let e=At(t.awg,t.supply_v,t.current_a,t.length_m,t.temp_c);return y(`AWG${t.awg}: \xD8${p(e.diameterMm,3)} mm, loop R ${p(e.resistanceOhm,4)} \u03A9 \u2192 drop ${p(e.voltageDropV,3)} V (${p(e.dropPercent,2)}%) \u2014 ${e.verdict}`,{diameter_mm:p(e.diameterMm,4),area_mm2:p(e.areaMm2,4),loop_resistance_ohm:p(e.resistanceOhm,5),voltage_drop_v:p(e.voltageDropV,4),drop_percent:p(e.dropPercent,2),verdict:e.verdict})}),n.registerTool("embedcalc_ne555",{title:"NE555 Timer Calculator",description:`NE555 timing. Astable: f = 1/(0.693\xB7(Ra+2Rb)\xB7C), duty = (Ra+Rb)/(Ra+2Rb) (always >50%). Monostable: t = 1.1\xB7Ra\xB7C.
|
|
194
|
+
|
|
195
|
+
Args: mode ('astable'|'monostable'), ra_ohm, rb_ohm (astable only), c_farad (e.g. 10e-6).
|
|
196
|
+
Returns (structured): astable -> { freq_hz, duty_percent, t_high_s, t_low_s }; monostable -> { pulse_s }.
|
|
197
|
+
Example: astable 1k/10k/10\xB5F -> 6.87 Hz, 52.4%.`,inputSchema:{mode:m.enum(["astable","monostable"]).describe("Oscillator or one-shot"),ra_ohm:m.number().positive().describe("Ra in ohms"),rb_ohm:m.number().positive().optional().describe("Rb in ohms (astable only)"),c_farad:m.number().positive().describe("Timing capacitor in farads (10\xB5F = 10e-6)")},outputSchema:{freq_hz:m.number().nullable(),duty_percent:m.number().nullable(),t_high_s:m.number().nullable(),t_low_s:m.number().nullable(),pulse_s:m.number().nullable()},annotations:x},async t=>{if(t.mode==="astable"){if(t.rb_ohm===void 0)return v("Error: astable mode requires 'rb_ohm'.");let r=Mt(t.ra_ohm,t.rb_ohm,t.c_farad);return y(`NE555 astable: f = ${p(r.freq,4)} Hz, duty ${p(r.duty,2)}% (tHigh ${p(r.tHigh*1e3,3)} ms, tLow ${p(r.tLow*1e3,3)} ms)`,{freq_hz:p(r.freq,5),duty_percent:p(r.duty,3),t_high_s:r.tHigh,t_low_s:r.tLow,pulse_s:null})}let e=Tt(t.ra_ohm,t.c_farad);return y(`NE555 monostable: pulse = ${p(e*1e3,3)} ms`,{freq_hz:null,duty_percent:null,t_high_s:null,t_low_s:null,pulse_s:e})}),n.registerTool("embedcalc_dcdc_converter",{title:"Buck / Boost / Buck-Boost Converter Calculator",description:`DC-DC converter design (CCM): duty cycle, minimum inductance, minimum output capacitance, input current, and peak switch current for buck, boost, or inverting buck-boost.
|
|
198
|
+
|
|
199
|
+
Args: topology ('buck'|'boost'|'buckboost'), vin, vout, iout_a, freq_khz (default 100), ripple_current_pct (of Iout, default 30), ripple_voltage_mv (default 50).
|
|
200
|
+
Returns (structured): { duty_percent, l_min_uh, c_min_uf, iin_a, i_peak_a }.
|
|
201
|
+
Example: buck 12->5 V, 1 A, 100 kHz -> D=41.7%, L\u226597.2 \xB5H, C\u22657.5 \xB5F. Snap L/C with embedcalc_eseries_nearest.`,inputSchema:{topology:m.enum(["buck","boost","buckboost"]).describe("Converter topology (buckboost = inverting)"),vin:m.number().positive().describe("Input voltage V"),vout:m.number().positive().describe("Output voltage V (magnitude for inverting)"),iout_a:m.number().positive().describe("Output current A"),freq_khz:m.number().positive().default(100).describe("Switching frequency kHz"),ripple_current_pct:m.number().positive().max(100).default(30).describe("Inductor ripple current as % of Iout"),ripple_voltage_mv:m.number().positive().default(50).describe("Allowed output ripple voltage mV")},outputSchema:{duty_percent:m.number(),l_min_uh:m.number(),c_min_uf:m.number(),iin_a:m.number(),i_peak_a:m.number()},annotations:x},async t=>{if(t.topology==="buck"&&t.vout>=t.vin)return v("Error: buck requires vout < vin (use boost or buckboost).");if(t.topology==="boost"&&t.vout<=t.vin)return v("Error: boost requires vout > vin (use buck or buckboost).");let e=$t(t.topology,t.vin,t.vout,t.iout_a,t.freq_khz*1e3,t.ripple_current_pct,t.ripple_voltage_mv),r={duty_percent:p(e.duty*100,2),l_min_uh:p(e.lMinH*1e6,3),c_min_uf:p(e.cMinF*1e6,3),iin_a:p(e.iinA,4),i_peak_a:p(e.iPeakA,4)};return y(`${t.topology} ${t.vin}V\u2192${t.vout}V @ ${t.iout_a}A, ${t.freq_khz}kHz:
|
|
202
|
+
D = ${r.duty_percent}%, L \u2265 ${r.l_min_uh} \xB5H, Cout \u2265 ${r.c_min_uf} \xB5F, Iin ${r.iin_a} A, Ipeak ${r.i_peak_a} A`,r)}),n.registerTool("embedcalc_op_amp",{title:"Op-Amp Gain & Bandwidth",description:`Ideal op-amp closed-loop gain and resulting -3 dB bandwidth for inverting or non-inverting configs.
|
|
203
|
+
|
|
204
|
+
Inverting: Av = -Rf/Rin. Non-inverting: Av = 1 + Rf/Rin. Noise gain = 1 + Rf/Rin (both). Bandwidth = GBW / noise_gain.
|
|
205
|
+
Args: config ('inverting'|'noninverting'), rin_ohm, exactly one of rf_ohm or target_gain (magnitude), gbw_hz (optional -> bandwidth).
|
|
206
|
+
Returns (structured): { gain_vv (signed), gain_db, noise_gain, rf_ohm, bandwidth_hz (null if no gbw) }.
|
|
207
|
+
Example: non-inverting Rf=90k, Rin=10k -> +10 V/V, 20 dB; GBW 1 MHz -> 100 kHz. Snap Rf with embedcalc_eseries_nearest.`,inputSchema:{config:m.enum(["inverting","noninverting"]).describe("Amplifier configuration"),rin_ohm:m.number().positive().describe("Input resistor (inverting) or ground resistor Rg (non-inverting)"),rf_ohm:m.number().positive().optional().describe("Feedback resistor (compute gain)"),target_gain:m.number().positive().optional().describe("Desired gain magnitude (solve Rf)"),gbw_hz:m.number().positive().optional().describe("Gain-bandwidth product in Hz (compute bandwidth)")},outputSchema:{gain_vv:m.number(),gain_db:m.number(),noise_gain:m.number(),rf_ohm:m.number(),bandwidth_hz:m.number().nullable()},annotations:x},async t=>{if(t.rf_ohm===void 0==(t.target_gain===void 0))return v("Error: provide exactly one of 'rf_ohm' (compute gain) or 'target_gain' (solve Rf).");try{let e=Nt(t.config,t.rin_ohm,{rfOhm:t.rf_ohm,targetGain:t.target_gain,gbwHz:t.gbw_hz}),r=e.bandwidthHz!==null?`, BW ${p(e.bandwidthHz,1)} Hz`:"";return y(`Op-amp ${t.config}: Av = ${p(e.gainVV,3)} V/V (${p(e.gainDb,2)} dB), noise gain ${p(e.noiseGain,3)}, Rf ${p(e.rfOhm,1)} \u03A9${r}`,{gain_vv:p(e.gainVV,4),gain_db:p(e.gainDb,3),noise_gain:p(e.noiseGain,4),rf_ohm:p(e.rfOhm,2),bandwidth_hz:e.bandwidthHz!==null?p(e.bandwidthHz,3):null})}catch(e){return v(`Error: ${e instanceof Error?e.message:String(e)}`)}}),n.registerTool("embedcalc_rc_filter",{title:"First-Order RC Filter Cutoff",description:`First-order RC low-pass or high-pass cutoff frequency: fc = 1/(2*pi*R*C), tau = R*C. LP and HP share the cutoff formula (response shape differs).
|
|
208
|
+
|
|
209
|
+
Args: type ('lowpass'|'highpass'), and exactly two of { r_ohm, c_farad, fc_hz } -> solves the third.
|
|
210
|
+
Returns (structured): { fc_hz, r_ohm, c_farad, tau_s }.
|
|
211
|
+
Example: R=10k, C=1uF -> fc 15.92 Hz. Common for ADC anti-aliasing / debounce. Snap the computed part with embedcalc_eseries_nearest.`,inputSchema:{type:m.enum(["lowpass","highpass"]).describe("Filter response type"),r_ohm:m.number().positive().optional().describe("Resistor in ohms"),c_farad:m.number().positive().optional().describe("Capacitor in farads (100nF = 100e-9)"),fc_hz:m.number().positive().optional().describe("Cutoff frequency in Hz")},outputSchema:{fc_hz:m.number(),r_ohm:m.number(),c_farad:m.number(),tau_s:m.number()},annotations:x},async t=>{if([t.r_ohm,t.c_farad,t.fc_hz].filter(r=>r!==void 0).length!==2)return v("Error: provide exactly two of 'r_ohm', 'c_farad', 'fc_hz' (the third is solved).");try{let r=Pt(t.type,{rOhm:t.r_ohm,cFarad:t.c_farad,fcHz:t.fc_hz}),o=B(r.cFarad,6);return y(`RC ${t.type}: fc = ${p(r.fcHz,3)} Hz (R ${p(r.rOhm,2)} \u03A9, C ${o} F, tau ${B(r.tauS,6)} s)`,{fc_hz:p(r.fcHz,4),r_ohm:p(r.rOhm,3),c_farad:o,tau_s:B(r.tauS,6)})}catch(r){return v(`Error: ${r instanceof Error?r.message:String(r)}`)}}),n.registerTool("embedcalc_bjt_bias",{title:"BJT Voltage-Divider Bias Point",description:`DC operating point of an NPN voltage-divider (four-resistor) bias network, including base-current loading.
|
|
212
|
+
|
|
213
|
+
Vth = Vcc*R2/(R1+R2), Rth = R1||R2, Ib = (Vth-Vbe)/(Rth+(b+1)*Re), Ic = b*Ib, Vce = Vcc - Ic*Rc - Ie*Re. Region: cutoff / active / saturation (Vce <= 0.2 V).
|
|
214
|
+
Args: vcc_v, r1_ohm (top, base-to-Vcc), r2_ohm (bottom, base-to-GND), rc_ohm, re_ohm (default 0), beta (default 100), vbe_v (default 0.7).
|
|
215
|
+
Returns (structured): { ib_a, ic_a, ie_a, vb_v, ve_v, vc_v, vce_v, region }.
|
|
216
|
+
Example: Vcc=12, R1=47k, R2=10k, Rc=1k, Re=220, b=100 -> Ic 4.61 mA, Vce 6.36 V, active.`,inputSchema:{vcc_v:m.number().positive().describe("Supply voltage Vcc in V"),r1_ohm:m.number().positive().describe("Upper divider resistor (base to Vcc)"),r2_ohm:m.number().positive().describe("Lower divider resistor (base to GND)"),rc_ohm:m.number().min(0).describe("Collector resistor in ohms"),re_ohm:m.number().min(0).default(0).describe("Emitter resistor in ohms"),beta:m.number().positive().default(100).describe("DC current gain hFE"),vbe_v:m.number().positive().default(.7).describe("Base-emitter drop in V")},outputSchema:{ib_a:m.number(),ic_a:m.number(),ie_a:m.number(),vb_v:m.number(),ve_v:m.number(),vc_v:m.number(),vce_v:m.number(),region:m.enum(["active","saturation","cutoff"])},annotations:x},async t=>{let e=kt(t.vcc_v,t.r1_ohm,t.r2_ohm,t.rc_ohm,t.re_ohm,t.beta,t.vbe_v);return y(`BJT bias: Ic = ${p(e.icA*1e3,4)} mA, Vce = ${p(e.vceV,3)} V, region ${e.region} (Ib ${p(e.ibA*1e6,3)} \xB5A, Vb ${p(e.vbV,3)} V, Ve ${p(e.veV,3)} V)`,{ib_a:p(e.ibA,9),ic_a:p(e.icA,7),ie_a:p(e.ieA,7),vb_v:p(e.vbV,4),ve_v:p(e.veV,4),vc_v:p(e.vcV,4),vce_v:p(e.vceV,4),region:e.region})}),n.registerTool("embedcalc_mosfet_switch",{title:"MOSFET Low-Side Switch Losses",description:`Conduction loss and gate-drive requirements for a MOSFET used as a low-side switch. Conduction + gate-drive ONLY \u2014 switching loss (needs drain voltage and rise/fall times) is NOT included.
|
|
217
|
+
|
|
218
|
+
P_cond = Id^2 * Rds_on * duty. Gate avg current = Qg * fsw. Gate power = Qg * Vdrive * fsw. Tj = Ta + P_cond * theta_JA.
|
|
219
|
+
Args: id_a, rds_on_ohm, duty (0-1, default 1); optional qg_c (coulombs), fsw_hz, vdrive_v, theta_ja, ambient_c, tj_max_c (default 150).
|
|
220
|
+
Returns (structured): { p_conduction_w, gate_drive_avg_a, gate_drive_power_w, tj_c, margin_c, ok } (optional fields null if inputs omitted).
|
|
221
|
+
Example: Id=5A, Rds=20m\u03A9 -> 0.5 W conduction; Qg=20nC @ 100kHz -> 2 mA drive.`,inputSchema:{id_a:m.number().positive().describe("Drain (load) current in A"),rds_on_ohm:m.number().positive().describe("On-resistance Rds(on) in ohms"),duty:m.number().min(0).max(1).default(1).describe("Conduction duty cycle 0-1"),qg_c:m.number().positive().optional().describe("Total gate charge in coulombs (20nC = 20e-9)"),fsw_hz:m.number().positive().optional().describe("Switching frequency in Hz"),vdrive_v:m.number().positive().optional().describe("Gate drive voltage in V"),theta_ja:m.number().positive().optional().describe("Junction-to-ambient thermal resistance \xB0C/W"),ambient_c:m.number().optional().describe("Ambient temperature in \xB0C"),tj_max_c:m.number().positive().default(150).describe("Max junction temperature rating in \xB0C")},outputSchema:{p_conduction_w:m.number(),gate_drive_avg_a:m.number().nullable(),gate_drive_power_w:m.number().nullable(),tj_c:m.number().nullable(),margin_c:m.number().nullable(),ok:m.boolean().nullable()},annotations:x},async t=>{let e=It(t.id_a,t.rds_on_ohm,t.duty,{qgC:t.qg_c,fswHz:t.fsw_hz,vdriveV:t.vdrive_v,thetaJa:t.theta_ja,ambientC:t.ambient_c,tjMaxC:t.tj_max_c}),r=e.gateDriveAvgA!==null?`, gate ${p(e.gateDriveAvgA*1e3,4)} mA avg`:"",o=e.tjC!==null?`, Tj ${p(e.tjC,1)} \xB0C (${e.ok?"OK":"OVER LIMIT"})`:"";return y(`MOSFET switch: P_cond = ${p(e.pConductionW,4)} W (conduction + gate only)${r}${o}`,{p_conduction_w:p(e.pConductionW,5),gate_drive_avg_a:e.gateDriveAvgA!==null?B(e.gateDriveAvgA,6):null,gate_drive_power_w:e.gateDrivePowerW!==null?B(e.gateDrivePowerW,6):null,tj_c:e.tjC!==null?p(e.tjC,2):null,margin_c:e.marginC!==null?p(e.marginC,2):null,ok:e.ok})}),n.registerTool("embedcalc_zener_reg",{title:"Zener Shunt Regulator",description:`Series resistor and power dissipation for a Zener shunt regulator. Is = (Vin-Vz)/Rs, Iz = Is - Il, Pz = Vz*Iz, Prs = (Vin-Vz)*Is.
|
|
222
|
+
|
|
223
|
+
Args: vin_v, vz_v, il_a (load current, default 0), exactly one of rs_ohm (compute Iz) or iz_min_a (solve Rs), pz_max_w (optional Zener rating check).
|
|
224
|
+
Returns (structured): { rs_ohm, is_a, iz_a, pz_w, prs_w, zener_ok (null if no pz_max) }.
|
|
225
|
+
Example: Vin=12, Vz=5.1, Il=10mA, Rs=220 -> Iz 21.4 mA, Pz 0.109 W. Snap Rs with embedcalc_eseries_nearest.`,inputSchema:{vin_v:m.number().positive().describe("Input voltage in V"),vz_v:m.number().positive().describe("Zener voltage in V"),il_a:m.number().min(0).default(0).describe("Load current in A"),rs_ohm:m.number().positive().optional().describe("Series resistor (compute Iz)"),iz_min_a:m.number().positive().optional().describe("Target minimum Zener current (solve Rs)"),pz_max_w:m.number().positive().optional().describe("Zener power rating in W (sets zener_ok)")},outputSchema:{rs_ohm:m.number(),is_a:m.number(),iz_a:m.number(),pz_w:m.number(),prs_w:m.number(),zener_ok:m.boolean().nullable()},annotations:x},async t=>{if(t.rs_ohm===void 0==(t.iz_min_a===void 0))return v("Error: provide exactly one of 'rs_ohm' (compute Iz) or 'iz_min_a' (solve Rs).");try{let e=Dt(t.vin_v,t.vz_v,t.il_a,{rsOhm:t.rs_ohm,izMinA:t.iz_min_a,pzMaxW:t.pz_max_w}),r=e.zenerOk===!1?" \u2014 WARNING: Pz exceeds rating":"";return y(`Zener: Rs ${p(e.rsOhm,2)} \u03A9, Iz ${p(e.izA*1e3,3)} mA, Pz ${p(e.pzW,4)} W, Prs ${p(e.prsW,4)} W${r}`,{rs_ohm:p(e.rsOhm,3),is_a:p(e.isA,6),iz_a:p(e.izA,6),pz_w:p(e.pzW,5),prs_w:p(e.prsW,5),zener_ok:e.zenerOk})}catch(e){return v(`Error: ${e instanceof Error?e.message:String(e)}`)}})}import{z as $}from"zod";var Rn="https://www.embedcalc.com/api/digikey-param",Fn={resistor:"ohms",capacitor:"farads",inductor:"henries",ldo:"vout",mosfet:"vds",bjt:"vce"},En={resistor:"resistance in ohms",capacitor:"capacitance in farads (e.g. 1e-7 = 100 nF)",inductor:"inductance in henries (e.g. 1e-5 = 10 \xB5H)",ldo:"fixed output voltage in volts",mosfet:"minimum Vds rating in volts (N-channel)",bjt:"minimum Vce rating in volts (NPN)"};function Bt(n){n.registerTool("embedcalc_find_component",{title:"Find Real Component (MPN + buy link)",description:`Find real, in-stock components matching an electrical value via parametric distributor search \u2014 returns manufacturer part numbers and buy links ONLY (no price or stock data, by design).
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
- type: resistor | capacitor | inductor | ldo | mosfet | bjt.
|
|
229
|
+
- value (number): ${Object.entries(En).map(([t,e])=>`${t}: ${e}`).join("; ")}.
|
|
230
|
+
- Optional filters: tolerance_pct (R/C/L: keep this tolerance or tighter), min_power_w (resistor), min_voltage_v (capacitor rating), min_current_a (ldo/mosfet/bjt), max_rds_on_ohm (mosfet), min_hfe (bjt).
|
|
231
|
+
- max_results (default 5).
|
|
232
|
+
|
|
233
|
+
Returns (structured): { matched_value, parts: [{ mpn, manufacturer, description, buy_url }] }. For prices, follow the buy link \u2014 pricing is intentionally not returned.
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
- "220 ohm 1% resistor" -> type='resistor', value=220, tolerance_pct=1
|
|
237
|
+
- "100 nF cap rated 16V+" -> type='capacitor', value=1e-7, min_voltage_v=16
|
|
238
|
+
- "3.3V LDO, 500 mA" -> type='ldo', value=3.3, min_current_a=0.5
|
|
239
|
+
Requires internet access (queries embedcalc.com).`,inputSchema:{type:$.enum(["resistor","capacitor","inductor","ldo","mosfet","bjt"]).describe("Component type"),value:$.number().positive().describe("Primary value \u2014 meaning depends on type (ohms / farads / henries / volts)"),tolerance_pct:$.number().positive().max(20).optional().describe("Max tolerance in % (resistor/capacitor/inductor)"),min_power_w:$.number().positive().optional().describe("Minimum power rating in watts (resistor)"),min_voltage_v:$.number().positive().optional().describe("Minimum voltage rating in volts (capacitor)"),min_current_a:$.number().positive().optional().describe("Minimum current in amps (ldo output / mosfet Id / bjt Ic)"),max_rds_on_ohm:$.number().positive().optional().describe("Maximum Rds(on) in ohms (mosfet)"),min_hfe:$.number().positive().optional().describe("Minimum hFE / current gain (bjt)"),max_results:$.number().int().min(1).max(20).default(5).describe("Maximum parts to return")},outputSchema:{matched_value:$.string().describe("The parametric value string the search matched"),parts:$.array($.object({mpn:$.string(),manufacturer:$.string(),description:$.string(),buy_url:$.string()}))},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},async t=>{let e=new URLSearchParams;e.set("type",t.type),e.set(Fn[t.type],String(t.value)),t.tolerance_pct!==void 0&&e.set("tolPct",String(t.tolerance_pct)),t.min_power_w!==void 0&&e.set("watts",String(t.min_power_w)),t.min_voltage_v!==void 0&&e.set("volts",String(t.min_voltage_v)),t.min_current_a!==void 0&&e.set("amps",String(t.min_current_a)),t.max_rds_on_ohm!==void 0&&e.set("rdsMax",String(t.max_rds_on_ohm)),t.min_hfe!==void 0&&e.set("hfeMin",String(t.min_hfe));let r;try{let l=new AbortController,a=setTimeout(()=>l.abort(),2e4),c=await fetch(`${Rn}?${e.toString()}`,{signal:l.signal});if(clearTimeout(a),!c.ok)return v(`Error: part search failed (HTTP ${c.status}). Try again or relax the filters.`);r=await c.json()}catch(l){return v(`Error: could not reach embedcalc.com part search (${l instanceof Error?l.message:String(l)}). Check internet access.`)}if(r.error)return v(`Error: ${r.error}`);let o=(r.results??[]).slice(0,t.max_results);if(!o.length)return v(`No parts found for ${t.type} = ${t.value}. Try relaxing tolerance/rating filters.`);let s=o.map(l=>({mpn:l.part_number??"",manufacturer:l.manufacturer??"",description:l.description??"",buy_url:l.url??""})),i=[`# Matching parts \u2014 ${t.type} ${r.matched??t.value}`,"",...s.map(l=>`- **${l.mpn}** (${l.manufacturer}) \u2014 ${l.description}
|
|
240
|
+
${l.buy_url}`),"","_Prices/stock: follow the buy link (not re-served here by design)._"];return y(i.join(`
|
|
241
|
+
`),{matched_value:r.matched??String(t.value),parts:s})})}import{z as oe}from"zod";import{readFileSync as ln}from"node:fs";import{z as R}from"zod";var An=["mcu","cap","res","ind","ic","conn","xtal","led","diode","other"],Mn=["in","out","bidir","power","passive","nc"],Tn=["anode","cathode","positive","negative"],$n=R.object({ref:R.string().min(1),type:R.enum(An),value:R.string().optional(),pinDirs:R.record(R.string(),R.enum(Mn)).optional(),ratings:R.object({voltage_v:R.number().positive().optional(),power_w:R.number().positive().optional()}).optional(),pinRoles:R.record(R.string(),R.enum(Tn)).optional()}),Nn=R.object({name:R.string().min(1),pins:R.array(R.string().regex(/^[^.]+\.[^.]+$/)).min(1),voltage_v:R.number().optional()}),pe=R.object({mcu:R.object({family:R.string().optional(),part:R.string().optional()}).optional(),parts:R.array($n),nets:R.array(Nn)});function L(n){let t=n.indexOf(".");return{ref:n.slice(0,t),pin:n.slice(t+1)}}function K(n){let t=new Map(n.parts.map(s=>[s.ref,s])),e=new Map(n.nets.map(s=>[s.name,s])),r=new Map,o=new Map;for(let s of n.nets)for(let i of s.pins){r.set(i,s);let{ref:l}=L(i),a=o.get(l)??[];a.includes(s)||a.push(s),o.set(l,a)}return{sch:n,partByRef:t,netByName:e,netOfPin:r,netsOfPart:o}}var Pn=/^(VDD|VCC|VBAT|VDDA|VDDIO|VIN|3V3|5V|1V8|2V5|\+\d)/i,kn=/^(GND|VSS|AGND|DGND|VSSA|PGND)/i,In=/(^|_)[AD]?(VDD|VCC)|(^|_|[A-Z])\d+(\.\d+)?V\d*($|_)|^VC\d|(^|_)V\d+P\d+|(^|_)\d+V(CC|DD)|(^|_)VD\d\d($|_)/i;function T(n){return Pn.test(n.name)||In.test(n.name)||(n.voltage_v??0)>0}var Dn=/(^|_)[AD]?(GND|VSS)($|_|\d)/i;function A(n){return kn.test(n.name)||Dn.test(n.name)}function S(n,t,e){let r=new Set,o=[];for(let s of t.pins){let{ref:i}=L(s);if(r.has(i))continue;r.add(i);let l=n.partByRef.get(i);l&&(!e||l.type===e)&&o.push(l)}return o}var zn=/^0(\.0+)?\s*(R|Ω|OHMS?)?$/i,Bn=/(FERRITE|BEAD)/i;function On(n){return n.type==="res"?zn.test((n.value??"").trim()):n.type==="ind"?Bn.test(n.value??"")||/^FB/i.test(n.ref):!1}function k(n,t){let e=[t];for(let r=0;r<e.length;r++)for(let o of S(n,e[r]))if(On(o))for(let s of n.netsOfPart.get(o.ref)??[])!e.includes(s)&&!A(s)&&e.push(s);return e}function N(n,t,e,r){let o=n.netsOfPart.get(t)??[];return o.includes(e)&&o.includes(r)}var Ln={id:"missing_decoupling_cap",severity:"error",description:"Checks that every power rail has a decoupling capacitor to GND (nets joined by a 0\u03A9 resistor or ferrite bead count as one rail)",run(n){let t=n.sch.nets.filter(T),e=n.sch.nets.filter(A);if(t.length===0||e.length===0)return{applicable:!1,violations:[]};let r=[],o=new Set;for(let s of t){let i=k(n.graph,s);if(!Ot(n.graph,i))continue;if(!i.some(a=>S(n.graph,a,"cap").some(c=>e.some(u=>N(n.graph,c.ref,a,u))))&&!i.some(a=>o.has(a.name))){i.forEach(c=>o.add(c.name));let a=i.map(c=>c.name).join(" / ");r.push({severity:"error",rule:"missing_decoupling_cap",evidence:`No decoupling capacitor between power net ${a} and GND`,fix:`Place 100nF from ${s.name} to GND close to each supply pin`,ref:"MCU datasheet \u2014 power supply / decoupling recommendations"})}}return{applicable:!0,violations:r}}},Vn={id:"floating_input_pin",severity:"warning",description:"Flags nets with only one pin connected (possibly unconnected)",run(n){let t=[];for(let e of n.sch.nets)e.pins.length===1&&!T(e)&&!A(e)&&t.push({severity:"warning",rule:"floating_input_pin",evidence:`Net ${e.name} has only one pin (${e.pins[0]}) \u2014 it may be floating`,fix:"Ignore if intentionally unused; otherwise connect it or add a pull-up/pull-down",ref:"General \u2014 floating inputs cause undefined logic levels / leakage"});return{applicable:!0,violations:t}}},Un=/SDA/i,jn=/SCL(?!K)/i,Hn={id:"missing_i2c_pullup",severity:"error",description:"Checks that I2C SDA/SCL nets have pull-up resistors to a supply (warning instead of error when the bus only leaves through a connector)",run(n){let t=n.sch.nets.filter(o=>Un.test(o.name)||jn.test(o.name));if(t.length===0)return{applicable:!1,violations:[]};let e=n.sch.nets.filter(T),r=[];for(let o of t)if(![o,...S(n.graph,o,"res").flatMap(l=>(n.graph.netsOfPart.get(l.ref)??[]).filter(a=>a!==o&&!T(a)&&!A(a)))].some(l=>S(n.graph,l,"res").some(a=>e.some(c=>N(n.graph,a.ref,l,c))))){let l=S(n.graph,o,"conn").length>0;r.push({severity:l?"warning":"error",rule:"missing_i2c_pullup",evidence:l?`I2C net ${o.name} has no pull-up resistor to a supply on this board \u2014 it goes to a connector, so the pull-ups may be on the attached board`:`I2C net ${o.name} has no pull-up resistor to a supply`,fix:`Add a 4.7k\u03A9 pull-up from ${o.name} to VDD`,ref:"I2C specification \u2014 SDA/SCL are open-drain, require pull-ups"})}return{applicable:!0,violations:r}}};function de(n){if(!n)return null;let t=n.trim().replace("\xB5","u"),e=/^([\d.]+)\s*([pnum]?)F?(?=$|[\s\/,_])/i.exec(t);if(!e)return null;let r={p:1e-12,n:1e-9,u:1e-6,m:.001,"":1};return parseFloat(e[1])*r[e[2].toLowerCase()]}function Ot(n,t){return t.some(e=>S(n,e).some(r=>r.type==="ic"||r.type==="mcu"))}var Gn={id:"missing_bulk_cap",severity:"warning",description:"Checks that every power rail has a bulk capacitor (\u22651\xB5F); nets joined by a 0\u03A9 resistor or ferrite bead count as one rail",run(n){let t=n.sch.nets.filter(T),e=n.sch.nets.filter(A);if(t.length===0||e.length===0)return{applicable:!1,violations:[]};let r=[],o=new Set;for(let s of t){let i=k(n.graph,s);if(!Ot(n.graph,i))continue;if(!i.flatMap(c=>S(n.graph,c,"cap").filter(u=>e.some(d=>N(n.graph,u.ref,c,d)))).some(c=>{let u=de(c.value);return u===null||u>=1e-6})&&!i.some(c=>o.has(c.name))){i.forEach(u=>o.add(u.name));let c=i.map(u=>u.name).join(" / ");r.push({severity:"warning",rule:"missing_bulk_cap",evidence:`No bulk capacitor (\u22651\xB5F) on power net ${c}`,fix:`Add a 10\xB5F bulk capacitor from ${s.name} to GND`,ref:"General power integrity \u2014 bulk decoupling per supply rail"})}}return{applicable:!0,violations:r}}},Wn=/^([N~\/#]?(RST|RESET|MCLR)|T?ICE_?RST)/i,qn={id:"reset_pin_handling",severity:"warning",description:"Checks that the reset net has an external pull-up resistor or debounce capacitor (warning only: many MCUs have an internal pull-up)",run(n){let t=n.sch.nets.filter(r=>Wn.test(r.name));if(t.length===0)return{applicable:!1,violations:[]};let e=[];for(let r of t){let o=S(n.graph,r,"res").length>0,s=S(n.graph,r,"cap").length>0;!o&&!s&&e.push({severity:"warning",rule:"reset_pin_handling",evidence:`Reset net ${r.name} has no external pull-up or debounce part (works on MCUs with an internal pull-up, but noise immunity is weak)`,fix:`Add a 100nF debounce capacitor on ${r.name} (plus a 10k\u03A9 pull-up if needed)`,ref:"MCU datasheet \u2014 reset pin; e.g. STM32 NRST has internal pull-up, external RC recommended"})}return{applicable:!0,violations:e}}},Kn={id:"output_conflict",severity:"error",description:"Checks for two or more output pins driving the same net (needs pin direction data)",run(n){if(!n.sch.parts.some(r=>r.pinDirs&&Object.keys(r.pinDirs).length>0))return{applicable:!1,violations:[]};let e=[];for(let r of n.sch.nets){let o=r.pins.filter(s=>{let i=s.indexOf("."),l=s.slice(0,i),a=s.slice(i+1);return n.graph.partByRef.get(l)?.pinDirs?.[a]==="out"});o.length>=2&&e.push({severity:"error",rule:"output_conflict",evidence:`${o.length} output pins drive net ${r.name} (${o.join(", ")})`,fix:"Keep a single driver, or separate them with a buffer / tri-state",ref:"General \u2014 multiple push-pull outputs on one net cause contention"})}return{applicable:!0,violations:e}}},Jn=/(VDD|VCC|VBAT|VDDA|VIN|3V3|5V|1V8)/i,Zn=/(GND|VSS|AGND|DGND)/i,Xn={id:"power_short",severity:"error",description:"Flags a net whose name suggests both a supply and ground (possible short; conservative)",run(n){let t=[];for(let e of n.sch.nets)Jn.test(e.name)&&Zn.test(e.name)&&t.push({severity:"error",rule:"power_short",evidence:`Net name ${e.name} suggests both a supply and ground \u2014 possible short`,fix:"Separate the supply and GND nets and label them clearly",ref:"General \u2014 VDD shorted to GND"});return{applicable:!0,violations:t}}},fe={id:"netlist_consistency",severity:"error",description:"Sanity-checks the transcribed netlist itself: a pin listed in two nets, a pin whose part is not in the parts list, parts with no connection. Any hit means the other findings may be artefacts of a bad transcription",run(n){let t=[],e=new Map;for(let i of n.sch.nets)for(let l of new Set(i.pins))e.set(l,[...e.get(l)??[],i.name]);let r=[...e].filter(([,i])=>i.length>1);r.length>0&&t.push({severity:"error",rule:"netlist_consistency",evidence:`${r.length} pin(s) appear in more than one net (${r.slice(0,8).map(([i,l])=>`${i}: ${l.join(" + ")}`).join("; ")}${r.length>8?"; \u2026":""}) \u2014 a pin can only be on one net, so the transcription is wrong and the other findings are unreliable`,fix:"Re-read those pins on the drawing; if the nets really are one node, merge them into a single net",ref:"Netlist integrity \u2014 one pin, one net"});let o=[...new Set([...e.keys()].map(i=>i.slice(0,i.indexOf("."))))].filter(i=>!n.graph.partByRef.has(i));o.length>0&&t.push({severity:"error",rule:"netlist_consistency",evidence:`Nets reference part(s) missing from the parts list: ${o.slice(0,12).join(", ")}${o.length>12?", \u2026":""} \u2014 rules that look for a part type on a net cannot see them`,fix:"Add those parts (with their type) to the parts list, or correct the reference",ref:"Netlist integrity \u2014 every pin belongs to a listed part"});let s=n.sch.parts.filter(i=>!n.graph.netsOfPart.has(i.ref)).map(i=>i.ref);return s.length>0&&t.push({severity:"warning",rule:"netlist_consistency",evidence:`${s.length} part(s) are not connected to any net: ${s.slice(0,12).join(", ")}${s.length>12?", \u2026":""} \u2014 their connections were probably not transcribed`,fix:"Transcribe their connections, or remove them if they are not fitted",ref:"Netlist integrity \u2014 listed parts should be connected"}),{applicable:!0,violations:t}}},J=[fe,Ln,Gn,Vn,Hn,qn,Kn,Xn];var Lt={family:"stm32f4",rules:[{id:"stm32f4_boot0_defined",severity:"warning",check:"net_exists",net_pattern:"^BOOT0$",message:"No BOOT0 net label \u2014 the boot mode cannot be confirmed from the netlist (fine if the pin is tied straight to a rail)",fix:"Add a 10k\u03A9 pull-down on BOOT0 (boot from flash)",ref:"STM32F4 RM0090 \xA72.4 Boot configuration"},{id:"stm32f4_vcap_present",severity:"error",check:"net_has_part",net_pattern:"^VCAP",part_type:"cap",message:"No stabilising capacitor on the VCAP pins",fix:"Place 2.2\xB5F ceramic capacitors on VCAP1/VCAP2",ref:"STM32F407 datasheet \u2014 VCAP1/VCAP2 stabilization"},{id:"stm32f4_vdda_filter",severity:"warning",check:"net_has_part",net_pattern:"^VDDA$",part_type:"cap",message:"No filter capacitor on VDDA",fix:"Add 100nF + 1\xB5F filtering on VDDA",ref:"STM32F4 datasheet \u2014 analog supply filtering"}]};var Vt={family:"stm32f1",rules:[{id:"stm32f1_boot0_defined",severity:"warning",check:"net_exists",net_pattern:"^BOOT0$",message:"No BOOT0 net label \u2014 the boot mode cannot be confirmed from the netlist (fine if the pin is tied straight to a rail)",fix:"Add a 10k\u03A9 pull-down on BOOT0 (boot from flash)",ref:"STM32F1 RM0008 \u2014 Boot configuration / AN2586 hardware getting started"},{id:"stm32f1_vdda_filter",severity:"warning",check:"net_has_part",net_pattern:"^VDDA$",part_type:"cap",message:"No filter capacitor on VDDA",fix:"Add 100nF + 1\xB5F filtering on VDDA",ref:"STM32F103 datasheet / AN2586 \u2014 analog supply (VDDA) filtering"}]};var Ut={family:"esp32",rules:[{id:"esp32_en_rc",severity:"warning",check:"net_has_rc",net_pattern:"^EN$",message:"No power-on reset RC (pull-up resistor + capacitor) on the EN pin",fix:"Add a 10k\u03A9 pull-up and a 1\xB5F capacitor on EN (RC delay)",ref:"Espressif ESP32 Hardware Design Guidelines \u2014 \xA7Power-on sequence (EN pin RC delay)"},{id:"esp32_gpio0_strapping",severity:"warning",check:"net_exists",net_pattern:"^(GPIO0|IO0)$",message:"No GPIO0 boot-strapping net \u2014 the boot mode cannot be confirmed from the netlist",fix:"Add a pull-up on GPIO0 (plus a button for download mode)",ref:"Espressif ESP32 Series Datasheet \u2014 \xA7Strapping Pins (GPIO0)"}]};var jt={family:"numicro",rules:[{id:"numicro_ldo_cap",severity:"error",check:"net_has_part",net_pattern:"^(LDO|DCDC)_?CAP",part_type:"cap",message:"No stabilising capacitor on the LDO_CAP pin",fix:"Place the capacitor the datasheet specifies on LDO_CAP (1\xB5F on many series)",ref:"Nuvoton NuMicro datasheet (e.g. M480/M031 series) \u2014 LDO_CAP 1\xB5F requirement"},{id:"numicro_nreset_pullup",severity:"error",check:"net_has_part",net_pattern:"^(nRESET|RESET|T?ICE_?RST)$",part_type:"res",message:"No pull-up resistor on nRESET",fix:"Add a 10k\u03A9 pull-up on nRESET",ref:"Nuvoton NuMicro datasheet \u2014 nRESET circuit recommendation (series-specific: many parts have internal pull-up)"}]};var Qn={stm32f4:Lt,stm32f1:Vt,esp32:Ut,numicro:jt};function Yn(n){return n.replace(/[\^$()]/g,"").replace(/\|/g," or ")}function er(n){let t=new RegExp(n.net_pattern,"i");return{id:n.id,severity:n.severity,description:n.message,run(e){let r=e.sch.nets.filter(s=>t.test(s.name));if(n.check==="net_exists")return r.length>0?{applicable:!0,violations:[]}:{applicable:!0,violations:[{severity:n.severity,rule:n.id,evidence:`No net named ${Yn(n.net_pattern)} is defined`,fix:n.fix,ref:n.ref}]};if(r.length===0)return{applicable:!1,violations:[]};let o=[];for(let s of r)if(n.check==="net_has_rc"){let i=[];S(e.graph,s,"res").length===0&&i.push("resistor"),S(e.graph,s,"cap").length===0&&i.push("capacitor"),i.length>0&&o.push({severity:n.severity,rule:n.id,evidence:`${s.name} RC network is missing: ${i.join(", ")}`,fix:n.fix,ref:n.ref})}else S(e.graph,s,n.part_type).length===0&&o.push({severity:n.severity,rule:n.id,evidence:`${s.name} has no ${n.part_type??"part"} (required)`,fix:n.fix,ref:n.ref});return{applicable:!0,violations:o}}}}function Z(n){if(!n)return[];let t=Qn[n.toLowerCase()];return t?t.rules.map(er):[]}function X(n){if(!n)return null;let t=n.trim().replace(/\s*(Ω|ohm[s]?)\s*$/i,""),e=/^(\d*)([kKMR])(\d*)$/.exec(t);if(e&&(e[1]!==""||e[3]!=="")){let s=e[2]==="R"?1:e[2]==="M"?1e6:1e3,i=e[1]===""?0:parseInt(e[1],10),l=e[3]===""?0:parseFloat("0."+e[3]);return(i+l)*s}let r=/^([\d.]+)([kKM]?)$/.exec(t);if(!r)return null;let o=parseFloat(r[1]);return isFinite(o)?o*(r[2]===""?1:r[2]==="M"?1e6:1e3):null}function Q(n){if(!n)return null;let t=/(\d+)V(\d+)/.exec(n);if(t)return parseFloat(t[1]+"."+t[2]);let e=/(\d+(?:\.\d+)?)\s*V\b/i.exec(n);return e?parseFloat(e[1]):null}function Y(n){return typeof n.voltage_v=="number"?n.voltage_v:A(n)?0:T(n)?"power":null}var tr={id:"polarity_orientation",severity:"error",description:"Checks for polarised parts fitted backwards (electrolytic cap = error, diode = warning); needs pinRoles and net voltages",run(n){let t=!1,e=[];for(let r of n.sch.parts){if(!r.pinRoles)continue;let o,s;for(let[d,b]of Object.entries(r.pinRoles))(b==="positive"||b==="anode")&&(o=d),(b==="negative"||b==="cathode")&&(s=d);if(!o||!s)continue;let i=n.graph.netOfPin.get(`${r.ref}.${o}`),l=n.graph.netOfPin.get(`${r.ref}.${s}`);if(!i||!l)continue;let a=Y(i),c=Y(l),u=!1;if(typeof a=="number"&&typeof c=="number")t=!0,u=c>a;else if(a===0&&(c==="power"||typeof c=="number"&&c>0))u=!0,t=!0;else if(c===0&&(a==="power"||typeof a=="number"&&a>0))t=!0;else continue;if(u){let d=r.type==="cap";e.push({severity:d?"error":"warning",rule:"polarity_orientation",evidence:`${r.ref} (${r.type}) is reversed \u2014 pin ${s} (negative/cathode) is on ${l.name}, pin ${o} is on ${i.name}`+(d?" (a reversed electrolytic capacitor can fail or burst)":" (may be intentional for a protection diode)"),fix:"Flip the part or check the net connections",ref:"Component datasheet \u2014 polarity marking"})}}return t?{applicable:!0,violations:e}:{applicable:!1,violations:[]}}},nr={id:"cap_voltage_derating",severity:"warning",description:"Checks capacitor voltage rating against the rail (rating < rail = error, < 1.5\xD7 rail = warning)",run(n){let t=n.sch.nets.filter(s=>T(s)&&typeof s.voltage_v=="number"),e=n.sch.nets.filter(A);if(t.length===0||e.length===0)return{applicable:!1,violations:[]};let r=!1,o=[];for(let s of t){let i=s.voltage_v;for(let l of s.pins){let{ref:a}=L(l),c=n.graph.partByRef.get(a);if(!c||c.type!=="cap"||!e.some(d=>N(n.graph,a,s,d)))continue;let u=c.ratings?.voltage_v??Q(c.value);u!=null&&(r=!0,u<i?o.push({severity:"error",rule:"cap_voltage_derating",evidence:`${c.ref} is rated ${u}V, below rail ${s.name}=${i}V \u2014 it can fail immediately`,fix:`Use a capacitor rated \u2265 ${Math.ceil(i*2)}V`,ref:"Capacitor derating practice (MLCC DC bias / electrolytic lifetime)"}):u<i*1.5&&o.push({severity:"warning",rule:"cap_voltage_derating",evidence:`${c.ref} is rated ${u}V \u2014 less than 1.5\xD7 the ${i}V rail (MLCC DC bias cuts the effective capacitance sharply)`,fix:`A rating \u2265 ${Math.ceil(i*2)}V is recommended`,ref:"Capacitor derating practice (MLCC DC bias / electrolytic lifetime)"}))}}return r?{applicable:!0,violations:o}:{applicable:!1,violations:[]}}},rr={id:"resistor_power_margin",severity:"warning",description:"Checks that resistor dissipation P=V\xB2/R stays within its rating (needs both net voltages, the value and the rating)",run(n){let t=!1,e=[];for(let r of n.sch.parts){if(r.type!=="res")continue;let o=r.ratings?.power_w;if(!o)continue;let s=X(r.value);if(s===null||s<=0)continue;let i=n.graph.netsOfPart.get(r.ref)??[];if(i.length!==2)continue;let l=Y(i[0]),a=Y(i[1]);if(typeof l!="number"||typeof a!="number")continue;t=!0;let c=Math.pow(l-a,2)/s;c>o&&e.push({severity:"warning",rule:"resistor_power_margin",evidence:`${r.ref}=${r.value}\u03A9 has ${Math.abs(l-a)}V across it \u2192 ${c.toFixed(3)}W, above its ${o}W rating`,fix:`Use a resistor rated \u2265 ${(c*2).toFixed(2)}W (2\xD7 margin recommended)`,ref:"Resistor power derating practice"})}return t?{applicable:!0,violations:e}:{applicable:!1,violations:[]}}},ee=[tr,nr,rr];var te="((HS|FS)?USB\\d?_?)?",ir=new RegExp(`^${te}(DP|D\\+|D_P)\\d?$`,"i"),or=new RegExp(`^${te}(DM|DN|D-|D_N)\\d?$`,"i"),sr=new RegExp(`^${te}(DP|DM|DN|D\\+|D-|D_P|D_N)\\d?$`,"i"),ar=new RegExp(`^${te}((HS|FS)_?)?VBUS\\d?$`,"i"),Ht=/(ESD|TVS|USBLC|PESD|SRV05|PRTR|TPD\d|NSP\d|SP30\d|[RU]CLAMP|IP42\d)/i;function be(n){return n.nets.some(t=>ir.test(t.name))&&n.nets.some(t=>or.test(t.name))}var cr={id:"usb_esd_protection",severity:"warning",description:"Checks that the USB D+/D- nets have an ESD/TVS protection device",run(n){if(!be(n.sch))return{applicable:!1,violations:[]};let t=n.sch.nets.filter(i=>sr.test(i.name)),e=i=>{let l=k(n.graph,i),a=l.flatMap(c=>S(n.graph,c,"res").flatMap(u=>(n.graph.netsOfPart.get(u.ref)??[]).filter(d=>!l.includes(d)&&!T(d)&&!A(d))));return[...l,...a.flatMap(c=>k(n.graph,c))]},r=i=>i.type==="diode"&&(n.graph.netsOfPart.get(i.ref)??[]).some(A)||(i.type==="diode"||i.type==="ic")&&(Ht.test(i.value??"")||Ht.test(i.ref)),o=t.filter(i=>!e(i).flatMap(l=>S(n.graph,l)).some(r));return{applicable:!0,violations:o.length===0?[]:[{severity:"warning",rule:"usb_esd_protection",evidence:`No ESD/TVS protection device on USB data nets ${o.map(i=>i.name).join(", ")}`,fix:"Add a USB ESD array (e.g. USBLC6-2SC6, TPD2E) on D+/D-, close to the connector",ref:"USB-IF / ST AN4879 \u2014 USB ESD protection"}]}}},lr={id:"usb_vbus_cap",severity:"warning",description:"Checks that VBUS has a capacitor",run(n){if(!be(n.sch))return{applicable:!1,violations:[]};let t=n.sch.nets.filter(r=>ar.test(r.name));if(t.length===0)return{applicable:!1,violations:[]};let e=[];for(let r of t)S(n.graph,r,"cap").length===0&&e.push({severity:"warning",rule:"usb_vbus_cap",evidence:`No capacitor on ${r.name}`,fix:"Add a bulk capacitor (1\u201310\xB5F) plus 100nF from VBUS to GND (keep a device at 10\xB5F total or less)",ref:"USB 2.0 spec \xA77.2.4 \u2014 VBUS bypass capacitance"});return{applicable:!0,violations:e}}},Gt={name:"usb",detect:be,rules:[cr,lr]};var ur=/^CAN_?H$/i,mr=/^CAN_?L$/i;function he(n){let t=n.nets.find(r=>ur.test(r.name)),e=n.nets.find(r=>mr.test(r.name));return t&&e?{h:t,l:e}:null}function pr(n){return he(n)!==null}var dr={id:"can_termination",severity:"warning",description:"Checks for a termination resistor (120\u03A9) between CANH and CANL (only the two end nodes of a bus need one)",run(n){let t=he(n.sch);if(!t)return{applicable:!1,violations:[]};let e=S(n.graph,t.h,"res").filter(o=>N(n.graph,o.ref,t.h,t.l)),r=[];if(e.length===0)r.push({severity:"warning",rule:"can_termination",evidence:`No termination resistor between ${t.h.name} and ${t.l.name} (fine if this is not an end node of the bus)`,fix:"Fit 120\u03A9 termination only on the two end nodes of the bus (middle nodes have none)",ref:"ISO 11898-2 \u2014 bus termination"});else for(let o of e){let s=X(o.value);s!==null&&(s<100||s>150)&&r.push({severity:"warning",rule:"can_termination",evidence:`Termination resistor ${o.ref} is ${o.value}\u03A9 \u2014 outside the standard 120\u03A9 (100\u2013150\u03A9)`,fix:"Change it to 120\u03A9 (for split termination use 2\xD760\u03A9 plus a capacitor)",ref:"ISO 11898-2 \u2014 bus termination"})}return{applicable:!0,violations:r}}},fr={id:"can_transceiver_present",severity:"warning",description:"Checks that a transceiver IC sits on CANH/CANL (catches MCU CAN_TX/RX wired straight to the bus)",run(n){let t=he(n.sch);return t?{applicable:!0,violations:[...S(n.graph,t.h,"ic"),...S(n.graph,t.l,"ic")].length>0?[]:[{severity:"warning",rule:"can_transceiver_present",evidence:`No IC on ${t.h.name}/${t.l.name} \u2014 the MCU CAN_TX/RX may be wired straight to the bus`,fix:"Place a CAN transceiver (SN65HVD23x, TJA105x, \u2026) between the MCU and the bus",ref:"ISO 11898-2 \u2014 transceiver required between controller and bus"}]}:{applicable:!1,violations:[]}}},Wt={name:"can",detect:pr,rules:[dr,fr]};var br=/RS?_?485.*A$/i,hr=/RS?_?485.*B$/i;function ge(n){let t=n.nets.find(r=>br.test(r.name)),e=n.nets.find(r=>hr.test(r.name));return t&&e?{a:t,b:e}:null}function _e(n){if(ge(n)!==null)return!0;let t=n.nets.some(r=>/^DE$/i.test(r.name)),e=n.nets.some(r=>/^~?RE$/i.test(r.name));return t&&e}var gr={id:"rs485_termination",severity:"warning",description:"Checks for a termination resistor between RS-485 A and B (only the two end nodes of a bus need one)",run(n){if(!_e(n.sch))return{applicable:!1,violations:[]};let t=ge(n.sch);return t?{applicable:!0,violations:S(n.graph,t.a,"res").some(o=>N(n.graph,o.ref,t.a,t.b))?[]:[{severity:"warning",rule:"rs485_termination",evidence:`No termination resistor between ${t.a.name} and ${t.b.name} (fine if this is not an end node of the bus)`,fix:"Fit 120\u03A9 termination only on the two end nodes of the bus",ref:"TIA/EIA-485 \u2014 line termination"}]}:{applicable:!1,violations:[]}}},_r={id:"rs485_bias",severity:"warning",description:"Checks for RS-485 fail-safe bias (pull-up / pull-down) resistors",run(n){if(!_e(n.sch))return{applicable:!1,violations:[]};let t=ge(n.sch);if(!t)return{applicable:!1,violations:[]};let e=n.sch.nets.filter(T),r=n.sch.nets.filter(A),o=[t.a,t.b],s=o.some(a=>S(n.graph,a,"res").some(c=>e.some(u=>N(n.graph,c.ref,a,u)))),i=o.some(a=>S(n.graph,a,"res").some(c=>r.some(u=>N(n.graph,c.ref,a,u))));return{applicable:!0,violations:s&&i?[]:[{severity:"warning",rule:"rs485_bias",evidence:`${t.a.name}/${t.b.name} has no fail-safe bias ${[s?"":"pull-up",i?"":"pull-down"].filter(Boolean).join(" or ")} resistor \u2014 the receiver level may be undefined when the bus is idle`,fix:"Add pull-up + pull-down bias at one node (usually the master) \u2014 size them with embedcalc_rs485_bus (\u2264720\u03A9 at Vcc 5V)",ref:"TIA/EIA-485 \u2014 fail-safe biasing"}]}}},qt={name:"rs485",detect:_e,rules:[gr,_r]};var yr=/^(SWD_?IO|SWD?_?CLK|SWO|J?TMS|J?TCK|J?TDI|J?TDO|T?ICE_?DAT|T?ICE_?CLK)$/i,Kt=/^([N~\/#]?(RST|RESET|MCLR)|T?ICE_?RST)/i;function ye(n){return n.nets.filter(t=>yr.test(t.name))}function xe(n){let t=n.nets.map(r=>r.name),e=r=>t.some(o=>r.test(o));return e(/^SWD_?IO$/i)&&e(/^SWD?_?CLK$/i)||e(/^J?TMS$/i)&&e(/^J?TCK$/i)||e(/^T?ICE_?DAT$/i)&&e(/^T?ICE_?CLK$/i)}var xr={id:"swd_connector",severity:"warning",description:"Checks that the SWD/JTAG debug nets reach a connector (catches boards a debugger cannot be attached to)",run(n){if(!xe(n.sch))return{applicable:!1,violations:[]};let t=i=>{let l=[i,...S(n.graph,i,"res").flatMap(a=>(n.graph.netsOfPart.get(a.ref)??[]).filter(c=>c!==i&&!T(c)&&!A(c)))];return[...new Set(l.flatMap(a=>S(n.graph,a).filter(c=>c.type==="ic"||c.type==="mcu").map(c=>c.ref)))]},e=i=>S(n.graph,i,"conn").length>0,r=ye(n.sch).filter(i=>e(i)||!t(i).every(l=>/^ICE/i.test(l)));return r.length===0?{applicable:!1,violations:[]}:{applicable:!0,violations:r.some(i=>e(i)||t(i).length>=2)?[]:[{severity:"warning",rule:"swd_connector",evidence:`No connector on the debug nets (${r.map(i=>i.name).join(", ")}) \u2014 a debugger/programmer cannot be attached`,fix:"Add an SWD header (e.g. 2\xD75 1.27mm Cortex Debug) wired to SWDIO/SWCLK/GND/VTref (+NRST)",ref:"ARM Cortex Debug Connector spec"}]}}},vr={id:"swd_reset_line",severity:"warning",description:"Checks that reset is wired to the debug connector (needed for lock-up recovery and connect-under-reset)",run(n){if(!xe(n.sch))return{applicable:!1,violations:[]};let t=new Set;for(let i of ye(n.sch))for(let l of S(n.graph,i,"conn"))t.add(l.ref);if(t.size===0)return{applicable:!1,violations:[]};let e=new Set(ye(n.sch).flatMap(i=>S(n.graph,i).filter(l=>l.type==="ic"||l.type==="mcu").map(l=>l.ref)));return{applicable:!0,violations:n.sch.nets.some(i=>Kt.test(i.name)&&S(n.graph,i).filter(l=>e.has(l.ref)).length>=2)||[...t].some(i=>(n.graph.netsOfPart.get(i)??[]).some(l=>Kt.test(l.name)))?[]:[{severity:"warning",rule:"swd_reset_line",evidence:`Reset is not wired to the debug connector (${[...t].join(", ")}) \u2014 connect-under-reset is impossible from lock-up or low-power states`,fix:"Wire the connector nRESET pin to the MCU reset pin",ref:"ARM Cortex Debug Connector \u2014 nRESET pin"}]}}},Jt={name:"swd",detect:xe,rules:[xr,vr]};var wr=/(XTAL|OSC(?!_?EN)|(?<![A-Z])(HSE|LSE|HXT|LXT)(?![A-Z])|(?<![A-Z])XT\d?_?(IN|OUT)|(?<![A-Z])X(IN|OUT)(?![A-Z]))/i;function ve(n){let t=new Set(n.parts.filter(r=>r.type==="xtal").map(r=>r.ref)),e=r=>!T(r)&&!A(r)&&r.pins.some(o=>t.has(o.slice(0,o.indexOf("."))));return n.nets.filter(r=>wr.test(r.name)||e(r))}function we(n){return ve(n).length>0}function Zt(n,t){let e=n.sch.nets.filter(A);return k(n.graph,t).flatMap(r=>S(n.graph,r,"cap").filter(o=>e.some(s=>N(n.graph,o.ref,r,s))))}var Sr={id:"crystal_load_caps",severity:"warning",description:"Checks that each crystal net has a load capacitor to GND",run(n){if(!we(n.sch))return{applicable:!1,violations:[]};let t=[];for(let e of ve(n.sch))Zt(n,e).length===0&&t.push({severity:"warning",rule:"crystal_load_caps",evidence:`No load capacitor to GND on crystal net ${e.name} (fine for an oscillator module or a part with built-in capacitors)`,fix:"Add a load capacitor on each crystal pin \u2014 size it with embedcalc_crystal_load_caps (C = 2\xB7(CL\u2212Cstray))",ref:"Crystal datasheet CL spec / MCU AN (e.g. ST AN2867)"});return{applicable:!0,violations:t}}},Cr={id:"crystal_cap_value_sane",severity:"warning",description:"Checks that load capacitor values are in the usual range (4.7\u201333pF)",run(n){if(!we(n.sch))return{applicable:!1,violations:[]};let t=!1,e=[];for(let r of ve(n.sch))for(let o of Zt(n,r)){let s=de(o.value);s!==null&&(t=!0,(s<47e-13||s>33e-12)&&e.push({severity:"warning",rule:"crystal_cap_value_sane",evidence:`Load capacitor ${o.ref}=${o.value} is outside the usual 4.7\u201333pF range (possibly confused with a decoupling capacitor)`,fix:"Recalculate from the crystal CL spec \u2014 embedcalc_crystal_load_caps",ref:"Crystal datasheet CL spec"}))}return t?{applicable:!0,violations:e}:{applicable:!1,violations:[]}}},Xt={name:"crystal",detect:we,rules:[Sr,Cr]};var Se=[Gt,Wt,qt,Jt,Xt],ne=Se.flatMap(n=>n.rules);var Qt={error:0,warning:1,info:2};function Yt(n){let t=K(n),e={sch:n,graph:t},r=[...J,...ee,...ne,...Z(n.mcu?.family)],o=[],s=[],i=[];for(let a of r){let c=a.run(e);c.applicable?(s.push(a.id),o.push(...c.violations)):i.push(a.id)}o.sort((a,c)=>Qt[a.severity]-Qt[c.severity]);let l=Se.filter(a=>a.detect(n)).map(a=>a.name);return{violations:o,checked:s,skipped:i,interfacesDetected:l}}function Rr(n){let t=[],e=0;for(;e<n.length;){let r=n[e];if(r==="("||r===")")t.push(r),e++;else if(r==='"'){let o="";for(e++;e<n.length&&n[e]!=='"';)n[e]==="\\"&&e++,o+=n[e++];e++,t.push(JSON.stringify(o))}else if(/\s/.test(r))e++;else{let o="";for(;e<n.length&&!/[\s()]/.test(n[e]);)o+=n[e++];t.push(o)}}return t}function Fr(n){let t=0;function e(){let r=n[t++];if(r==="("){let o=[];for(;n[t]!==")";){if(t>=n.length)throw new Error("unbalanced S-expression");o.push(e())}return t++,o}return r.startsWith('"')?JSON.parse(r):r}return e()}function P(n,t){return Array.isArray(n)?n.filter(e=>Array.isArray(e)&&e[0]===t):[]}function j(n,t=1){let e=n[t];return typeof e=="string"?e:""}var Er={U:"ic",IC:"ic",C:"cap",R:"res",L:"ind",Y:"xtal",X:"xtal",D:"diode",LED:"led",J:"conn",P:"conn",SW:"other",Q:"other"};function re(n){let t=/^[A-Za-z]+/.exec(n),e=(t?t[0]:"").toUpperCase();return Er[e]??"other"}function en(n){let t=Fr(Rr(n));if(!Array.isArray(t)||t[0]!=="export")throw new Error("not a KiCad netlist export (expected top-level (export ...))");let e=[],r=P(t,"components")[0];if(r)for(let i of P(r,"comp")){let l=j(P(i,"ref")[0]??[]),a=j(P(i,"value")[0]??[]);if(!l)continue;let c=Q(a);e.push({ref:l,type:re(l),value:a||void 0,...c!==null?{ratings:{voltage_v:c}}:{}})}let o=[],s=P(t,"nets")[0];if(s)for(let i of P(s,"net")){let l=j(P(i,"name")[0]??[]),a=[];for(let c of P(i,"node")){let u=j(P(c,"ref")[0]??[]),d=j(P(c,"pin")[0]??[]);u&&d&&a.push(`${u}.${d}`)}l&&a.length>0&&o.push({name:l,pins:a})}return{parts:e,nets:o}}var Ce=`# Schematic image / PDF \u2192 standard schematic schema: extraction guide
|
|
242
|
+
|
|
243
|
+
You (the vision model) rebuild the netlist from the drawing. The review is only as good as this transcription:
|
|
244
|
+
a wrong or half-finished netlist produces confident, wrong findings. Work ONE PAGE AT A TIME and check after
|
|
245
|
+
every page. Do not try to transcribe the whole schematic in one pass.
|
|
246
|
+
|
|
247
|
+
## Schema
|
|
248
|
+
{
|
|
249
|
+
"mcu": { "family": "stm32f4|stm32f1|esp32|numicro|<other>", "part": "<MPN>" },
|
|
250
|
+
"parts": [ { "ref": "U1", "type": "mcu|cap|res|ind|ic|conn|xtal|led|diode|other", "value": "100nF", "pinDirs": { "3": "out", "5": "in" } } ],
|
|
251
|
+
"nets": [ { "name": "VDD", "pins": ["U1.19", "C1.1"] } ]
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
## Procedure
|
|
255
|
+
1. Look at every page once and note what each page contains. Render pages large enough to read pin numbers;
|
|
256
|
+
zoom into dense areas. If the PDF has a text layer, use it to confirm the spelling of refs, values and net labels.
|
|
257
|
+
2. Start with the page that holds the MCU symbol. Write down its pin table (pin number \u2192 pin name \u2192 net label on
|
|
258
|
+
that pin) exactly as printed. Every MCU pin you use later must come from this table.
|
|
259
|
+
3. For each page, in order:
|
|
260
|
+
a. Add every fitted part on the page to "parts" (skip parts marked NC / DNP / not fitted, and test points).
|
|
261
|
+
b. For EVERY part you just added, put each of its pins into a net. A capacitor or resistor has two pins \u2014
|
|
262
|
+
both must end up in a net. Passive parts are the ones most often forgotten, and most rules depend on them.
|
|
263
|
+
c. A net label that appears on several pages is ONE net: add the new pins to the existing net of that name.
|
|
264
|
+
An unlabelled wire gets a name of your own (N1, N2, \u2026).
|
|
265
|
+
d. Call embedcalc_check_transcription with the whole netlist so far.
|
|
266
|
+
- "problems" not empty \u2192 re-read those pins on the drawing and fix them before going on.
|
|
267
|
+
- "unconnectedParts" / "halfConnectedParts" containing parts of the pages done so far \u2192 you missed their
|
|
268
|
+
connections; go back to the drawing and add them.
|
|
269
|
+
4. After the last page, embedcalc_check_transcription must return ready=true. Check each entry of "singlePinNets"
|
|
270
|
+
against the drawing (a real unconnected pin, or a connection you missed?). Only then call embedcalc_review_schematic.
|
|
271
|
+
5. Tell the user which connections you could not read and left out.
|
|
272
|
+
|
|
273
|
+
## Rules
|
|
274
|
+
- Pins must be written as "ref.pin" (e.g. "U1.19"). Use the pin numbers printed on the drawing \u2014 never invent one.
|
|
275
|
+
If a pin number is unreadable, leave that connection out and say so.
|
|
276
|
+
- A pin belongs to exactly one net, and every ref used in a net must be in "parts".
|
|
277
|
+
- Keep power and ground net names exactly as labelled on the drawing (VDD, VCC, 3V3, GND, VSS, \u2026).
|
|
278
|
+
- Write a 0\u03A9 jumper as a "res" with value "0", and a ferrite bead as an "ind" with value "FERRITE BEAD" (or an FBx ref).
|
|
279
|
+
Nets joined by those parts are treated as one supply rail by the capacitor checks.
|
|
280
|
+
- I2C rules need SDA / SCL in the net name; the reset rule needs NRST / nRESET / RESET / RST.
|
|
281
|
+
- Interface rules are triggered by net names only. Recognised names:
|
|
282
|
+
USB (USB_DP/USB_DM, DP/DM, D+/D-, HSUSB_D+/HSUSB_D-, VBUS), CAN (CANH/CAN_H, CANL/CAN_L),
|
|
283
|
+
RS-485 (RS485_A/RS485_B or DE, RE), SWD/JTAG (SWDIO/SWCLK, TMS/TCK, ICE_DAT/ICE_CLK),
|
|
284
|
+
crystal (XTAL, OSC, HSE, LSE, HXT, LXT, XT1_IN/XT1_OUT, XIN/XOUT).
|
|
285
|
+
A circuit whose nets are named differently is skipped \u2014 check "interfacesDetected" and "skipped" in the result.
|
|
286
|
+
- Fill in mcu.family when you know it (enables the family rules). Omit it otherwise.
|
|
287
|
+
|
|
288
|
+
Optional fields (only when readable \u2014 never guess, omit if unknown):
|
|
289
|
+
- Electrolytic / tantalum capacitor voltage rating: "ratings": {"voltage_v": 16}
|
|
290
|
+
- Resistor power rating: "ratings": {"power_w": 0.25}
|
|
291
|
+
- Supply rail voltage: "voltage_v": 3.3 on the net (from the label or power flag)
|
|
292
|
+
- Polarised parts (diode / LED / electrolytic cap): "pinRoles": {"1": "anode", "2": "cathode"} or positive/negative
|
|
293
|
+
- pinDirs: a "pin": "in|out|bidir|power|passive|nc" map when pin directions are known (output-conflict check).
|
|
294
|
+
Rules that need an omitted field are reported under "skipped" (not a silent pass).
|
|
295
|
+
|
|
296
|
+
## Caution
|
|
297
|
+
Any misreading of the image goes straight into the result. When a KiCad netlist is available,
|
|
298
|
+
passing it through \`parse_kicad\` is more accurate.`;var Ar=new Set(["cap","res","ind","led","diode","xtal"]);function tn(n){let t=K(n),e=new Map,r=0;for(let u of n.nets)for(let d of u.pins){r++;let{ref:b,pin:_}=L(d);e.set(b,(e.get(b)??new Set).add(_))}let o=fe.run({sch:n,graph:t}).violations.filter(u=>u.severity==="error").map(u=>u.evidence),s=n.parts.filter(u=>!e.has(u.ref)).map(u=>u.ref),i=n.parts.filter(u=>Ar.has(u.type)&&e.get(u.ref)?.size===1).map(u=>u.ref),l=n.nets.filter(u=>u.pins.length===1).map(u=>`${u.name} (${u.pins[0]})`),a=o.length===0&&s.length===0&&i.length===0,c=o.length>0?"Fix the problems first: re-read those pins on the drawing. Do not go on to the next page until this list is empty.":s.length+i.length>0?"No contradictions. Parts still listed as unconnected or half-connected need their pins traced on the drawing (or removal if they are marked not fitted). Expected while pages remain; must be empty after the last page.":"Transcription is self-consistent. If every page is done, review each entry of singlePinNets against the drawing, then call embedcalc_review_schematic.";return{ready:a,stats:{parts:n.parts.length,connectedParts:n.parts.length-s.length,nets:n.nets.length,pins:r},problems:o,unconnectedParts:s,halfConnectedParts:i,singlePinNets:l,next:c}}import{readFileSync as $r}from"node:fs";import{inflateRawSync as Nr}from"node:zlib";var Mr="d0cf11e0a1b11ae1";function rn(n){return n.length>=8&&n.subarray(0,8).toString("hex")===Mr}function Tr(n,t){let e=1<<n.readUInt16LE(30),r=1<<n.readUInt16LE(32),o=h=>n.subarray(512+h*e,512+(h+1)*e),s=[];for(let h=0;h<109;h++)s.push(n.readUInt32LE(76+h*4));for(let h=n.readUInt32LE(68),C=n.readUInt32LE(72);C>0&&h<4294967294;C--){let E=o(h);for(let F=0;F<e/4-1;F++)s.push(E.readUInt32LE(F*4));h=E.readUInt32LE(e-4)}let i=[];for(let h of s.slice(0,n.readUInt32LE(44))){let C=o(h);for(let E=0;E<e/4;E++)i.push(C.readUInt32LE(E*4))}let l=(h,C,E)=>{let F=[];for(let M=h,D=0;M<4294967294&&D<C.length+1;M=C[M],D++)F.push(E(M));return Buffer.concat(F)},a=l(n.readUInt32LE(48),i,o),c=[];for(let h=0;h+128<=a.length;h+=128){let C=a.readUInt16LE(h+64);C<2||c.push({name:a.toString("utf16le",h,h+C-2),start:a.readUInt32LE(h+116),size:a.readUInt32LE(h+120)})}let u=c.find(h=>t.includes(h.name));if(!u)throw new Error("xls: no Workbook stream");if(u.size>=n.readUInt32LE(56))return l(u.start,i,o).subarray(0,u.size);let d=[],b=l(n.readUInt32LE(60),i,o);for(let h=0;h<b.length/4;h++)d.push(b.readUInt32LE(h*4));let _=l(c[0].start,i,o);return l(u.start,d,h=>_.subarray(h*r,(h+1)*r)).subarray(0,u.size)}var ie=class{constructor(t,e){this.data=t;this.bounds=e}data;bounds;pos=0;u8(){return this.data[this.pos++]}u16(){let t=this.data.readUInt16LE(this.pos);return this.pos+=2,t}u32(){let t=this.data.readUInt32LE(this.pos);return this.pos+=4,t}str(){let t=this.u16(),e=this.u8(),r=(e&1)!==0,o=e&8?this.u16():0,s=e&4?this.u32():0,i="";for(;t>0;){let l=this.bounds.find(c=>c>this.pos)??this.data.length,a=Math.min(t,Math.floor((l-this.pos)/(r?2:1)));i+=this.data.toString(r?"utf16le":"latin1",this.pos,this.pos+a*(r?2:1)),this.pos+=a*(r?2:1),t-=a,t>0&&(r=(this.u8()&1)!==0)}return this.pos+=o*4+s,i}};function nn(n){let t=n&2?n>>2:(()=>{let e=Buffer.alloc(8);return e.writeUInt32LE((n&4294967292)>>>0,4),e.readDoubleLE(0)})();return n&1?t/100:t}var Re=n=>String(Math.round(n*1e9)/1e9);function on(n){let t=Tr(n,["Workbook","Book"]),e=[],r=[],o=[],s=(a,c,u)=>{(o[a]??=[])[c]=u},i=0,l=!1;for(;i+4<=t.length;){let a=t.readUInt16LE(i),c=t.readUInt16LE(i+2),u=t.subarray(i+4,i+4+c);if(i+=4+c,a===2057){u.readUInt16LE(2)===16&&(l=!0,o=[],r.push(o));continue}if(a===252){let _=[],h=[u],C=u.length;for(;i+4<=t.length&&t.readUInt16LE(i)===60;){let M=t.readUInt16LE(i+2);_.push(C),h.push(t.subarray(i+4,i+4+M)),C+=M,i+=4+M}u=Buffer.concat(h);let E=new ie(u,_);E.pos=8;let F=u.readUInt32LE(4);for(let M=0;M<F&&E.pos<u.length;M++)e.push(E.str());continue}if(!l)continue;if(a===10){l=!1;continue}let d=()=>u.readUInt16LE(0),b=()=>u.readUInt16LE(2);if(a===253)s(d(),b(),e[u.readUInt32LE(6)]??"");else if(a===516){let _=new ie(u,[]);_.pos=6,s(d(),b(),_.str())}else if(a===515)s(d(),b(),Re(u.readDoubleLE(6)));else if(a===638)s(d(),b(),Re(nn(u.readInt32LE(6))));else if(a===189){let _=b(),h=(c-6)/6;for(let C=0;C<h;C++)s(d(),_+C,Re(nn(u.readInt32LE(4+C*6+2))))}}return r.map(a=>Array.from(a,c=>Array.from(c??[],u=>u??"")))}var Pr=/(^|[\s(\/])(NC|DNP|DNI|NF|NOT ?FITTED)($|[\s)\/])/i,kr=/(HEADER|CONN|SOCKET|RECEPTACLE|USB|FPC|JACK|TERMINAL|PM254|PZ254|RJ45|RJ11)/i,Ir=/^\s*(RED|GREEN|YELLOW|BLUE|WHITE|ORANGE|AMBER)\b|\bLED\b/i,Dr=/^\s*[\d.]+\s*[pnuµ]F?(?=$|[\s\/,_])/i;function Ee(n){if(/^ICE[A-Z]/i.test(n))return Ee(n.slice(3));let t=re(n);if(t!=="other")return t;let e=/^[A-Za-z]+/.exec(n)?.[0]??"";if(e.length<2)return t;if(/(CON|CONN|CN|HDR|USB)$|^JP$/i.test(e))return"conn";if(/^(PD|ZD)$/i.test(e))return"diode";let r=re(e[0]+"1");return r==="xtal"?"other":r}var zr=/^(\S+)\s+(\S+)\s+-?\d+\s+-?\d+\s+-?\d+\.\d+\s+[UG]\s/,Br=/^\S+\.\S+(\s+\S+\.\S+)*\s*$/,Or=/^(\S+)\s+(\S+)\s*$/;function sn(n){if(!/^!PADS-/.test(n))throw new Error("not a PADS ASCII file (expected a first line starting with !PADS-)");let t=[],e=[],r=new Map,o="",s="";for(let a of n.split(/\r?\n/)){if(a.startsWith("*")&&!a.startsWith("*REMARK*")){let c=/^\*(\w+)\*\s*(\S*)/.exec(a);o=c?c[1]:"",s=o==="SIGNAL"&&c?c[2]:"";continue}if(o==="PART"){let c=zr.exec(a);if(c)t.push({ref:c[1],type:Ee(c[1])});else{let u=Or.exec(a);u&&e.push({ref:u[1],type:Ee(u[1])})}}else if(o==="SIGNAL"&&s&&Br.test(a)){let c=r.get(s)??new Set;for(let u of a.trim().split(/\s+/))c.add(u);r.set(s,c)}}let i=[...r].map(([a,c])=>({name:a,pins:[...c]})),l=new Set(i.flatMap(a=>a.pins.map(c=>c.slice(0,c.indexOf(".")))));return{parts:(t.length>0?t:e).filter(a=>l.has(a.ref)),nets:i}}function an(n){let t=$r(n),e=/\.xlsx$/i.test(n)?[Ur(t)]:rn(t)?on(t):[Lr(t.toString("latin1"))],r=d=>/^(reference|designator|ref\.?\s*des)/i.test(d.trim()),o=e.find(d=>d.some(b=>b.some(r)));if(!o)throw new Error("BOM: no header row with a Reference/Designator column");let s=o.findIndex(d=>d.some(r)),i=o[s].map(d=>d.trim().toLowerCase()),l=i.findIndex(r),a=["value","part","comment","description"].map(d=>i.indexOf(d)).filter(d=>d>=0);if(a.length===0)throw new Error("BOM: no Value/Part/Comment column");let c=new Map,u="";for(let d of o.slice(s+1)){let b=a.map(h=>(d[h]??"").trim()).find(Boolean)??"",_=!b&&d.every((h,C)=>C===l||!(h??"").trim());b&&(u=b);for(let h of(d[l]??"").split(/[,;\s]+/).filter(Boolean))c.set(h,b||(_?u:""))}return c}function cn(n,t){let e=n.parts.filter(c=>!t.has(c.ref)||Pr.test(t.get(c.ref))).map(c=>c.ref),r=new Set(e),o=n.parts.filter(c=>!r.has(c.ref)).map(c=>{let u=t.get(c.ref)||c.value,d=u&&kr.test(u)?"conn":u&&/(ESD|TVS)/i.test(u)?"diode":u&&Ir.test(u)?"led":(c.type==="other"||c.type==="diode")&&u&&Dr.test(u)?"cap":c.type;return{...c,value:u,type:d}}),s=[],i=n.nets.map(c=>({...c,pins:c.pins.filter(u=>!r.has(u.slice(0,u.indexOf(".")))),before:c.pins.length})).filter(c=>c.pins.length===1&&c.before>1?(s.push(c.name),!1):c.pins.length>0).map(({before:c,...u})=>u),l=new Set(n.parts.map(c=>c.ref)),a=[...t.keys()].filter(c=>!l.has(c));return{schematic:{...n,parts:o,nets:i},notFitted:e,bomOnly:a,netsOnlyToNotFitted:s}}function Lr(n){return n.split(/\r?\n/).filter(t=>t.trim()).map(t=>{let e=[],r="",o=!1,s=t.includes(" ")?" ":",";for(let i of t)i==='"'?o=!o:i===s&&!o?(e.push(r),r=""):r+=i;return e.push(r),e})}function Vr(n){let t=n.lastIndexOf(Buffer.from([80,75,5,6]));if(t<0)throw new Error("BOM: not a valid xlsx (zip) file");let e=new Map,r=n.readUInt32LE(t+16);for(let o=n.readUInt16LE(t+10);o>0;o--){let s=n.readUInt16LE(r+10),i=n.readUInt32LE(r+20),l=n.readUInt16LE(r+28),a=n.readUInt16LE(r+30),c=n.readUInt16LE(r+32),u=n.readUInt32LE(r+42),d=n.toString("utf8",r+46,r+46+l),b=u+30+n.readUInt16LE(u+26)+n.readUInt16LE(u+28),_=n.subarray(b,b+i);e.set(d,s===8?Nr(_):_),r+=46+l+a+c}return e}var Fe=n=>n.replace(/<[^>]+>/g,"").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&");function Ur(n){let t=Vr(n),e=[...(t.get("xl/sharedStrings.xml")?.toString("utf8")??"").matchAll(/<si>([\s\S]*?)<\/si>/g)].map(s=>[...s[1].matchAll(/<t[^>]*>([\s\S]*?)<\/t>/g)].map(i=>Fe(i[1])).join("")),r=[...t.keys()].filter(s=>/^xl\/worksheets\/sheet\d+\.xml$/.test(s)).sort()[0];if(!r)throw new Error("BOM: xlsx has no worksheet");return[...t.get(r).toString("utf8").matchAll(/<row[^>]*>([\s\S]*?)<\/row>/g)].map(s=>{let i=[];for(let l of s[1].matchAll(/<c r="([A-Z]+)\d+"([^>]*?)(?:\/>|>([\s\S]*?)<\/c>)/g)){let a=[...l[1]].reduce((d,b)=>d*26+b.charCodeAt(0)-64,0)-1,c=/<v>([\s\S]*?)<\/v>/.exec(l[3]??""),u=/<is>([\s\S]*?)<\/is>/.exec(l[3]??"");i[a]=c?/t="s"/.test(l[2])?e[Number(c[1])]??"":Fe(c[1]):u?Fe(u[1]):""}return Array.from(i,l=>l??"")})}function un(n){n.registerTool("embedcalc_review_schematic",{title:"Review Schematic",description:`Reviews a schematic (in the standard schema) against deterministic embedded-board rules: decoupling, reset, I2C pull-ups, power, interface wiring and MCU-family requirements.
|
|
299
|
+
|
|
300
|
+
For a schematic image or PDF, call embedcalc_get_extraction_guide first and follow its page-by-page procedure (embedcalc_check_transcription after each page); pass the netlist here only once that check returns ready=true.
|
|
301
|
+
For a KiCad netlist or a PADS PCB file (+ BOM), pass the output of embedcalc_parse_kicad / embedcalc_parse_pads with mcu.family added \u2014 preferred over images.
|
|
302
|
+
|
|
303
|
+
Returns { violations[], checked[], skipped[], interfacesDetected[] }. "skipped" lists rules that could not run on the given data \u2014 read it to know the coverage; a clean result only covers the rules in "checked". interfacesDetected lists the interface packs (usb/can/rs485/swd/crystal) recognised from net names.
|
|
304
|
+
|
|
305
|
+
Known limits \u2014 tell the user when they apply: a rail with no capacitor is reported twice (decoupling error + bulk warning); a ferrite bead whose value is only a part number is not recognised, so each side is reported as its own rail; decoupling/bulk run only on rails that feed an ic/mcu pin; on-board debugger detection relies on ICE-prefixed references (Nuvoton Nu-Link). A violation means the netlist matches the rule, not that the board is wrong \u2014 evaluation boards often leave ESD or I2C pull-ups to the attached board on purpose.`,inputSchema:{schematic:pe},annotations:x},async({schematic:t})=>{try{let e=Yt(t),r=e.interfacesDetected.length>0?`Interfaces detected: ${e.interfacesDetected.join(", ")}
|
|
306
|
+
`:"";return y(r+JSON.stringify(e,null,2),e)}catch(e){return v(`review failed: ${e.message}`)}}),n.registerTool("embedcalc_parse_kicad",{title:"Parse KiCad Netlist",description:`Converts a KiCad "netlist export" file (.net, S-expression) into the standard schematic schema.
|
|
307
|
+
|
|
308
|
+
Part types are inferred from the reference prefix (U\u2192ic, C\u2192cap, R\u2192res \u2026) and mcu.family is left empty.
|
|
309
|
+
For family rules, add mcu.family (stm32f4|stm32f1|esp32|numicro) to the returned schema before passing it to embedcalc_review_schematic.
|
|
310
|
+
|
|
311
|
+
Input: path (absolute path of the netlist file). Returns the standard schematic schema.`,inputSchema:{path:oe.string().describe("Path of the KiCad netlist export file (.net)")},annotations:x},async({path:t})=>{try{let e=ln(t,"utf8"),r=en(e);return y(JSON.stringify(r,null,2),r)}catch(e){return v(`parse_kicad failed: ${e.message}`)}}),n.registerTool("embedcalc_parse_pads",{title:"Parse PADS PCB + BOM",description:`Converts a PADS PowerPCB ASCII file (.asc, first line "!PADS-POWERPCB\u2026") into the standard schematic schema, using the board's real connectivity \u2014 no transcription from images.
|
|
312
|
+
|
|
313
|
+
The .asc file has no component values, so pass the BOM (bomPath: .xlsx, legacy .xls, or a comma/tab-separated text BOM with a Reference/Designator column and a Value/Part/Comment column; NC/DNP values count as not fitted). Parts on the PCB but missing from the BOM are treated as not fitted and removed (listed in notFitted \u2014 check it: connectors or headers left out of a BOM show up there).
|
|
314
|
+
Part types are inferred from the reference prefix (U\u2192ic, C\u2192cap, R\u2192res, L\u2192ind \u2026) and the BOM value; mcu.family is left empty \u2014 add it (stm32f4|stm32f1|esp32|numicro) before calling embedcalc_review_schematic.
|
|
315
|
+
Without a BOM, resistor/inductor values are unknown, so 0\u03A9 links and beads are not followed and parts behind them can be missed. Parts with no connection in the PCB file (mounting holes, fiducials) are dropped.
|
|
316
|
+
|
|
317
|
+
Returns { schematic, notFitted[], bomOnly[], netsOnlyToNotFitted[] }. netsOnlyToNotFitted = nets dropped because only one pin was left after removing not-fitted parts (e.g. an MCU pin that only went to an unfitted header). Net names are the PCB's names, which can differ from the schematic labels.`,inputSchema:{path:oe.string().describe("Absolute path of the PADS ASCII file (.asc)"),bomPath:oe.string().optional().describe("Absolute path of the BOM (.xlsx, .xls or .csv/.txt). Strongly recommended: without it values and fitted/not-fitted status are unknown")},annotations:x},async({path:t,bomPath:e})=>{try{let r=sn(ln(t,"latin1")),o=e?cn(r,an(e)):{schematic:r,notFitted:[],bomOnly:[],netsOnlyToNotFitted:[]};return y(JSON.stringify(o,null,2),o)}catch(r){return v(`parse_pads failed: ${r.message}`)}}),n.registerTool("embedcalc_get_extraction_guide",{title:"Get Image Extraction Guide",description:"Returns the instructions and schema template for transcribing a schematic image or PDF into the standard schematic schema. First step of an image review.",inputSchema:{},annotations:x},async()=>y(Ce,{guide:Ce})),n.registerTool("embedcalc_check_transcription",{title:"Check Schematic Transcription",description:`Checks a schematic transcription in progress \u2014 call it after every page while transcribing an image or PDF (see embedcalc_get_extraction_guide), with the whole netlist so far. It does not review the design.
|
|
318
|
+
|
|
319
|
+
Returns { ready, stats, problems[], unconnectedParts[], halfConnectedParts[], singlePinNets[], next }.
|
|
320
|
+
problems = contradictions that must be fixed (a pin in two nets, a ref missing from parts). unconnectedParts / halfConnectedParts = parts whose connections were not transcribed yet. Call embedcalc_review_schematic only when ready is true.`,inputSchema:{schematic:pe},annotations:x},async({schematic:t})=>{try{let e=tn(t);return y(JSON.stringify(e,null,2),e)}catch(e){return v(`check_transcription failed: ${e.message}`)}}),n.registerTool("embedcalc_list_schematic_rules",{title:"List Schematic Rules",description:"Returns the schematic review rule catalogue. Pass family (stm32f4|stm32f1|esp32|numicro) to include that family's rules.",inputSchema:{family:oe.string().optional().describe("stm32f4 | stm32f1 | esp32 | numicro")},annotations:x},async({family:t})=>{let e=[...J,...ee,...ne,...Z(t)].map(r=>({id:r.id,severity:r.severity,description:r.description}));return y(JSON.stringify({rules:e},null,2),{rules:e})})}var{version:Wr}=Gr(import.meta.url)("../package.json"),I=new jr({name:"embedcalc-mcp-server",version:Wr});Be(I);st(I);dt(I);zt(I);Bt(I);un(I);async function qr(){let n=new Hr;await I.connect(n),console.error("embedcalc-mcp-server running via stdio")}qr().catch(n=>{console.error("Server error:",n),process.exit(1)});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "embedcalc-mcp",
|
|
3
|
+
"version": "0.6.6",
|
|
4
|
+
"mcpName": "io.github.ethan-hub26/embedcalc-mcp",
|
|
5
|
+
"description": "EmbedCalc embedded-engineering calculators, code generators & schematic review as MCP tools — CRC→C, CAN/UART/I2C bit timing, struct layout, Q-format, IEEE-754, and KiCad/PADS schematic review (decoupling, reset, I2C pull-up, family rules). https://www.embedcalc.com",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"embedcalc-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
17
|
+
"homepage": "https://www.embedcalc.com",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"embedded",
|
|
22
|
+
"firmware",
|
|
23
|
+
"crc",
|
|
24
|
+
"can-bus",
|
|
25
|
+
"bit-timing",
|
|
26
|
+
"struct-alignment",
|
|
27
|
+
"fixed-point",
|
|
28
|
+
"ieee754",
|
|
29
|
+
"stm32",
|
|
30
|
+
"calculator",
|
|
31
|
+
"schematic-review",
|
|
32
|
+
"kicad",
|
|
33
|
+
"netlist",
|
|
34
|
+
"erc"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --target=node18 --packages=external --minify --legal-comments=none --outfile=dist/index.js",
|
|
38
|
+
"start": "node dist/index.js",
|
|
39
|
+
"test": "node --import tsx --test \"tests/*.test.ts\"",
|
|
40
|
+
"prepublishOnly": "npm run build && npm test"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
47
|
+
"zod": "^3.23.8"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.10.0",
|
|
51
|
+
"esbuild": "^0.28.1",
|
|
52
|
+
"tsx": "^4.19.2",
|
|
53
|
+
"typescript": "^5.7.2"
|
|
54
|
+
}
|
|
55
|
+
}
|