From faa6f6b7b3beae87fabb572f9e9024ff025f9c59 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Mon, 9 Mar 2026 02:31:04 -0300 Subject: [PATCH] feat: add ILI9341 Cap Touch display and related components; implement home screen and example sketches --- .../Breakout & Icon editor.ino | 34 +++++ .../calculator-breakout-icon/diagram.json | 58 ++++++++ .../calculator-breakout-icon/homeScr.cpp | 129 ++++++++++++++++++ .../calculator-breakout-icon/homeScr.h | 29 ++++ .../calculator-breakout-icon/libraries.txt | 27 ++++ .../calculator-breakout-icon/ourOSObj.cpp | 101 ++++++++++++++ .../calculator-breakout-icon/ourOSObj.h | 26 ++++ .../wokwi-project.txt | 3 + .../ili9341-test-sketch.ino | 53 +++++++ frontend/public/components-metadata.json | 34 +++++ frontend/src/components/DynamicComponent.tsx | 11 +- frontend/src/simulation/parts/ComplexParts.ts | 14 +- frontend/src/store/useSimulatorStore.ts | 29 ++-- 13 files changed, 532 insertions(+), 16 deletions(-) create mode 100644 example_zip/extracted/calculator-breakout-icon/Breakout & Icon editor.ino create mode 100644 example_zip/extracted/calculator-breakout-icon/diagram.json create mode 100644 example_zip/extracted/calculator-breakout-icon/homeScr.cpp create mode 100644 example_zip/extracted/calculator-breakout-icon/homeScr.h create mode 100644 example_zip/extracted/calculator-breakout-icon/libraries.txt create mode 100644 example_zip/extracted/calculator-breakout-icon/ourOSObj.cpp create mode 100644 example_zip/extracted/calculator-breakout-icon/ourOSObj.h create mode 100644 example_zip/extracted/calculator-breakout-icon/wokwi-project.txt create mode 100644 example_zip/extracted/ili9341-test-sketch/ili9341-test-sketch.ino diff --git a/example_zip/extracted/calculator-breakout-icon/Breakout & Icon editor.ino b/example_zip/extracted/calculator-breakout-icon/Breakout & Icon editor.ino new file mode 100644 index 0000000..640c6b9 --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/Breakout & Icon editor.ino @@ -0,0 +1,34 @@ +#include +#include +#include +#include "ourOSObj.h" +#include + +ourOSObj ourOS; +blinker deadMan; + +void setup() { + + Serial.begin(57600); // Fire up serial for debugging. + if (!initScreen(ADAFRUIT_1947,ADA_1947_SHIELD_CS,PORTRAIT)) { // Init screen. + Serial.println("NO SCREEN!"); // Screen init failed. Tell user. + Serial.flush(); // Make sure the message gets out. + while(true); // Lock the process here. + } + if (!SD.begin(ADA_1947_SHIELD_SDCS)) { // With icons, we now MUST have an SD card. + Serial.println("NO SD CARD!"); // Tell user we have no SD card. + Serial.flush(); // Make sure the message gets out. + while(true); // Lock the process here. + } + ourEventMgr.begin(); // Startup our event manager. + ourOS.begin(); // Boot OS manager. + //nextPanel = breakoutApp; // <<-- URISH LOOK HERE!! + deadMan.setOnOff(true); +} + + +void loop() { // During loop.. + + idle(); // Idlers get their time. + ourOS.loop(); // ourOS gets time to pass on to the current panel. +} \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/diagram.json b/example_zip/extracted/calculator-breakout-icon/diagram.json new file mode 100644 index 0000000..0332622 --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/diagram.json @@ -0,0 +1,58 @@ +{ + "version": 1, + "author": "Jim Lee", + "editor": "wokwi", + "parts": [ + { + "type": "wokwi-arduino-mega", + "id": "mega", + "top": 269.4, + "left": -22.8, + "attrs": { "__fakeRamSize": "65000" } + }, + { + "type": "board-ili9341-cap-touch", + "id": "lcd1", + "top": -152.84, + "left": 86.02, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd1", + "top": 39.23, + "left": 395.41, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-buzzer", + "id": "bz1", + "top": 499.2, + "left": 105.6, + "rotate": 180, + "attrs": { "volume": "0.025" } + } + ], + "connections": [ + [ "mega:GND.1", "lcd1:GND", "black", [ "v-48", "h38.2" ] ], + [ "mega:52", "lcd1:SCK", "white", [ "h-11.8", "v-258.1", "h-144" ] ], + [ "mega:50", "lcd1:MISO", "green", [ "v-8.6", "h36.2", "v-222.8", "h-172.8" ] ], + [ "mega:51", "lcd1:MOSI", "blue", [ "v1", "h45.4", "v-242", "h-220.8" ] ], + [ "mega:53", "lcd1:D/C", "yellow", [ "v39.5", "h35.8", "v-271", "h-220.8" ] ], + [ "mega:5V", "lcd1:VCC", "red", [ "v26", "h261.5", "v-268.7", "h-297.6", "v-76.8" ] ], + [ "mega:10", "lcd1:CS", "red", [ "v-28.8", "h18.9" ] ], + [ "mega:20", "lcd1:SDA", "blue", [ "v-128.26", "h-83.64" ] ], + [ "mega:52", "sd1:SCK", "white", [ "h-11.8", "v-258.1", "h110.33" ] ], + [ "mega:50", "sd1:DO", "green", [ "v-8.6", "h36.2", "v-222.8", "h81.65" ] ], + [ "mega:53", "sd1:CD", "yellow", [ "v39.5", "h35.8", "v-271", "h81.54" ] ], + [ "mega:4", "sd1:CS", "green", [ "v-28.8", "h5.4", "v-28.8", "h206.4" ] ], + [ "mega:5V", "sd1:VCC", "red", [ "v26.06", "h261.5", "v-268.76", "h24.08" ] ], + [ "mega:GND.1", "sd1:GND", "black", [ "v-48", "h350.25" ] ], + [ "mega:51", "sd1:DI", "blue", [ "v1", "h45.4", "v-240", "h23.85" ] ], + [ "mega:21", "lcd1:SCL", "white", [ "v-120.21", "h-4.54" ] ], + [ "mega:14", "bz1:2", "green", [ "v-19.2", "h165.1", "v230.4", "h-269.2" ] ], + [ "mega:GND.2", "bz1:1", "black", [ "v0" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/homeScr.cpp b/example_zip/extracted/calculator-breakout-icon/homeScr.cpp new file mode 100644 index 0000000..2d9a05b --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/homeScr.cpp @@ -0,0 +1,129 @@ +#include "homeScr.h" +#include + +#define BAR_Y 286 + + +struct spacer { + float startPos; + float stepSize; +}; + + +spacer calcSpacer(float numItems, float itemLength, float areaLength) { + + spacer aSpacer; + + numItems++; + aSpacer.stepSize = areaLength/numItems; + aSpacer.startPos = aSpacer.stepSize - itemLength/2; + return aSpacer; +} + + +homeScr::homeScr(void) + : panel(homeApp,noMenuBar) { } + + +homeScr::~homeScr(void) { } + + +char* homeScr::iconPath(int appID,char* iconName) { + + strcpy(pathBuff,ourOSPtr->getPanelFolder(appID)); + strcat(pathBuff,iconName); + return pathBuff; +} + + +void homeScr::setup(void) { + + int traceY; + int traceX; + int stepX; + appIcon* theAppIcon; + spacer barSpacer; + + barSpacer = calcSpacer(3,32,240); // Calculate bar spacing. Num apps, icon width, bar width. + traceX = barSpacer.startPos; // Set initial x position. + stepX = barSpacer.stepSize; // Set our step size. + traceY = BAR_Y; // Set our y location. + + theAppIcon = new appIcon(traceX,traceY,calcApp,iconPath(calcApp,"calc32.bmp")); // Create icon for the calculator. + theAppIcon->setMask(&(ourOSPtr->icon32Mask)); // Add the mask to the icon. + addObj(theAppIcon); // Send the icon to our drawObj list to be displayed. + + traceX = traceX + stepX; + theAppIcon = new appIcon(traceX,traceY,breakoutApp,iconPath(breakoutApp,"breakout.bmp")); // Create icon for the breakout game. + theAppIcon->setMask(&(ourOSPtr->icon32Mask)); // Add the mask to the icon. (Its the same mask over and over.) + addObj(theAppIcon); // Send the icon to our drawObj list to be displayed. + + traceX = traceX + stepX; + theAppIcon = new appIcon(traceX,traceY,iconEditApp,iconPath(iconEditApp,"iconEdit.bmp")); // Create icon for the icon editor disaster. + theAppIcon->setMask(&(ourOSPtr->icon32Mask)); // Add the mask. + addObj(theAppIcon); // And its off to the races. + +// traceX = traceX + stepX; +// theAppIcon = new appIcon(traceX,traceY,starTrekApp,iconPath(starTrekApp,"sTrek32.bmp")); // Create icon for the Star Trek game. +// theAppIcon->setMask(&(ourOSPtr->icon32Mask)); // Mask. +// addObj(theAppIcon); // Drop it into the list. + +// traceX = traceX + stepX; +// theAppIcon = new appIcon(traceX,traceY,testApp,iconPath(testApp,"app32.bmp")); // Create icon for the testApp. +// theAppIcon->setMask(&(ourOSPtr->icon32Mask)); // Mask. +// addObj(theAppIcon); // Drop it into the list. + +} + + +void homeScr::loop(void) { } + + +void homeScr::doStarField(void) { + + int randNum; + colorMapper ourCMapper; + colorObj aColor; + mapper yMapper(0,282,0,100); + float yPercent; + + aColor.setColor(LC_LIGHT_BLUE); + aColor.blend(&blue,50); + ourCMapper.setColors(&white,&aColor); + randomSeed(analogRead(A10)); + for (int sy=0;sy<282;sy++) { + for (int sx=0;sx<240;sx++) { + randNum = random(0,400); + if (randNum==300) { + yPercent = yMapper.map(sy); + aColor = ourCMapper.map(yPercent); + screen->drawPixel(sx,sy,&aColor); + } + if (sy<141 && randNum==250) { + yPercent = yMapper.map(sy); + aColor = ourCMapper.map(yPercent); + screen->drawPixel(sx,sy,&aColor); + } + } + } +} + + +void homeScr::drawSelf(void) { + + + //bmpObj theScreenImage(0,0,240,282,"/system/images/lake.bmp"); + colorObj lineColor; + colorObj scrFadeColor; + + lineColor.setColor(LC_CHARCOAL); + lineColor.blend(&blue,20); + screen->fillRectGradient(0,282,240,38,&lineColor,&black); + + //theScreenImage.draw(); + + scrFadeColor.setColor(LC_LIGHT_BLUE); + scrFadeColor.blend(&blue,50); + screen->fillRectGradient(0,0,240,282,&black,&scrFadeColor); + doStarField(); +} \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/homeScr.h b/example_zip/extracted/calculator-breakout-icon/homeScr.h new file mode 100644 index 0000000..5c57947 --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/homeScr.h @@ -0,0 +1,29 @@ +#ifndef homePanel_h +#define homePanel_h + +#include "ourOSObj.h" +#include + +// ***************************************************** +// homeScr +// ***************************************************** + + +class homeScr : public panel { + + public: + homeScr(void); + virtual ~homeScr(void); + + char* iconPath(int appID,char* iconName); + virtual void setup(void); + virtual void loop(void); + void doStarField(void); + virtual void drawSelf(void); + + char pathBuff[80]; +}; + + + +#endif \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/libraries.txt b/example_zip/extracted/calculator-breakout-icon/libraries.txt new file mode 100644 index 0000000..cbb3843 --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/libraries.txt @@ -0,0 +1,27 @@ +Adafruit GFX Library +Adafruit FT6206 Library +Adafruit ILI9341 +LC_Adafruit_1947@wokwi:b065451f35dab6e1021d78f0f79b6eda6910455d +LC_baseTools@wokwi:95340986110645c1b45e55597a7caf4d023d4b4a +LC_GUIbase@wokwi:cad247b5fc057dce02f20f7dd8902e0ab3464bb0 +LC_GUIItems@wokwi:0dabc07df1078c562ee693693d51c280c68131ce +LC_GUITextTools@wokwi:0ea40c37c3578be382f9a915362413565a13779f +LC_keyboard@wokwi:2f79075f1332b48f679c2ab9f8199344458b2643 +LC_RPNCalculator@wokwi:c670627d569e21d822d4bc5e3a04c299191e9dce +LC_scrollingList@wokwi:57d9e60a3fbfb0aee4291d3ad9b9bb4ff1ad0650 +LCP_breakout@wokwi:93b61107ec6046ae81014971bbab8821411b066f +LCP_rpnCalc@wokwi:e87783abb639c397a4c3aa4c4410cd4912407d2c +SD +LC_lilOS@wokwi:f56250c6c13ec5691789b86c286c7c6058486d3d +LC_Adafruit_1431@wokwi:eedcd67a381fa43cda85fa1776a51984439ed091 +Adafruit SSD1351 library +LC_Adafruit_684@wokwi:7897df6f1e2c1ac20b3d78215333acd765b9d6af +Adafruit SSD1331 OLED Driver Library for Arduino +LC_offscreen@wokwi:581db7e6748a58e254fd43c3c490143be2c944f3 +LC_modalAlerts@wokwi:ad3762f077276900373fc61629c0ebc4711fa49d +LC_piezoTunes@wokwi:38e1678e85ffa5ddaa666a9fcd8bcc8c793ff430 +LC_debug@wokwi:0f9acf8678bd2eee8a41d9346ae6c935b05d16f3 +LC_stampObj@wokwi:cc7cc74900be1cbd4ec7e24b0fe835510be9facd +LC_docTools@wokwi:55bb6cfa6138aac31e50c0d60d6ab275e034954a +LCP_iconEdit@wokwi:a846c1543dc5f4727bc59057107d1045e475b994 +LC_SPI@wokwi:78dfdc640f2aa6850c4db21f7fb43b086a87ba86 diff --git a/example_zip/extracted/calculator-breakout-icon/ourOSObj.cpp b/example_zip/extracted/calculator-breakout-icon/ourOSObj.cpp new file mode 100644 index 0000000..704fbdb --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/ourOSObj.cpp @@ -0,0 +1,101 @@ +#include "ourOSObj.h" +#include +#include +#include +//#include +//#include "testAppPanel.h" +#include "homeScr.h" + +#define BEEP_PIN 14 // The digital pin choosen for the beeper. +#define SCREEN_PIN 25 // The ananlog pin choosen for the screen backlight. +char systemFolder[] = "/system/"; // Where we decided to store the systemp folder on our SD card. +char panelFolder[] = "/system/appFiles/"; // Where we decided to store the app folders on our SD card. + + +// ************************************** +// ************** ourOSObj ************** +// ************************************** + + +ourOSObj::ourOSObj(void) + : lilOS() { } + + +ourOSObj::~ourOSObj(void) { } + + +// The hardware is online, do hookups. +int ourOSObj::begin(void) { + + pinMode(BEEP_PIN, OUTPUT); // Setup The beeper pin. + digitalWrite(BEEP_PIN, HIGH); // Means off. + return lilOS::begin(); // Return result of the inherited +} + +//void backlightOn(void) { ourOSPtr->setBrightness(255); } + +// We need to write our own panel creation method. +panel* ourOSObj::createPanel(int panelID) { + + panel* result; + + beep(); + //setBrightness(0); + switch (panelID) { + case homeApp : result = new homeScr(); break; + case calcApp : result = new rpnCalc(this,panelID); break; + case iconEditApp : result = new iconEdit(this,panelID); break; + case breakoutApp : result = new breakout(this,panelID); break; + //case starTrekApp : result = new starTrekPanel(this,panelID); break; + //case testApp : result = new testAppPanel(this,panelID); break; + default : result = NULL; + } + return(result); +} + + +// Only WE know how to make it beep. +void ourOSObj::beep(void) { tone(BEEP_PIN, 750,20); } + + +// Here's the pin if you want to use it yourself. +int ourOSObj::getTonePin(void) {return BEEP_PIN; } + + +// And how to control the screen brightness. +void ourOSObj::setBrightness(byte brightness) { analogWrite(SCREEN_PIN,brightness); } + + +char* ourOSObj::getSystemFolder(void) { return systemFolder; } + + +// Hand this an appID and get back a pointer to the path of its data folder. +char* ourOSObj::getPanelFolder(int panelID) { + + strcpy(pathBuff,panelFolder); + switch (panelID) { + case homeApp : return NULL; + case calcApp : + strcat(pathBuff,"rpnCalc/"); + return pathBuff; + break; + case iconEditApp : + strcat(pathBuff,"iconEdit/"); + return pathBuff; + break; + case breakoutApp : + strcat(pathBuff,"breakout/"); + return pathBuff; + break; +// case starTrekApp : +// strcat(pathBuff,"starTrek/"); +// return pathBuff; +// break; +// case testApp : +// strcpy(pathBuff,getSystemFolder()); +// strcat(pathBuff,"icons/standard/"); +// return pathBuff; +// break; + default : return NULL; + } +} \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/ourOSObj.h b/example_zip/extracted/calculator-breakout-icon/ourOSObj.h new file mode 100644 index 0000000..6a36fdc --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/ourOSObj.h @@ -0,0 +1,26 @@ +#ifndef ourOSObj_h +#define ourOSObj_h + +#include + +enum apps { homeApp = HOME_PANEL_ID, calcApp, breakoutApp, /*testApp*/iconEditApp/*, starTrekApp*/ }; + +class ourOSObj : public lilOS { + + public: + ourOSObj(void); + virtual ~ourOSObj(void); + + virtual int begin(void); // The hardware is online, do hookups. + virtual panel* createPanel(int panelID); // We need to write our own panel creation method. + //void backlightOn(void); + virtual void beep(void); // Only WE know how to make it beep. + virtual int getTonePin(void); + virtual void setBrightness(byte brightness); // 0 for full bright 255 for off. + virtual char* getSystemFolder(void); + virtual char* getPanelFolder(int panelID); +}; + +extern bmpMask iconMask; + +#endif \ No newline at end of file diff --git a/example_zip/extracted/calculator-breakout-icon/wokwi-project.txt b/example_zip/extracted/calculator-breakout-icon/wokwi-project.txt new file mode 100644 index 0000000..34260ec --- /dev/null +++ b/example_zip/extracted/calculator-breakout-icon/wokwi-project.txt @@ -0,0 +1,3 @@ +Downloaded from https://wokwi.com/projects/320027251914572370 + +Simulate this project on https://wokwi.com diff --git a/example_zip/extracted/ili9341-test-sketch/ili9341-test-sketch.ino b/example_zip/extracted/ili9341-test-sketch/ili9341-test-sketch.ino new file mode 100644 index 0000000..94715fb --- /dev/null +++ b/example_zip/extracted/ili9341-test-sketch/ili9341-test-sketch.ino @@ -0,0 +1,53 @@ +/** + * ili9341-test-sketch.ino + * + * Minimal Adafruit_ILI9341 sketch for emulation tests. + * Draws shapes and text on the ILI9341 via hardware SPI. + * + * Wiring (Arduino Nano, hardware SPI): + * SCK → D13 (pin 13, SPI CLK) + * MOSI → D11 (pin 11, SPI MOSI) + * MISO → D12 (pin 12, SPI MISO) + * CS → D10 (pin 10) + * DC → D9 (pin 9) + * RST → D8 (pin 8) + */ + +#include +#include +#include + +#define TFT_CS 10 +#define TFT_DC 9 +#define TFT_RST 8 + +Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); + +void setup() { + Serial.begin(9600); + tft.begin(); + + // Fill screen red + tft.fillScreen(ILI9341_RED); + + // Draw a white rectangle + tft.fillRect(20, 20, 200, 80, ILI9341_WHITE); + + // Draw text + tft.setTextColor(ILI9341_BLACK); + tft.setTextSize(2); + tft.setCursor(30, 40); + tft.println("ILI9341 Test"); + + // Draw a blue circle + tft.fillCircle(120, 200, 50, ILI9341_BLUE); + + // Draw a yellow triangle + tft.fillTriangle(60, 280, 120, 260, 180, 280, ILI9341_YELLOW); + + Serial.println("DONE"); +} + +void loop() { + // Nothing +} diff --git a/frontend/public/components-metadata.json b/frontend/public/components-metadata.json index 871379f..4bf20a7 100644 --- a/frontend/public/components-metadata.json +++ b/frontend/public/components-metadata.json @@ -223,6 +223,40 @@ "ili9341" ] }, + { + "id": "ili9341-cap-touch", + "tagName": "wokwi-ili9341", + "name": "ILI9341 Cap Touch", + "category": "displays", + "description": "ILI9341 TFT display with FT6206 capacitive touch controller", + "thumbnail": "ILI9341 CAP", + "properties": [ + { + "name": "flipHorizontal", + "type": "string", + "defaultValue": false, + "control": "text" + }, + { + "name": "flipVertical", + "type": "string", + "defaultValue": false, + "control": "text" + } + ], + "defaultValues": { + "flipHorizontal": false, + "flipVertical": false + }, + "pinCount": 0, + "tags": [ + "ili9341", + "ili9341-cap-touch", + "tft", + "capacitive", + "touch" + ] + }, { "id": "lcd2004", "tagName": "wokwi-lcd2004", diff --git a/frontend/src/components/DynamicComponent.tsx b/frontend/src/components/DynamicComponent.tsx index 5ad7ff4..c5e0914 100644 --- a/frontend/src/components/DynamicComponent.tsx +++ b/frontend/src/components/DynamicComponent.tsx @@ -51,6 +51,13 @@ export const DynamicComponent: React.FC = ({ const handleComponentEvent = useSimulatorStore((s) => s.handleComponentEvent); const running = useSimulatorStore((s) => s.running); const simulator = useSimulatorStore((s) => s.simulator); + // hexEpoch increments each time a new hex is loaded, triggering a fresh + // attachEvents call (and re-registration of I2C devices on the new bus). + // We intentionally do NOT depend on `running` so that I2C displays and + // other protocol parts (SSD1306, DS1307 …) are NOT torn down and + // re-created on every stop/play cycle — which previously caused the + // display to flash blank and lose its frame buffer. + const hexEpoch = useSimulatorStore((s) => s.hexEpoch); // Check if component is interactive (has simulation logic with attachEvents) const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]); @@ -187,7 +194,7 @@ export const DynamicComponent: React.FC = ({ const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]); let cleanupSimulationEvents: (() => void) | undefined; - if (logic && logic.attachEvents && simulator && running) { + if (logic && logic.attachEvents && simulator) { // Helper to find Arduino pin connected to a component pin const getArduinoPin = (componentPinName: string): number | null => { const wires = useSimulatorStore.getState().wires.filter( @@ -216,7 +223,7 @@ export const DynamicComponent: React.FC = ({ el.removeEventListener('button-press', onButtonPress); el.removeEventListener('button-release', onButtonRelease); }; - }, [id, handleComponentEvent, metadata.id, simulator, running]); + }, [id, handleComponentEvent, metadata.id, simulator, hexEpoch]); return (
{ const el = element as any; const pinManager = (avrSimulator as any).pinManager; @@ -740,4 +746,8 @@ PartSimulationRegistry.register('ili9341', { unsubscribers.forEach(u => u()); }; }, -}); +}; + +PartSimulationRegistry.register('ili9341', ili9341Simulation); +// board-ili9341-cap-touch (Wokwi type) maps to 'ili9341-cap-touch' metadataId — same SPI simulation +PartSimulationRegistry.register('ili9341-cap-touch', ili9341Simulation); diff --git a/frontend/src/store/useSimulatorStore.ts b/frontend/src/store/useSimulatorStore.ts index 69b85a3..b249987 100644 --- a/frontend/src/store/useSimulatorStore.ts +++ b/frontend/src/store/useSimulatorStore.ts @@ -48,6 +48,10 @@ interface SimulatorState { pinManager: PinManager; running: boolean; compiledHex: string | null; + /** Increments each time a new hex/binary is loaded — used to re-attach + * virtual devices (SSD1306, etc.) to the fresh I2C bus without toggling + * on every play/stop cycle. */ + hexEpoch: number; // Components components: Component[]; @@ -115,6 +119,7 @@ export const useSimulatorStore = create((set, get) => { pinManager, running: false, compiledHex: null, + hexEpoch: 0, components: [ { id: 'led-builtin', @@ -221,7 +226,11 @@ export const useSimulatorStore = create((set, get) => { if (simulator && simulator instanceof AVRSimulator) { try { simulator.loadHex(hex); - set({ compiledHex: hex }); + // Re-register background I2C devices on the fresh bus created by loadHex + simulator.addI2CDevice(new VirtualDS1307()); + simulator.addI2CDevice(new VirtualTempSensor()); + simulator.addI2CDevice(new I2CMemoryDevice(0x50)); + set((s) => ({ compiledHex: hex, hexEpoch: s.hexEpoch + 1 })); console.log('HEX file loaded successfully'); } catch (error) { console.error('Failed to load HEX:', error); @@ -236,7 +245,11 @@ export const useSimulatorStore = create((set, get) => { if (simulator && simulator instanceof RP2040Simulator) { try { simulator.loadBinary(base64); - set({ compiledHex: base64 }); // reuse compiledHex as "program loaded" flag + // Re-register background I2C devices on the fresh bus + simulator.addI2CDevice(new VirtualDS1307() as RP2040I2CDevice); + simulator.addI2CDevice(new VirtualTempSensor() as RP2040I2CDevice); + simulator.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice); + set((s) => ({ compiledHex: base64, hexEpoch: s.hexEpoch + 1 })); console.log('Binary loaded into RP2040 successfully'); } catch (error) { console.error('Failed to load binary:', error); @@ -249,16 +262,8 @@ export const useSimulatorStore = create((set, get) => { startSimulation: () => { const { simulator } = get(); if (simulator) { - // Register virtual I2C devices before starting - if (simulator instanceof AVRSimulator && simulator.i2cBus) { - simulator.addI2CDevice(new VirtualDS1307()); - simulator.addI2CDevice(new VirtualTempSensor()); - simulator.addI2CDevice(new I2CMemoryDevice(0x50)); - } else if (simulator instanceof RP2040Simulator) { - simulator.addI2CDevice(new VirtualDS1307() as RP2040I2CDevice); - simulator.addI2CDevice(new VirtualTempSensor() as RP2040I2CDevice); - simulator.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice); - } + // Background I2C devices are registered in loadHex/loadBinary, + // so we just need to start the CPU loop here. simulator.start(); set({ running: true, serialMonitorOpen: true }); }