feat(ssd1306): one auto-detecting OLED part (merge the I2C/SPI picker entries)
The SSD1306 was three picker entries — a generic `ssd1306` with a protocol selector plus `ssd1306-i2c` / `ssd1306-spi` shortcuts (issue #101) — all the same 8-pin wokwi-ssd1306 element. That is confusing for one physical module (issue #215). Wokwi ships a single I2C-only part; this goes one better: a single part that auto-detects the protocol from the wiring, like a real breadboard — CS or DC wired to a GPIO means SPI, otherwise I2C. No protocol switch to set, just wire it up. Works on every board with an I2C/SPI bus (AVR, RP2040, ESP32 Xtensa, STM32). The ssd1306-i2c / ssd1306-spi ids stay as backward-compat simulation aliases for projects saved before the merge, but are removed from the picker. Adds an i2cAddress property (0x3c/0x3d) matching the real module and Wokwi. Note: ESP32-C3, Raspberry Pi 3 and the bare RISC-V board do not emulate I2C/SPI peripherals, so no I2C/SPI device (this or any other) attaches there yet.
This commit is contained in:
parent
061bb91da1
commit
b6dd2f5201
|
|
@ -1430,7 +1430,7 @@
|
|||
{
|
||||
"id": "ssd1306",
|
||||
"tagName": "wokwi-ssd1306",
|
||||
"name": "SSD1306",
|
||||
"name": "SSD1306 OLED",
|
||||
"category": "displays",
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n SSD1306\n </text>\n </svg>",
|
||||
"properties": [
|
||||
|
|
@ -1440,75 +1440,25 @@
|
|||
"control": "text"
|
||||
},
|
||||
{
|
||||
"name": "protocol",
|
||||
"name": "i2cAddress",
|
||||
"type": "string",
|
||||
"defaultValue": "i2c",
|
||||
"control": "select",
|
||||
"description": "Communication protocol",
|
||||
"options": [
|
||||
"i2c",
|
||||
"spi"
|
||||
]
|
||||
"defaultValue": "0x3c",
|
||||
"control": "text",
|
||||
"description": "I2C address (I2C mode)"
|
||||
}
|
||||
],
|
||||
"defaultValues": {
|
||||
"protocol": "i2c"
|
||||
"i2cAddress": "0x3c"
|
||||
},
|
||||
"pinCount": 0,
|
||||
"tags": [
|
||||
"ssd1306"
|
||||
]
|
||||
},
|
||||
{
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#0b1226\" rx=\"4\"/><rect x=\"8\" y=\"14\" width=\"48\" height=\"32\" rx=\"2\" fill=\"#000\"/><text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#9be1ff\" font-weight=\"bold\">SSD1306</text><text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"6\" font-family=\"monospace\" fill=\"#9be1ff\">I2C 0x3C</text><text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"7\" font-family=\"sans-serif\" fill=\"#e5e7eb\" font-weight=\"bold\">SDA SCL</text></svg>",
|
||||
"pinCount": 8,
|
||||
"tags": [
|
||||
"ssd1306",
|
||||
"oled",
|
||||
"display",
|
||||
"i2c",
|
||||
"spi",
|
||||
"0x3c"
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "imageData",
|
||||
"type": "string",
|
||||
"control": "text"
|
||||
}
|
||||
],
|
||||
"defaultValues": {
|
||||
"protocol": "i2c"
|
||||
},
|
||||
"pinCount": 4,
|
||||
"$comment": "Picker shortcut for SSD1306 in I2C mode — same wokwi-ssd1306 element, but the user finds it by name without having to discover the protocol selector on the generic ssd1306 entry. Issue #101.",
|
||||
"id": "ssd1306-i2c",
|
||||
"tagName": "wokwi-ssd1306",
|
||||
"name": "SSD1306 OLED (I2C)",
|
||||
"category": "displays"
|
||||
},
|
||||
{
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#0b1226\" rx=\"4\"/><rect x=\"8\" y=\"14\" width=\"48\" height=\"32\" rx=\"2\" fill=\"#000\"/><text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#9be1ff\" font-weight=\"bold\">SSD1306</text><text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"6\" font-family=\"monospace\" fill=\"#9be1ff\">SPI mode</text><text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"6\" font-family=\"sans-serif\" fill=\"#e5e7eb\" font-weight=\"bold\">MOSI/SCK/DC</text></svg>",
|
||||
"tags": [
|
||||
"ssd1306",
|
||||
"oled",
|
||||
"display",
|
||||
"spi"
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "imageData",
|
||||
"type": "string",
|
||||
"control": "text"
|
||||
}
|
||||
],
|
||||
"defaultValues": {
|
||||
"protocol": "spi"
|
||||
},
|
||||
"pinCount": 7,
|
||||
"$comment": "Picker shortcut for SSD1306 in SPI mode — counterpart to ssd1306-i2c.",
|
||||
"id": "ssd1306-spi",
|
||||
"tagName": "wokwi-ssd1306",
|
||||
"name": "SSD1306 OLED (SPI)",
|
||||
"category": "displays"
|
||||
]
|
||||
},
|
||||
{
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n MOTOR-DRIVER-L293D\n </text>\n </svg>",
|
||||
|
|
|
|||
|
|
@ -206,6 +206,56 @@ describe('ssd1306 — I2C device', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ─── ssd1306 — protocol auto-detect (one component, wired like real life) ─────
|
||||
|
||||
describe('ssd1306 — protocol auto-detect', () => {
|
||||
it('runs I2C when neither CS nor DC is wired', () => {
|
||||
const sim = makeI2CSim();
|
||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(makeElement(), sim as any, noPins);
|
||||
expect(sim.addI2CDevice).toHaveBeenCalledOnce();
|
||||
expect(sim.spi).toBeNull();
|
||||
});
|
||||
|
||||
it('runs SPI when CS is wired to a GPIO', () => {
|
||||
const sim = makeSPISim();
|
||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
pinMap({ CS: 5 }),
|
||||
);
|
||||
// SPI decoder hooks spi.onByte; the I2C path would call addI2CDevice instead.
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs SPI when DC is wired to a GPIO', () => {
|
||||
const sim = makeSPISim();
|
||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
pinMap({ DC: 4 }),
|
||||
);
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('legacy ssd1306-i2c alias forces I2C even with CS wired', () => {
|
||||
const sim = makeI2CSim();
|
||||
PartSimulationRegistry.get('ssd1306-i2c')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
pinMap({ CS: 5 }),
|
||||
);
|
||||
expect(sim.addI2CDevice).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('legacy ssd1306-spi alias forces SPI even with nothing wired', () => {
|
||||
const sim = makeSPISim();
|
||||
PartSimulationRegistry.get('ssd1306-spi')!.attachEvents!(makeElement(), sim as any, noPins);
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── ds1307 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('ds1307 — I2C RTC', () => {
|
||||
|
|
|
|||
|
|
@ -337,20 +337,22 @@ function attachSSD1306(
|
|||
simulator: unknown,
|
||||
getPin: (n: string) => number | null,
|
||||
protocol: 'i2c' | 'spi',
|
||||
i2cAddr = 0x3c,
|
||||
): () => void {
|
||||
if (protocol === 'spi') {
|
||||
return attachSSD1306SPI(element, simulator, getPin);
|
||||
}
|
||||
const sim = simulator as any;
|
||||
const i2cAddr = 0x3c;
|
||||
const device = new VirtualSSD1306(i2cAddr, element);
|
||||
|
||||
// Check ESP32 first — its shim exposes BOTH registerSensor (for the
|
||||
// backend QEMU slave) AND addI2CDevice (for the frontend bus used by
|
||||
// the Interconnect cross-board bridge). AVR / RP2040 only expose
|
||||
// addI2CDevice, so registerSensor is the unambiguous ESP32 marker.
|
||||
// The ESP32/STM32 bridge shims expose registerSensor (backend QEMU slave) +
|
||||
// addI2CTransactionListener (framebuffer bytes streamed back) AND addI2CDevice
|
||||
// (frontend bus for the cross-board Interconnect). AVR / RP2040 also carry a
|
||||
// registerSensor() stub that returns false, so they enter this branch too —
|
||||
// harmlessly: registerSensor no-ops, the absent addI2CTransactionListener is
|
||||
// skipped, and the real attach happens via the addI2CDevice mirror below.
|
||||
if (typeof sim.registerSensor === 'function') {
|
||||
// ── ESP32 path ─────────────────────────────────────────────────────────
|
||||
// ── ESP32 / STM32 (and AVR/RP2040 via the addI2CDevice mirror) ──────────
|
||||
const virtualPin = 200 + i2cAddr;
|
||||
sim.registerSensor('ssd1306', virtualPin, { addr: i2cAddr });
|
||||
sim.addI2CTransactionListener?.(i2cAddr, (data: number[]) => {
|
||||
|
|
@ -374,31 +376,47 @@ function attachSSD1306(
|
|||
}
|
||||
|
||||
/**
|
||||
* Generic `ssd1306` entry — reads the user-selectable `protocol`
|
||||
* property (control: select, options: i2c | spi). Keeps backward
|
||||
* compatibility for existing projects whose components carry this id.
|
||||
* Which wire protocol did the user build? A real SSD1306 breakout is ONE
|
||||
* board that talks either I2C or SPI depending on how it is wired: SPI drives
|
||||
* the chip-select (CS) and data/command (DC) lines from MCU GPIOs, while I2C
|
||||
* leaves them tied to power (address select) or unconnected. So if CS or DC is
|
||||
* wired to a GPIO we decode SPI; otherwise I2C. This mirrors the physical part
|
||||
* — one component, no protocol switch to set, just wire it up.
|
||||
*
|
||||
* Pure wiring check: it deliberately does NOT read `simulator.spi`, whose
|
||||
* getter on some boards (RP2040, and the ESP32/STM32 bridge shims) lazily
|
||||
* re-routes the board SPI bus as a side effect and must not fire in I2C mode.
|
||||
*/
|
||||
function detectSSD1306Protocol(getPin: (n: string) => number | null): 'i2c' | 'spi' {
|
||||
return getPin('CS') !== null || getPin('DC') !== null ? 'spi' : 'i2c';
|
||||
}
|
||||
|
||||
/**
|
||||
* SSD1306 OLED — a single component that works on every board with an I2C or
|
||||
* SPI bus (AVR, RP2040, ESP32, STM32), auto-detecting the protocol from the
|
||||
* wiring like the physical module. Consolidates the old ssd1306 / ssd1306-i2c
|
||||
* / ssd1306-spi picker entries into one (issues #101 / #215).
|
||||
*/
|
||||
PartSimulationRegistry.register('ssd1306', {
|
||||
attachEvents: (element, simulator, getPin, componentId) => {
|
||||
const { components } = useSimulatorStore.getState();
|
||||
const comp = components.find((c) => c.id === componentId);
|
||||
const protocol = ((comp?.properties?.protocol as string) ?? 'i2c') as 'i2c' | 'spi';
|
||||
return attachSSD1306(element, simulator, getPin, protocol);
|
||||
const i2cAddr = parseI2cAddress(comp?.properties?.i2cAddress, 0x3c);
|
||||
const protocol = detectSSD1306Protocol(getPin);
|
||||
return attachSSD1306(element, simulator, getPin, protocol, i2cAddr);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Picker shortcut: "SSD1306 OLED (I2C)" — same web component, but the
|
||||
* metadata defaults protocol to 'i2c' and the part skips the property
|
||||
* lookup. Lets users find the I2C variant by name without having to
|
||||
* discover the protocol property on the generic ssd1306 entry.
|
||||
* Legacy aliases kept ONLY for projects saved before the picker entries merged
|
||||
* into the single auto-detecting `ssd1306` above. New projects never carry
|
||||
* these ids. They force a fixed protocol (no auto-detect) to reproduce the old
|
||||
* behaviour exactly.
|
||||
*/
|
||||
PartSimulationRegistry.register('ssd1306-i2c', {
|
||||
attachEvents: (element, simulator, getPin) =>
|
||||
attachSSD1306(element, simulator, getPin, 'i2c'),
|
||||
});
|
||||
|
||||
/** Picker shortcut: "SSD1306 OLED (SPI)" — counterpart to ssd1306-i2c. */
|
||||
PartSimulationRegistry.register('ssd1306-spi', {
|
||||
attachEvents: (element, simulator, getPin) =>
|
||||
attachSSD1306(element, simulator, getPin, 'spi'),
|
||||
|
|
|
|||
|
|
@ -2455,57 +2455,6 @@
|
|||
"2004",
|
||||
"liquidcrystal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$comment": "Picker shortcut for SSD1306 in I2C mode — same wokwi-ssd1306 element, but the user finds it by name without having to discover the protocol selector on the generic ssd1306 entry. Issue #101.",
|
||||
"id": "ssd1306-i2c",
|
||||
"tagName": "wokwi-ssd1306",
|
||||
"name": "SSD1306 OLED (I2C)",
|
||||
"category": "displays",
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#0b1226\" rx=\"4\"/><rect x=\"8\" y=\"14\" width=\"48\" height=\"32\" rx=\"2\" fill=\"#000\"/><text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#9be1ff\" font-weight=\"bold\">SSD1306</text><text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"6\" font-family=\"monospace\" fill=\"#9be1ff\">I2C 0x3C</text><text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"7\" font-family=\"sans-serif\" fill=\"#e5e7eb\" font-weight=\"bold\">SDA SCL</text></svg>",
|
||||
"properties": [
|
||||
{
|
||||
"name": "imageData",
|
||||
"type": "string",
|
||||
"control": "text"
|
||||
}
|
||||
],
|
||||
"defaultValues": {
|
||||
"protocol": "i2c"
|
||||
},
|
||||
"pinCount": 4,
|
||||
"tags": [
|
||||
"ssd1306",
|
||||
"oled",
|
||||
"display",
|
||||
"i2c",
|
||||
"0x3c"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$comment": "Picker shortcut for SSD1306 in SPI mode — counterpart to ssd1306-i2c.",
|
||||
"id": "ssd1306-spi",
|
||||
"tagName": "wokwi-ssd1306",
|
||||
"name": "SSD1306 OLED (SPI)",
|
||||
"category": "displays",
|
||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#0b1226\" rx=\"4\"/><rect x=\"8\" y=\"14\" width=\"48\" height=\"32\" rx=\"2\" fill=\"#000\"/><text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#9be1ff\" font-weight=\"bold\">SSD1306</text><text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"6\" font-family=\"monospace\" fill=\"#9be1ff\">SPI mode</text><text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"6\" font-family=\"sans-serif\" fill=\"#e5e7eb\" font-weight=\"bold\">MOSI/SCK/DC</text></svg>",
|
||||
"properties": [
|
||||
{
|
||||
"name": "imageData",
|
||||
"type": "string",
|
||||
"control": "text"
|
||||
}
|
||||
],
|
||||
"defaultValues": {
|
||||
"protocol": "spi"
|
||||
},
|
||||
"pinCount": 7,
|
||||
"tags": [
|
||||
"ssd1306",
|
||||
"oled",
|
||||
"display",
|
||||
"spi"
|
||||
]
|
||||
}
|
||||
],
|
||||
"led": {
|
||||
|
|
@ -2525,21 +2474,28 @@
|
|||
}
|
||||
},
|
||||
"ssd1306": {
|
||||
"properties": {
|
||||
"protocol": {
|
||||
"name": "protocol",
|
||||
"type": "string",
|
||||
"defaultValue": "i2c",
|
||||
"control": "select",
|
||||
"description": "Communication protocol",
|
||||
"options": [
|
||||
"$comment": "Single SSD1306 OLED entry. One physical breakout; the sim auto-detects I2C vs SPI from the wiring (see ProtocolParts.detectSSD1306Protocol). Replaces the old ssd1306-i2c/ssd1306-spi picker shortcuts (issues #101/#215).",
|
||||
"name": "SSD1306 OLED",
|
||||
"pinCount": 8,
|
||||
"tags": [
|
||||
"ssd1306",
|
||||
"oled",
|
||||
"display",
|
||||
"i2c",
|
||||
"spi"
|
||||
]
|
||||
"spi",
|
||||
"0x3c"
|
||||
],
|
||||
"properties": {
|
||||
"i2cAddress": {
|
||||
"name": "i2cAddress",
|
||||
"type": "string",
|
||||
"defaultValue": "0x3c",
|
||||
"control": "text",
|
||||
"description": "I2C address (I2C mode)"
|
||||
}
|
||||
},
|
||||
"defaultValues": {
|
||||
"protocol": "i2c"
|
||||
"i2cAddress": "0x3c"
|
||||
}
|
||||
},
|
||||
"resistor": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue