200 lines
8.7 KiB
TypeScript
200 lines
8.7 KiB
TypeScript
/**
|
|
* boardPinToNumber — the bridge between a board element's PAD NAMES and the
|
|
* GPIO numbers the simulators speak.
|
|
*
|
|
* A board whose kind has no branch here falls through to `return null`, and a
|
|
* null pin means every wire to that header silently connects to nothing: the
|
|
* part sits on the canvas, the sketch reads a dead pin, and nothing reports an
|
|
* error. That is what happened to the M5 Cardputer, whose EXT header and Grove
|
|
* Port A were unreachable even though the element drew them.
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
import { boardPinToNumber } from '../utils/boardPinMapping';
|
|
|
|
describe('boardPinToNumber — M5 Cardputer ADV header', () => {
|
|
// The pad names CardputerElement.ts renders on its wireable bottom row.
|
|
const GPIO_NAMES = ['3', '4', '5', '6', '8', '9', '13', '14', '15', '39', '40', '2', '1'];
|
|
|
|
it('maps every GPIO pad to its own number', () => {
|
|
for (const name of GPIO_NAMES) {
|
|
expect(boardPinToNumber('cardputer-adv', name)).toBe(Number(name));
|
|
}
|
|
});
|
|
|
|
it('leaves no pad unmapped', () => {
|
|
const unmapped = GPIO_NAMES.filter((n) => boardPinToNumber('cardputer-adv', n) === null);
|
|
expect(unmapped).toEqual([]);
|
|
});
|
|
|
|
it('maps the power pads to -1, not to a GPIO', () => {
|
|
expect(boardPinToNumber('cardputer-adv', '5V')).toBe(-1);
|
|
expect(boardPinToNumber('cardputer-adv', 'GND')).toBe(-1);
|
|
});
|
|
|
|
it('accepts G40, which the classic ESP32 range would have rejected', () => {
|
|
// The shared esp32 branch clamps at 39; the S3 has 48 GPIOs and this board
|
|
// exposes 40 on its header.
|
|
expect(boardPinToNumber('cardputer-adv', '40')).toBe(40);
|
|
});
|
|
|
|
it('rejects a pad that is not a pin', () => {
|
|
expect(boardPinToNumber('cardputer-adv', 'NOPE')).toBeNull();
|
|
expect(boardPinToNumber('cardputer-adv', '99')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('m5stack-core header pins', () => {
|
|
// The Core's pads sat unmapped for months: the kind does not start with
|
|
// 'esp32', so it fell through every branch and each wire from the M-Bus
|
|
// header resolved to null — a part wired to the header never saw a signal,
|
|
// silently. These pin the dedicated branch.
|
|
const CORE_GPIO = ['21', '22', '23', '19', '18', '3', '1', '16', '17', '2', '5', '25', '26', '35', '36', '12', '13', '15', '0', '34'];
|
|
|
|
it('maps every M-Bus GPIO pad to its number', () => {
|
|
for (const name of CORE_GPIO) {
|
|
expect(boardPinToNumber('m5stack-core', name)).toBe(Number(name));
|
|
}
|
|
});
|
|
|
|
it('maps the power pads (including BAT) to -1', () => {
|
|
for (const name of ['5V', 'GND', '3V3', 'BAT']) {
|
|
expect(boardPinToNumber('m5stack-core', name)).toBe(-1);
|
|
}
|
|
});
|
|
|
|
it('rejects out-of-range and non-pins', () => {
|
|
expect(boardPinToNumber('m5stack-core', '40')).toBeNull(); // classic tops out at 39
|
|
expect(boardPinToNumber('m5stack-core', 'NOPE')).toBeNull();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* ESP32-family pads — no pad may resolve to null, and no supply pad may
|
|
* resolve to a GPIO.
|
|
*
|
|
* Two shapes of this bug shipped at once. `parseInt('3V', 10)` is 3, so the
|
|
* Wemos Lolin32 Lite's supply pad resolved to GPIO3 and the XIAO's 3V3/5V pads
|
|
* to GPIO3/GPIO5 — a wire to the rail drove a real pin. And a pad the mapper
|
|
* cannot name returns null, which means the wire connects to nothing at all:
|
|
* the DevKit V1's RX0/TX0 (the only route to GPIO1 and GPIO3, with no numeric
|
|
* twin), the Nano ESP32's RX0/TX1 (its map named them D0/D1, pads that do not
|
|
* exist on the element), and the S3 DevKitC's 40..48, rejected by a GPIO
|
|
* ceiling of 39 shared with the classic ESP32.
|
|
*
|
|
* Expected values cross-checked against the `target` field of the upstream
|
|
* wokwi-boards board.json for each board.
|
|
*
|
|
* The pad lists are copied verbatim from Esp32Element.ts (BOARD_CONFIGS) —
|
|
* same convention as pin-inspector-layout.test.ts, because vitest runs in the
|
|
* `node` environment and that module calls customElements.define at import.
|
|
* If a board's pads change, refresh the copy.
|
|
*/
|
|
const ESP32_FAMILY_PADS: Record<string, string[]> = {
|
|
'esp32': [
|
|
'EN', 'VN', 'VP', '34', '35', '32', '33', '25', '26', '27', '14', '12', '13', 'GND',
|
|
'VIN', '3V3', 'GND2', '15', '2', '4', 'RX2', '16', 'TX2', '17', '5', '18', '19',
|
|
'21', 'RX0', 'TX0', '22', '23',
|
|
],
|
|
'esp32-s3': [
|
|
'3V3.1', '3V3.2', 'RST', '4', '5', '6', '7', '15', '16', '17', '18', '8', '3', '46',
|
|
'9', '10', '11', '12', '13', '14', '5V', 'GND.1', 'GND.2', 'TX', 'RX', '1', '2',
|
|
'42', '41', '40', '39', '38', '37', '36', '35', '0', '45', '48', '47', '21', '20',
|
|
'19', 'GND.3', 'GND.4',
|
|
],
|
|
'esp32-c3': [
|
|
'GND.1', '3V3.1', '3V3.2', '2', '3', 'GND.2', 'RST', 'GND.3', '0', '1', '10', 'GND.4',
|
|
'5V.1', '5V.2', 'GND.5', 'GND.6', '19', '18', 'GND.7', '4', '5', '6', '7', 'GND.8',
|
|
'8', '9', 'GND.9', 'RX', 'TX', 'GND.10',
|
|
],
|
|
'esp32-devkit-c-v4': [
|
|
'3V3', 'EN', 'VP', 'VN', '34', '35', '32', '33', '25', '26', '27', '14', '12', 'GND.1',
|
|
'13', 'D2', 'D3', 'CMD', '5V', 'GND.2', '23', '22', 'TX', 'RX', '21', 'GND.3', '19',
|
|
'18', '5', '17', '16', '4', '0', '2', '15', 'D1', 'D0', 'CLK',
|
|
],
|
|
'esp32-cam': [
|
|
'5V.1', 'GND.1', '12', '13', '15', '14', '2', '4', '3V3', '16', '0', 'GND.2', 'VCC',
|
|
'RX', 'TX', 'GND.3',
|
|
],
|
|
'wemos-lolin32-lite': [
|
|
'VP', 'VN', 'EN', 'GPIO34', 'GPIO35', 'GPIO32', 'GPIO33', 'GPIO25', 'GPIO26', 'GPIO27',
|
|
'GPIO14', 'GPIO12', 'GND', 'GPIO13', 'GPIO15', 'GPIO2', 'GPIO0', 'GPIO4', 'GPIO16',
|
|
'GPIO17', 'GPIO5', 'GPIO18', 'GPIO23', 'GPIO19', 'GPIO22', '3V',
|
|
],
|
|
'xiao-esp32-s3': ['D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'D10', '3V3', 'GND', '5V'],
|
|
'arduino-nano-esp32': [
|
|
'D12', 'D11', 'D10', 'D9', 'D8', 'D7', 'D6', 'D5', 'D4', 'D3', 'D2', 'GND.1', 'RST',
|
|
'RX0', 'TX1', 'D13', '3V3', 'B0', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7',
|
|
'VBUS', 'B1', 'GND.2', 'VIN',
|
|
],
|
|
'xiao-esp32-c3': ['D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'D10', '3V3', 'GND', '5V'],
|
|
'aitewinrobot-esp32c3-supermini': ['5', '6', '7', '8', '9', '10', 'RX', 'TX', '5V', 'GND', '3V3', '4', '3', '2', '1', '0'],
|
|
};
|
|
|
|
/**
|
|
* The DevKitC V4 breaks out the SPI-flash block (GPIO6-11). Those pads are
|
|
* unresolved today and stay that way here deliberately: they are not usable
|
|
* as GPIOs, and mapping them would invite wiring to the flash bus. Listed
|
|
* rather than skipped so the gap is visible.
|
|
*/
|
|
const UNRESOLVED_BY_DESIGN: Record<string, string[]> = {
|
|
'esp32-devkit-c-v4': ['D0', 'D1', 'D3', 'CMD', 'CLK'],
|
|
};
|
|
|
|
const SUPPLY_PAD = /^(GND|VSS|3V|3V3|5V|VCC|VDD|VIN|VBUS|VBAT|BAT|EN|RST)([._]?\d+)?$/i;
|
|
|
|
describe('boardPinToNumber — ESP32 family pads', () => {
|
|
for (const [kind, names] of Object.entries(ESP32_FAMILY_PADS)) {
|
|
const allowed = UNRESOLVED_BY_DESIGN[kind] ?? [];
|
|
|
|
it(`${kind}: every pad resolves`, () => {
|
|
const unresolved = names
|
|
.filter((n) => !allowed.includes(n))
|
|
.filter((n) => boardPinToNumber(kind, n) === null);
|
|
expect(unresolved).toEqual([]);
|
|
});
|
|
|
|
it(`${kind}: no supply pad resolves to a GPIO`, () => {
|
|
const driving = names
|
|
.filter((n) => SUPPLY_PAD.test(n))
|
|
.filter((n) => (boardPinToNumber(kind, n) ?? -1) >= 0);
|
|
expect(driving).toEqual([]);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('boardPinToNumber — the pads that were dead', () => {
|
|
it('DevKit V1 UART pads reach GPIO1 and GPIO3', () => {
|
|
// RX2/TX2 have numeric twins at the same coordinate ('16'/'17', added by
|
|
// be24243e); RX0/TX0 have none, so they were the only route to 3 and 1.
|
|
expect(boardPinToNumber('esp32', 'RX0')).toBe(3);
|
|
expect(boardPinToNumber('esp32', 'TX0')).toBe(1);
|
|
expect(boardPinToNumber('esp32', 'RX2')).toBe(16);
|
|
expect(boardPinToNumber('esp32', 'TX2')).toBe(17);
|
|
});
|
|
|
|
it('the S3 reaches its GPIOs above 39, the classic ESP32 does not', () => {
|
|
for (const n of ['40', '41', '42', '45', '46', '47', '48'])
|
|
expect(boardPinToNumber('esp32-s3', n)).toBe(Number(n));
|
|
expect(boardPinToNumber('esp32', '40')).toBeNull();
|
|
});
|
|
|
|
it('the Nano ESP32 reaches GPIO44/43 through the pads it actually draws', () => {
|
|
expect(boardPinToNumber('arduino-nano-esp32', 'RX0')).toBe(44);
|
|
expect(boardPinToNumber('arduino-nano-esp32', 'TX1')).toBe(43);
|
|
});
|
|
|
|
it('supply pads are -1, not the digit their name starts with', () => {
|
|
expect(boardPinToNumber('wemos-lolin32-lite', '3V')).toBe(-1);
|
|
expect(boardPinToNumber('xiao-esp32-s3', '3V3')).toBe(-1);
|
|
expect(boardPinToNumber('xiao-esp32-s3', '5V')).toBe(-1);
|
|
expect(boardPinToNumber('xiao-esp32-c3', '3V3')).toBe(-1);
|
|
expect(boardPinToNumber('aitewinrobot-esp32c3-supermini', '5V')).toBe(-1);
|
|
expect(boardPinToNumber('arduino-nano-esp32', '3V3')).toBe(-1);
|
|
});
|
|
|
|
it('reset pads are -1, not null', () => {
|
|
for (const kind of ['esp32-s3', 'esp32-c3', 'arduino-nano-esp32'])
|
|
expect(boardPinToNumber(kind, 'RST')).toBe(-1);
|
|
});
|
|
});
|