diff --git a/README.md b/README.md index 4a2795b..a5b0a28 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,12 @@ them on source ports: | `isUsingInternalPulldown: true` | `is_using_internal_pulldown: true` | Input `pull = "Pull Down"` | | `activeCapability: "i2c_sda"` / `"i2c_scl"` | `is_configured_for_i2c_sda` / `is_configured_for_i2c_scl` | I2C0 SDA/SCL on physical RGE pins 3/19 | -`isGpio`, `supports_*`, and `can_use_*` describe capabilities; they do not activate -an instance. A GPIO requires exactly one input/output flag and no bidirectional -declaration. I2C accepts open-drain and bidirectional declarations, but rejects +`isGpio`, `isBidirectional`, `supports_*`, and `can_use_*` describe capabilities; +they do not activate an instance. Bidirectionality can coexist with an explicit +input/output choice; a GPIO still requires exactly one input/output flag. +Unselected SWD and connected LF crystal pins retain their native ownership when +bidirectional capability metadata is present. I2C accepts open-drain and +bidirectional declarations, but rejects a conflicting GPIO direction. Selected electrical modes must agree with the native driver. Tri-state, open-collector/emitter, open-drain GPIO, SPI and UART are currently unsupported and fail instead of being dropped. @@ -45,10 +48,11 @@ Output names are deterministic (`CONFIG_U1_PIN5`, `CONFIG_U1_I2C0`), and pins ar sorted by physical identity. No behavior is inferred from signal or net names. Unconnected capability-only ports remain unallocated. Connected physical GPIO pins without a supported function are reported together, even if all their -attributes (including `is_gpio`) are missing. Errors identify the MCU name/part number and physical pin labels (for example, -`U1 pin 4 (DIO11)`), without generated record IDs. They explain which existing -TSX attributes to set and distinguish datasheet capabilities from the board -function that must be selected. +attributes (including `is_gpio`) are missing. Missing-direction errors contain +one short line per pin with the component name, physical pin number, label and +problem, for example `- U1 pin 4 (DIO11): GPIO direction is missing`. +These diagnostics omit manufacturer part numbers, generated record IDs and TSX +setup instructions. An MCU with no source ports, or a trace referencing a missing source-port record, fails before any output is available. An incomplete I2C selection reports the selected endpoints and asks for both SDA and SCL declarations. Passives and other diff --git a/lib/cc2340/derive-requests.ts b/lib/cc2340/derive-requests.ts index 71a560e..ecbf984 100644 --- a/lib/cc2340/derive-requests.ts +++ b/lib/cc2340/derive-requests.ts @@ -68,10 +68,7 @@ export function deriveCc2340Requests( ) const isSda = port.is_configured_for_i2c_sda === true const isScl = port.is_configured_for_i2c_scl === true - const selectedGpio = - port.is_input === true || - port.is_output === true || - port.is_bidirectional === true + const selectedGpio = port.is_input === true || port.is_output === true const selectedElectricalMode = selectedElectricalModes.some( (attribute) => port[attribute] === true, ) @@ -149,12 +146,11 @@ export function deriveCc2340Requests( } if (!connected && !selectedGpio && !selectedElectricalMode) continue if ( - port.is_bidirectional || port.is_input === port.is_output || (!port.is_input && !port.is_output) ) { missing.push( - `${identity}: ${port.is_bidirectional ? "bidirectional GPIO is unsupported" : port.is_input && port.is_output ? "both isInput and isOutput are selected" : "no GPIO direction or supported peripheral selected"}`, + `${identity}: ${port.is_input && port.is_output ? "both isInput and isOutput are selected" : "GPIO direction is missing"}`, ) continue } @@ -174,9 +170,7 @@ export function deriveCc2340Requests( resolved.gpios.push({ request, pin: physical.pin }) } if (missing.length) - throw new Error( - `Unresolved CC2340 pin configuration for ${componentIdentity}:\n${missing.map((problem) => `- ${problem}`).join("\n")}\nUpdate ${component.name}'s TSX pinAttributes with the intended function for each listed pin: set exactly one of isInput: true or isOutput: true for GPIO, or activeCapability: "i2c_sda" / "i2c_scl" for I2C.\nDatasheet capabilities such as isGpio describe what a pin supports; they do not select how this board uses it.`, - ) + throw new Error(missing.map((problem) => `- ${problem}`).join("\n")) if (sdaPorts.length || sclPorts.length) { const sda = sdaPorts[0] const scl = sclPorts[0] diff --git a/lib/cc2340/resolve-port.ts b/lib/cc2340/resolve-port.ts index 3f7bcfd..2ff724b 100644 --- a/lib/cc2340/resolve-port.ts +++ b/lib/cc2340/resolve-port.ts @@ -105,7 +105,7 @@ export function checkCc2340Function( // TI's I2CLPF3 driver configures both pins with GPIO_CFG_OUT_OD_PU. // Open drain is compatible with I2C, but not our standard GPIO output mode. if (role === "gpio") { - forbidden.push("is_using_open_drain", "is_bidirectional") + forbidden.push("is_using_open_drain") if (typeof request !== "string") forbidden.push(request.direction === "input" ? "is_output" : "is_input") } else { diff --git a/tests/cc2340-conversion.test.ts b/tests/cc2340-conversion.test.ts index b53c57f..30ddbe3 100644 --- a/tests/cc2340-conversion.test.ts +++ b/tests/cc2340-conversion.test.ts @@ -70,6 +70,20 @@ test("actual board emits three GPIOs and the exact I2C identifiers", () => { ) }) +test("explicit requests preserve GPIO and I2C settings with bidirectional capabilities", () => { + const enriched = circuit.map((element) => + element.type === "source_port" && + element.source_component_id === pedometerOptions.source_component_id + ? { ...element, is_bidirectional: true } + : element, + ) + const before = structuredClone(enriched) + expect( + convertCircuitJsonToSysConfig(enriched, pedometerOptions).getString(), + ).toBe(output().getString()) + expect(enriched).toEqual(before) +}) + test("100000 bits/s becomes numeric 100 kbit/s through serialization and re-parsing", async () => { const options = structuredClone(pedometerOptions) expect(options.i2c?.max_bit_rate).toBe(100000) diff --git a/tests/derived-cc2340-lf-crystal.test.ts b/tests/derived-cc2340-lf-crystal.test.ts index e12095e..c089af5 100644 --- a/tests/derived-cc2340-lf-crystal.test.ts +++ b/tests/derived-cc2340-lf-crystal.test.ts @@ -97,6 +97,16 @@ test("direct traces and swapped interchangeable crystal terminals resolve the sa ) }) +test("bidirectional capabilities do not claim connected LF crystal pins", () => { + const circuitJson = crystalCircuit() + const baseline = convertCircuitJsonToSysConfig(circuitJson).getString() + for (const pin_number of [4, 14, 15]) + mcuPort(circuitJson, pin_number).is_bidirectional = true + const before = structuredClone(circuitJson) + expect(convertCircuitJsonToSysConfig(circuitJson).getString()).toBe(baseline) + expect(circuitJson).toEqual(before) +}) + test("missing crystal terminals and incomplete wiring remain unresolved", () => { for (const source_trace_id of [ "crystal_p_trace", @@ -202,7 +212,6 @@ test("multiple connected crystals are ambiguous", () => { for (const declarations of [ { is_output: true }, { is_input: true }, - { is_bidirectional: true }, { is_configured_for_i2c_sda: true }, { is_using_internal_pullup: true }, { is_using_open_drain: true }, diff --git a/tests/derived-cc2340.test.ts b/tests/derived-cc2340.test.ts index 9ff58be..89ccbbb 100644 --- a/tests/derived-cc2340.test.ts +++ b/tests/derived-cc2340.test.ts @@ -131,13 +131,12 @@ test("an MCU with no pin records gets an actionable component error", () => { }) test("pins with no attributes report every connected physical pin", () => { - expect(() => - convertCircuitJsonToSysConfig( - connected([{ pin_number: 5 }, { pin_number: 6 }]), - ), - ).toThrow( - /U1 \(CC2340R52E0RGER\)[\s\S]*U1 pin 5 \(signal_0\)[\s\S]*U1 pin 6 \(signal_1\)[\s\S]*isInput: true or isOutput: true/, - ) + expect( + conversionError(connected([{ pin_number: 5 }, { pin_number: 6 }])), + ).toMatchInlineSnapshot(` + "- U1 pin 5 (signal_0): GPIO direction is missing + - U1 pin 6 (signal_1): GPIO direction is missing" + `) }) test("one configured pin cannot hide another pin's missing attributes", () => { @@ -215,6 +214,7 @@ test("unused capabilities do not activate GPIO or I2C", () => { { pin_number: 6, is_gpio: true, + is_bidirectional: true, supports_i2c_sda: true, can_use_open_drain: true, }, @@ -227,8 +227,7 @@ test("unused capabilities do not activate GPIO or I2C", () => { for (const flags of [ { is_input: true, is_output: true }, - { is_bidirectional: true }, - { is_output: true, is_bidirectional: true }, + { is_input: true, is_output: true, is_bidirectional: true }, { is_output: true, is_using_open_drain: true }, { is_output: true, is_using_tri_state: true }, { is_output: true, is_using_open_collector: true }, @@ -278,6 +277,39 @@ test("unpaired, duplicate, conflicting and unsupported I2C routes fail", () => { expect(() => convertCircuitJsonToSysConfig(circuit(ports))).toThrow() }) +test("bidirectional capabilities preserve selected GPIO directions and SWD ownership", () => { + const input = connected([ + { pin_number: 4, name: "DIO11", is_output: true }, + { pin_number: 5, name: "DIO12", is_input: true }, + { pin_number: 7, name: "DIO16_SWDIO", is_gpio: true }, + { pin_number: 8, name: "DIO17_SWDCK", is_gpio: true }, + ]) + const baseline = convertCircuitJsonToSysConfig(input).getString() + const enriched = input.map((element) => + element.type === "source_port" + ? { ...element, is_bidirectional: true } + : element, + ) + const before = structuredClone(enriched) + expect(convertCircuitJsonToSysConfig(enriched).getString()).toBe(baseline) + expect(enriched).toEqual(before) +}) + +test("a connected bidirectional capability still requires a board direction", () => { + const input = connected([ + { pin_number: 4, name: "DIO11", is_output: true }, + { pin_number: 5, name: "DIO12", is_bidirectional: true, is_gpio: true }, + ]) + expect(conversionError(input)).toMatchInlineSnapshot( + `"- U1 pin 5 (DIO12): GPIO direction is missing"`, + ) + const converter = new CircuitJsonToSysConfigConverter(input) + converter.step() + expect(() => converter.step()).toThrow(/U1 pin 5/) + expect(converter.finished).toBe(false) + expect(() => converter.getOutput()).toThrow("must finish") +}) + test("SDK debug ownership and LF crystal conflicts are explicit", () => { const debug = convertCircuitJsonToSysConfig( connected([ @@ -368,7 +400,7 @@ test("three stages snapshot inputs, stay deterministic and expose no partial out test("the unmodified pedometer cannot acquire firmware choices from its name", () => { expect(() => convertCircuitJsonToSysConfig(pedometer)).toThrow( - /Unresolved CC2340 pin configuration/, + /GPIO direction is missing/, ) }) @@ -446,11 +478,8 @@ test("pin errors use circuit labels and stay unchanged when generated IDs change const message = conversionError(input) expect(conversionError(renamed)).toBe(message) expect(message).toMatchInlineSnapshot(` - "Unresolved CC2340 pin configuration for U1 (CC2340R52E0RGER): - - U1 pin 4 (DIO11): no GPIO direction or supported peripheral selected - - U1 pin 6 (DIO13): no GPIO direction or supported peripheral selected - Update U1's TSX pinAttributes with the intended function for each listed pin: set exactly one of isInput: true or isOutput: true for GPIO, or activeCapability: "i2c_sda" / "i2c_scl" for I2C. - Datasheet capabilities such as isGpio describe what a pin supports; they do not select how this board uses it." + "- U1 pin 4 (DIO11): GPIO direction is missing + - U1 pin 6 (DIO13): GPIO direction is missing" `) expect(message).not.toMatch( /source_component_123|source_trace_456|mcu|port_0|port_1/, diff --git a/tests/fixtures/derived-cc2340/README.md b/tests/fixtures/derived-cc2340/README.md index 3bc39e3..e4b187a 100644 --- a/tests/fixtures/derived-cc2340/README.md +++ b/tests/fixtures/derived-cc2340/README.md @@ -5,6 +5,9 @@ firmware application preset. It declares output pin 5, input pin 6 with an internal pull-up, and I2C SDA/SCL pins 3/19 using the existing pin attributes. Physical identities use the converter's independently validated RGE target. No startup level, interrupt, bitrate, LF clock or RTOS choice is declared. +The MCU ports also carry bidirectional capability flags, as published for +CC2340R52E0RGER. These coexist with the selected GPIO/I2C functions and do not +select a function themselves. `bun run validate:cc2340` converts it without request options, runs TI SysConfig 1.28.1+4785 against official SimpleLink F3 SDK tag `lpf3-9.21.00.36_LTS`, and diff --git a/tests/fixtures/derived-cc2340/circuit.json b/tests/fixtures/derived-cc2340/circuit.json index 60bf050..8144973 100644 --- a/tests/fixtures/derived-cc2340/circuit.json +++ b/tests/fixtures/derived-cc2340/circuit.json @@ -13,7 +13,8 @@ "name": "SDA", "pin_number": 3, "is_configured_for_i2c_sda": true, - "is_using_open_drain": true + "is_using_open_drain": true, + "is_bidirectional": true }, { "type": "source_port", @@ -23,7 +24,8 @@ "pin_number": 5, "is_gpio": true, "is_output": true, - "is_using_push_pull": true + "is_using_push_pull": true, + "is_bidirectional": true }, { "type": "source_port", @@ -33,7 +35,8 @@ "pin_number": 6, "is_gpio": true, "is_input": true, - "is_using_internal_pullup": true + "is_using_internal_pullup": true, + "is_bidirectional": true }, { "type": "source_port", @@ -42,6 +45,7 @@ "name": "SCL", "pin_number": 19, "is_configured_for_i2c_scl": true, - "is_using_open_drain": true + "is_using_open_drain": true, + "is_bidirectional": true } ] diff --git a/tests/fixtures/derived-cc2340/lf-crystal.circuit.json b/tests/fixtures/derived-cc2340/lf-crystal.circuit.json index 0e2dce9..b8210d3 100644 --- a/tests/fixtures/derived-cc2340/lf-crystal.circuit.json +++ b/tests/fixtures/derived-cc2340/lf-crystal.circuit.json @@ -12,7 +12,8 @@ "source_component_id": "mcu", "name": "DIO11", "pin_number": 4, - "is_output": true + "is_output": true, + "is_bidirectional": true }, { "type": "source_port", @@ -20,7 +21,8 @@ "source_component_id": "mcu", "name": "DIO3_X32P", "pin_number": 14, - "is_gpio": true + "is_gpio": true, + "is_bidirectional": true }, { "type": "source_port", @@ -28,7 +30,8 @@ "source_component_id": "mcu", "name": "DIO4_X32N", "pin_number": 15, - "is_gpio": true + "is_gpio": true, + "is_bidirectional": true }, { "type": "source_component",