feat: add ILI9341 Cap Touch display and related components; implement home screen and example sketches

pull/10/head
David Montero Crespo 2026-03-09 02:31:04 -03:00
parent e5d3152488
commit faa6f6b7b3
13 changed files with 532 additions and 16 deletions

View File

@ -0,0 +1,34 @@
#include <SD.h>
#include <adafruit_1947_Obj.h>
#include <screen.h>
#include "ourOSObj.h"
#include <blinker.h>
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.
}

View File

@ -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": {}
}

View File

@ -0,0 +1,129 @@
#include "homeScr.h"
#include <lilOS.h>
#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();
}

View File

@ -0,0 +1,29 @@
#ifndef homePanel_h
#define homePanel_h
#include "ourOSObj.h"
#include <bmpObj.h>
// *****************************************************
// 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

View File

@ -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

View File

@ -0,0 +1,101 @@
#include "ourOSObj.h"
#include <rpnCalc.h>
#include <breakout.h>
#include <iconEdit.h>
//#include <starTrek.h>
//#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;
}
}

View File

@ -0,0 +1,26 @@
#ifndef ourOSObj_h
#define ourOSObj_h
#include <lilOS.h>
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

View File

@ -0,0 +1,3 @@
Downloaded from https://wokwi.com/projects/320027251914572370
Simulate this project on https://wokwi.com

View File

@ -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 <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#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
}

View File

@ -223,6 +223,40 @@
"ili9341" "ili9341"
] ]
}, },
{
"id": "ili9341-cap-touch",
"tagName": "wokwi-ili9341",
"name": "ILI9341 Cap Touch",
"category": "displays",
"description": "ILI9341 TFT display with FT6206 capacitive touch controller",
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/><text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"9\" fill=\"#666\">ILI9341 CAP</text></svg>",
"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", "id": "lcd2004",
"tagName": "wokwi-lcd2004", "tagName": "wokwi-lcd2004",

View File

@ -51,6 +51,13 @@ export const DynamicComponent: React.FC<DynamicComponentProps> = ({
const handleComponentEvent = useSimulatorStore((s) => s.handleComponentEvent); const handleComponentEvent = useSimulatorStore((s) => s.handleComponentEvent);
const running = useSimulatorStore((s) => s.running); const running = useSimulatorStore((s) => s.running);
const simulator = useSimulatorStore((s) => s.simulator); 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) // Check if component is interactive (has simulation logic with attachEvents)
const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]); const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]);
@ -187,7 +194,7 @@ export const DynamicComponent: React.FC<DynamicComponentProps> = ({
const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]); const logic = PartSimulationRegistry.get(metadata.id || id.split('-')[0]);
let cleanupSimulationEvents: (() => void) | undefined; 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 // Helper to find Arduino pin connected to a component pin
const getArduinoPin = (componentPinName: string): number | null => { const getArduinoPin = (componentPinName: string): number | null => {
const wires = useSimulatorStore.getState().wires.filter( const wires = useSimulatorStore.getState().wires.filter(
@ -216,7 +223,7 @@ export const DynamicComponent: React.FC<DynamicComponentProps> = ({
el.removeEventListener('button-press', onButtonPress); el.removeEventListener('button-press', onButtonPress);
el.removeEventListener('button-release', onButtonRelease); el.removeEventListener('button-release', onButtonRelease);
}; };
}, [id, handleComponentEvent, metadata.id, simulator, running]); }, [id, handleComponentEvent, metadata.id, simulator, hexEpoch]);
return ( return (
<div <div

View File

@ -347,6 +347,11 @@ PartSimulationRegistry.register('buzzer', {
gainNode.gain.value = 0.1; gainNode.gain.value = 0.1;
gainNode.connect(audioCtx.destination); gainNode.connect(audioCtx.destination);
} }
// Browser autoplay policy: AudioContext starts in 'suspended' state
// until a user gesture has occurred. Resume it here so sound plays.
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
if (oscillator) { if (oscillator) {
oscillator.frequency.setTargetAtTime(freq, audioCtx.currentTime, 0.01); oscillator.frequency.setTargetAtTime(freq, audioCtx.currentTime, 0.01);
return; return;
@ -559,6 +564,7 @@ function createLcdSimulation(cols: number, rows: number) {
PartSimulationRegistry.register('lcd1602', createLcdSimulation(16, 2)); PartSimulationRegistry.register('lcd1602', createLcdSimulation(16, 2));
PartSimulationRegistry.register('lcd2004', createLcdSimulation(20, 4)); PartSimulationRegistry.register('lcd2004', createLcdSimulation(20, 4));
PartSimulationRegistry.register('lcd2002', createLcdSimulation(20, 2));
// ─── ILI9341 TFT Display (SPI) ─────────────────────────────────────────────── // ─── ILI9341 TFT Display (SPI) ───────────────────────────────────────────────
@ -574,7 +580,7 @@ PartSimulationRegistry.register('lcd2004', createLcdSimulation(20, 4));
* *
* DC/RS pin: LOW = command byte, HIGH = data bytes. * DC/RS pin: LOW = command byte, HIGH = data bytes.
*/ */
PartSimulationRegistry.register('ili9341', { const ili9341Simulation = {
attachEvents: (element, avrSimulator, getArduinoPinHelper) => { attachEvents: (element, avrSimulator, getArduinoPinHelper) => {
const el = element as any; const el = element as any;
const pinManager = (avrSimulator as any).pinManager; const pinManager = (avrSimulator as any).pinManager;
@ -740,4 +746,8 @@ PartSimulationRegistry.register('ili9341', {
unsubscribers.forEach(u => u()); 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);

View File

@ -48,6 +48,10 @@ interface SimulatorState {
pinManager: PinManager; pinManager: PinManager;
running: boolean; running: boolean;
compiledHex: string | null; 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
components: Component[]; components: Component[];
@ -115,6 +119,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
pinManager, pinManager,
running: false, running: false,
compiledHex: null, compiledHex: null,
hexEpoch: 0,
components: [ components: [
{ {
id: 'led-builtin', id: 'led-builtin',
@ -221,7 +226,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
if (simulator && simulator instanceof AVRSimulator) { if (simulator && simulator instanceof AVRSimulator) {
try { try {
simulator.loadHex(hex); 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'); console.log('HEX file loaded successfully');
} catch (error) { } catch (error) {
console.error('Failed to load HEX:', error); console.error('Failed to load HEX:', error);
@ -236,7 +245,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
if (simulator && simulator instanceof RP2040Simulator) { if (simulator && simulator instanceof RP2040Simulator) {
try { try {
simulator.loadBinary(base64); 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'); console.log('Binary loaded into RP2040 successfully');
} catch (error) { } catch (error) {
console.error('Failed to load binary:', error); console.error('Failed to load binary:', error);
@ -249,16 +262,8 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
startSimulation: () => { startSimulation: () => {
const { simulator } = get(); const { simulator } = get();
if (simulator) { if (simulator) {
// Register virtual I2C devices before starting // Background I2C devices are registered in loadHex/loadBinary,
if (simulator instanceof AVRSimulator && simulator.i2cBus) { // so we just need to start the CPU loop here.
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);
}
simulator.start(); simulator.start();
set({ running: true, serialMonitorOpen: true }); set({ running: true, serialMonitorOpen: true });
} }