feat: add Serial Monitor component and integrate with AVR simulator
- Implemented SerialMonitor component to display serial output and allow user input. - Enhanced AVRSimulator to handle USART communication and transmit serial data. - Updated useSimulatorStore to manage serial output state and toggle visibility of the Serial Monitor. - Added example Arduino sketches for serial communication, including Serial Echo and Serial LED Control. - Introduced I2CBusManager to manage virtual I2C devices and integrated with AVRSimulator.pull/10/head
parent
13cf7be465
commit
5d175abdcf
|
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* Serial Monitor — shows Arduino Serial output and allows sending data back.
|
||||
* Connects to the AVRSimulator USART via the Zustand store.
|
||||
*/
|
||||
|
||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
||||
|
||||
export const SerialMonitor: React.FC = () => {
|
||||
const serialOutput = useSimulatorStore((s) => s.serialOutput);
|
||||
const running = useSimulatorStore((s) => s.running);
|
||||
const serialWrite = useSimulatorStore((s) => s.serialWrite);
|
||||
const clearSerialOutput = useSimulatorStore((s) => s.clearSerialOutput);
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [lineEnding, setLineEnding] = useState<'none' | 'nl' | 'cr' | 'both'>('nl');
|
||||
const [autoscroll, setAutoscroll] = useState(true);
|
||||
const outputRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
// Auto-scroll to bottom when new output arrives
|
||||
useEffect(() => {
|
||||
if (autoscroll && outputRef.current) {
|
||||
outputRef.current.scrollTop = outputRef.current.scrollHeight;
|
||||
}
|
||||
}, [serialOutput, autoscroll]);
|
||||
|
||||
const handleSend = useCallback(() => {
|
||||
if (!inputValue && lineEnding === 'none') return;
|
||||
let text = inputValue;
|
||||
switch (lineEnding) {
|
||||
case 'nl': text += '\n'; break;
|
||||
case 'cr': text += '\r'; break;
|
||||
case 'both': text += '\r\n'; break;
|
||||
}
|
||||
serialWrite(text);
|
||||
setInputValue('');
|
||||
}, [inputValue, lineEnding, serialWrite]);
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
}, [handleSend]);
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
{/* Header */}
|
||||
<div style={styles.header}>
|
||||
<span style={styles.title}>Serial Monitor</span>
|
||||
<div style={styles.headerControls}>
|
||||
<label style={styles.autoscrollLabel}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoscroll}
|
||||
onChange={(e) => setAutoscroll(e.target.checked)}
|
||||
style={styles.checkbox}
|
||||
/>
|
||||
Autoscroll
|
||||
</label>
|
||||
<button onClick={clearSerialOutput} style={styles.clearBtn} title="Clear output">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Output area */}
|
||||
<pre ref={outputRef} style={styles.output}>
|
||||
{serialOutput || (running ? 'Waiting for serial data...\n' : 'Start simulation to see serial output.\n')}
|
||||
</pre>
|
||||
|
||||
{/* Input row */}
|
||||
<div style={styles.inputRow}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Type message to send..."
|
||||
style={styles.input}
|
||||
disabled={!running}
|
||||
/>
|
||||
<select
|
||||
value={lineEnding}
|
||||
onChange={(e) => setLineEnding(e.target.value as typeof lineEnding)}
|
||||
style={styles.select}
|
||||
>
|
||||
<option value="none">No line ending</option>
|
||||
<option value="nl">Newline</option>
|
||||
<option value="cr">Carriage return</option>
|
||||
<option value="both">Both NL & CR</option>
|
||||
</select>
|
||||
<button onClick={handleSend} disabled={!running} style={styles.sendBtn}>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles: Record<string, React.CSSProperties> = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
background: '#1e1e1e',
|
||||
borderTop: '1px solid #333',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '4px 8px',
|
||||
background: '#252526',
|
||||
borderBottom: '1px solid #333',
|
||||
minHeight: 28,
|
||||
},
|
||||
title: {
|
||||
color: '#cccccc',
|
||||
fontWeight: 600,
|
||||
fontSize: 12,
|
||||
},
|
||||
headerControls: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
autoscrollLabel: {
|
||||
color: '#999',
|
||||
fontSize: 11,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
checkbox: {
|
||||
margin: 0,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
clearBtn: {
|
||||
background: 'transparent',
|
||||
border: '1px solid #555',
|
||||
color: '#ccc',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 3,
|
||||
cursor: 'pointer',
|
||||
fontSize: 11,
|
||||
},
|
||||
output: {
|
||||
flex: 1,
|
||||
margin: 0,
|
||||
padding: 8,
|
||||
color: '#00ff41',
|
||||
background: '#0a0a0a',
|
||||
overflowY: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
minHeight: 0,
|
||||
fontSize: 13,
|
||||
lineHeight: '1.4',
|
||||
},
|
||||
inputRow: {
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
padding: 4,
|
||||
background: '#252526',
|
||||
borderTop: '1px solid #333',
|
||||
},
|
||||
input: {
|
||||
flex: 1,
|
||||
background: '#1e1e1e',
|
||||
border: '1px solid #444',
|
||||
color: '#ccc',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 3,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
outline: 'none',
|
||||
},
|
||||
select: {
|
||||
background: '#1e1e1e',
|
||||
border: '1px solid #444',
|
||||
color: '#ccc',
|
||||
padding: '4px',
|
||||
borderRadius: 3,
|
||||
fontSize: 11,
|
||||
outline: 'none',
|
||||
},
|
||||
sendBtn: {
|
||||
background: '#0e639c',
|
||||
border: 'none',
|
||||
color: '#fff',
|
||||
padding: '4px 12px',
|
||||
borderRadius: 3,
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
|
|
@ -761,6 +761,595 @@ void loop() {
|
|||
// Power / Contrast logic is usually handled internally or ignored in basic simulation
|
||||
],
|
||||
},
|
||||
// ─── Protocol Test Examples ──────────────────────────────────────────────
|
||||
{
|
||||
id: 'serial-echo',
|
||||
title: 'Serial Echo (USART)',
|
||||
description: 'Tests Serial communication: echoes typed characters back and prints status. Open the Serial Monitor to interact.',
|
||||
category: 'communication',
|
||||
difficulty: 'beginner',
|
||||
code: `// Serial Echo — USART Protocol Test
|
||||
// Open the Serial Monitor to send and receive data.
|
||||
// Everything you type is echoed back with extra info.
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("=============================");
|
||||
Serial.println(" Serial Echo Test (USART)");
|
||||
Serial.println("=============================");
|
||||
Serial.println("Type something and press Send.");
|
||||
Serial.println();
|
||||
|
||||
// Print system info
|
||||
Serial.print("CPU Clock: ");
|
||||
Serial.print(F_CPU / 1000000);
|
||||
Serial.println(" MHz");
|
||||
Serial.print("Baud rate: 9600");
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
unsigned long charCount = 0;
|
||||
|
||||
void loop() {
|
||||
if (Serial.available() > 0) {
|
||||
char c = Serial.read();
|
||||
charCount++;
|
||||
|
||||
Serial.print("[");
|
||||
Serial.print(charCount);
|
||||
Serial.print("] Received: '");
|
||||
Serial.print(c);
|
||||
Serial.print("' (ASCII ");
|
||||
Serial.print((int)c);
|
||||
Serial.println(")");
|
||||
}
|
||||
|
||||
// Periodic heartbeat
|
||||
static unsigned long lastBeat = 0;
|
||||
if (millis() - lastBeat >= 5000) {
|
||||
lastBeat = millis();
|
||||
Serial.print("Uptime: ");
|
||||
Serial.print(millis() / 1000);
|
||||
Serial.print("s | Chars received: ");
|
||||
Serial.println(charCount);
|
||||
}
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
{
|
||||
id: 'serial-led-control',
|
||||
title: 'Serial LED Control',
|
||||
description: 'Control an LED via Serial commands: send "1" or "0". Tests USART RX + GPIO output together.',
|
||||
category: 'communication',
|
||||
difficulty: 'beginner',
|
||||
code: `// Serial LED Control
|
||||
// Send "1" to turn LED ON, "0" to turn LED OFF.
|
||||
// Demonstrates Serial input controlling hardware.
|
||||
|
||||
const int LED_PIN = 13;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
Serial.println("=========================");
|
||||
Serial.println(" Serial LED Controller");
|
||||
Serial.println("=========================");
|
||||
Serial.println("Send '1' = LED ON");
|
||||
Serial.println("Send '0' = LED OFF");
|
||||
Serial.println("Send '?' = Status");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
bool ledState = false;
|
||||
|
||||
void loop() {
|
||||
if (Serial.available() > 0) {
|
||||
char cmd = Serial.read();
|
||||
|
||||
switch (cmd) {
|
||||
case '1':
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
ledState = true;
|
||||
Serial.println("[OK] LED is ON");
|
||||
break;
|
||||
case '0':
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
ledState = false;
|
||||
Serial.println("[OK] LED is OFF");
|
||||
break;
|
||||
case '?':
|
||||
Serial.print("[STATUS] LED is ");
|
||||
Serial.println(ledState ? "ON" : "OFF");
|
||||
Serial.print("[STATUS] Uptime: ");
|
||||
Serial.print(millis() / 1000);
|
||||
Serial.println("s");
|
||||
break;
|
||||
default:
|
||||
if (cmd >= 32) { // ignore control chars
|
||||
Serial.print("[ERR] Unknown command: '");
|
||||
Serial.print(cmd);
|
||||
Serial.println("' (use 1, 0, or ?)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
{ type: 'wokwi-led', id: 'led-1', x: 400, y: 120, properties: { color: 'green' } },
|
||||
],
|
||||
wires: [
|
||||
{ id: 'w-led', start: { componentId: 'arduino-uno', pinName: '13' }, end: { componentId: 'led-1', pinName: 'A' }, color: '#00cc00' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'i2c-scanner',
|
||||
title: 'I2C Scanner (TWI)',
|
||||
description: 'Scans the I2C bus and reports all devices found. Tests TWI protocol. Virtual devices at 0x48, 0x50, 0x68 should be detected.',
|
||||
category: 'communication',
|
||||
difficulty: 'intermediate',
|
||||
code: `// I2C Bus Scanner — TWI Protocol Test
|
||||
// Scans all 127 I2C addresses and reports which ones respond with ACK.
|
||||
// The emulator has virtual devices at:
|
||||
// 0x48 = Temperature sensor
|
||||
// 0x50 = EEPROM
|
||||
// 0x68 = DS1307 RTC
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Wire.begin();
|
||||
Serial.begin(9600);
|
||||
|
||||
Serial.println("===========================");
|
||||
Serial.println(" I2C Bus Scanner (TWI)");
|
||||
Serial.println("===========================");
|
||||
Serial.println("Scanning...");
|
||||
Serial.println();
|
||||
|
||||
int devicesFound = 0;
|
||||
|
||||
for (byte addr = 1; addr < 127; addr++) {
|
||||
Wire.beginTransmission(addr);
|
||||
byte error = Wire.endTransmission();
|
||||
|
||||
if (error == 0) {
|
||||
Serial.print(" Device found at 0x");
|
||||
if (addr < 16) Serial.print("0");
|
||||
Serial.print(addr, HEX);
|
||||
|
||||
// Identify known addresses
|
||||
switch (addr) {
|
||||
case 0x27: Serial.print(" (PCF8574 LCD backpack)"); break;
|
||||
case 0x3C: Serial.print(" (SSD1306 OLED)"); break;
|
||||
case 0x48: Serial.print(" (Temperature sensor)"); break;
|
||||
case 0x50: Serial.print(" (EEPROM)"); break;
|
||||
case 0x68: Serial.print(" (DS1307 RTC)"); break;
|
||||
case 0x76: Serial.print(" (BME280 sensor)"); break;
|
||||
case 0x77: Serial.print(" (BMP180/BMP280)"); break;
|
||||
}
|
||||
Serial.println();
|
||||
devicesFound++;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.print("Scan complete. ");
|
||||
Serial.print(devicesFound);
|
||||
Serial.println(" device(s) found.");
|
||||
|
||||
if (devicesFound == 0) {
|
||||
Serial.println("No I2C devices found. Check connections.");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Rescan every 10 seconds
|
||||
delay(10000);
|
||||
Serial.println("\\nRescanning...");
|
||||
setup();
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
{
|
||||
id: 'i2c-rtc-read',
|
||||
title: 'I2C RTC Clock (DS1307)',
|
||||
description: 'Reads time from a virtual DS1307 RTC via I2C and prints it to Serial. Tests TWI read transactions.',
|
||||
category: 'communication',
|
||||
difficulty: 'intermediate',
|
||||
code: `// I2C RTC Reader — DS1307 at address 0x68
|
||||
// Reads hours:minutes:seconds from the virtual RTC
|
||||
// and prints to Serial Monitor every second.
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#define DS1307_ADDR 0x68
|
||||
|
||||
byte bcdToDec(byte val) {
|
||||
return ((val >> 4) * 10) + (val & 0x0F);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Wire.begin();
|
||||
Serial.begin(9600);
|
||||
|
||||
Serial.println("===========================");
|
||||
Serial.println(" DS1307 RTC Reader (I2C)");
|
||||
Serial.println("===========================");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Set register pointer to 0 (seconds)
|
||||
Wire.beginTransmission(DS1307_ADDR);
|
||||
Wire.write(0x00);
|
||||
Wire.endTransmission();
|
||||
|
||||
// Request 7 bytes: sec, min, hr, dow, date, month, year
|
||||
Wire.requestFrom(DS1307_ADDR, 7);
|
||||
|
||||
if (Wire.available() >= 7) {
|
||||
byte sec = bcdToDec(Wire.read() & 0x7F);
|
||||
byte min = bcdToDec(Wire.read());
|
||||
byte hr = bcdToDec(Wire.read() & 0x3F);
|
||||
byte dow = bcdToDec(Wire.read());
|
||||
byte date = bcdToDec(Wire.read());
|
||||
byte month = bcdToDec(Wire.read());
|
||||
byte year = bcdToDec(Wire.read());
|
||||
|
||||
// Print formatted time
|
||||
Serial.print("Time: ");
|
||||
if (hr < 10) Serial.print("0");
|
||||
Serial.print(hr);
|
||||
Serial.print(":");
|
||||
if (min < 10) Serial.print("0");
|
||||
Serial.print(min);
|
||||
Serial.print(":");
|
||||
if (sec < 10) Serial.print("0");
|
||||
Serial.print(sec);
|
||||
|
||||
Serial.print(" Date: ");
|
||||
if (date < 10) Serial.print("0");
|
||||
Serial.print(date);
|
||||
Serial.print("/");
|
||||
if (month < 10) Serial.print("0");
|
||||
Serial.print(month);
|
||||
Serial.print("/20");
|
||||
if (year < 10) Serial.print("0");
|
||||
Serial.println(year);
|
||||
} else {
|
||||
Serial.println("Error: Could not read RTC");
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
{
|
||||
id: 'i2c-eeprom-rw',
|
||||
title: 'I2C EEPROM Read/Write',
|
||||
description: 'Writes data to a virtual I2C EEPROM (0x50) and reads it back. Tests TWI write+read transactions.',
|
||||
category: 'communication',
|
||||
difficulty: 'intermediate',
|
||||
code: `// I2C EEPROM Read/Write Test
|
||||
// Virtual EEPROM at address 0x50
|
||||
// Writes values to registers, then reads them back.
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#define EEPROM_ADDR 0x50
|
||||
|
||||
void writeEEPROM(byte reg, byte value) {
|
||||
Wire.beginTransmission(EEPROM_ADDR);
|
||||
Wire.write(reg); // register address
|
||||
Wire.write(value); // data
|
||||
Wire.endTransmission();
|
||||
delay(5); // EEPROM write cycle time
|
||||
}
|
||||
|
||||
byte readEEPROM(byte reg) {
|
||||
Wire.beginTransmission(EEPROM_ADDR);
|
||||
Wire.write(reg);
|
||||
Wire.endTransmission();
|
||||
|
||||
Wire.requestFrom(EEPROM_ADDR, 1);
|
||||
if (Wire.available()) {
|
||||
return Wire.read();
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Wire.begin();
|
||||
Serial.begin(9600);
|
||||
|
||||
Serial.println("============================");
|
||||
Serial.println(" I2C EEPROM R/W Test (0x50)");
|
||||
Serial.println("============================");
|
||||
Serial.println();
|
||||
|
||||
// Write test pattern
|
||||
Serial.println("Writing test data...");
|
||||
for (byte i = 0; i < 8; i++) {
|
||||
byte value = (i + 1) * 10; // 10, 20, 30, ...
|
||||
writeEEPROM(i, value);
|
||||
Serial.print(" Write reg[");
|
||||
Serial.print(i);
|
||||
Serial.print("] = ");
|
||||
Serial.println(value);
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.println("Reading back...");
|
||||
|
||||
// Read back and verify
|
||||
byte errors = 0;
|
||||
for (byte i = 0; i < 8; i++) {
|
||||
byte expected = (i + 1) * 10;
|
||||
byte actual = readEEPROM(i);
|
||||
Serial.print(" Read reg[");
|
||||
Serial.print(i);
|
||||
Serial.print("] = ");
|
||||
Serial.print(actual);
|
||||
|
||||
if (actual == expected) {
|
||||
Serial.println(" [OK]");
|
||||
} else {
|
||||
Serial.print(" [FAIL] expected ");
|
||||
Serial.println(expected);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
if (errors == 0) {
|
||||
Serial.println("All tests PASSED!");
|
||||
} else {
|
||||
Serial.print(errors);
|
||||
Serial.println(" test(s) FAILED.");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Nothing to do
|
||||
delay(1000);
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
{
|
||||
id: 'spi-loopback',
|
||||
title: 'SPI Loopback Test',
|
||||
description: 'Tests SPI by sending bytes and reading responses. Demonstrates MOSI/MISO/SCK/SS protocol.',
|
||||
category: 'communication',
|
||||
difficulty: 'intermediate',
|
||||
code: `// SPI Loopback Test
|
||||
// Sends bytes via SPI and logs the exchange.
|
||||
// Without a physical slave, the emulator returns the sent byte.
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#define SS_PIN 10
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("========================");
|
||||
Serial.println(" SPI Protocol Test");
|
||||
Serial.println("========================");
|
||||
Serial.println();
|
||||
|
||||
pinMode(SS_PIN, OUTPUT);
|
||||
digitalWrite(SS_PIN, HIGH);
|
||||
SPI.begin();
|
||||
SPI.setClockDivider(SPI_CLOCK_DIV16);
|
||||
|
||||
Serial.println("SPI initialized.");
|
||||
Serial.print("Clock divider: 16 (");
|
||||
Serial.print(F_CPU / 16);
|
||||
Serial.println(" Hz)");
|
||||
Serial.println();
|
||||
|
||||
// Send test pattern
|
||||
Serial.println("Sending test pattern via SPI:");
|
||||
byte testData[] = {0xAA, 0x55, 0xFF, 0x00, 0x42, 0xDE, 0xAD, 0xBE};
|
||||
|
||||
digitalWrite(SS_PIN, LOW); // Select slave
|
||||
|
||||
for (int i = 0; i < sizeof(testData); i++) {
|
||||
byte sent = testData[i];
|
||||
byte received = SPI.transfer(sent);
|
||||
|
||||
Serial.print(" TX: 0x");
|
||||
if (sent < 16) Serial.print("0");
|
||||
Serial.print(sent, HEX);
|
||||
Serial.print(" RX: 0x");
|
||||
if (received < 16) Serial.print("0");
|
||||
Serial.print(received, HEX);
|
||||
|
||||
if (sent == received) {
|
||||
Serial.println(" (loopback OK)");
|
||||
} else {
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
digitalWrite(SS_PIN, HIGH); // Deselect slave
|
||||
|
||||
Serial.println();
|
||||
Serial.println("SPI test complete.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
delay(1000);
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
{
|
||||
id: 'multi-protocol',
|
||||
title: 'Multi-Protocol Demo',
|
||||
description: 'Uses Serial + I2C + SPI together. Reads RTC via I2C, sends SPI data, and logs everything to Serial.',
|
||||
category: 'communication',
|
||||
difficulty: 'advanced',
|
||||
code: `// Multi-Protocol Demo: Serial + I2C + SPI
|
||||
// Demonstrates all three major communication protocols
|
||||
// working together in a single sketch.
|
||||
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#define DS1307_ADDR 0x68
|
||||
#define EEPROM_ADDR 0x50
|
||||
#define SS_PIN 10
|
||||
|
||||
byte bcdToDec(byte val) {
|
||||
return ((val >> 4) * 10) + (val & 0x0F);
|
||||
}
|
||||
|
||||
void readRTC(byte &hr, byte &min, byte &sec) {
|
||||
Wire.beginTransmission(DS1307_ADDR);
|
||||
Wire.write(0x00);
|
||||
Wire.endTransmission();
|
||||
Wire.requestFrom(DS1307_ADDR, 3);
|
||||
sec = bcdToDec(Wire.read() & 0x7F);
|
||||
min = bcdToDec(Wire.read());
|
||||
hr = bcdToDec(Wire.read() & 0x3F);
|
||||
}
|
||||
|
||||
void writeEEPROM(byte reg, byte value) {
|
||||
Wire.beginTransmission(EEPROM_ADDR);
|
||||
Wire.write(reg);
|
||||
Wire.write(value);
|
||||
Wire.endTransmission();
|
||||
delay(5);
|
||||
}
|
||||
|
||||
byte readEEPROM(byte reg) {
|
||||
Wire.beginTransmission(EEPROM_ADDR);
|
||||
Wire.write(reg);
|
||||
Wire.endTransmission();
|
||||
Wire.requestFrom(EEPROM_ADDR, 1);
|
||||
return Wire.available() ? Wire.read() : 0xFF;
|
||||
}
|
||||
|
||||
byte spiTransfer(byte data) {
|
||||
digitalWrite(SS_PIN, LOW);
|
||||
byte result = SPI.transfer(data);
|
||||
digitalWrite(SS_PIN, HIGH);
|
||||
return result;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Wire.begin();
|
||||
pinMode(SS_PIN, OUTPUT);
|
||||
digitalWrite(SS_PIN, HIGH);
|
||||
SPI.begin();
|
||||
|
||||
Serial.println("===================================");
|
||||
Serial.println(" Multi-Protocol Demo");
|
||||
Serial.println(" Serial (USART) + I2C (TWI) + SPI");
|
||||
Serial.println("===================================");
|
||||
Serial.println();
|
||||
|
||||
// ── I2C: Scan bus ──
|
||||
Serial.println("[I2C] Scanning bus...");
|
||||
int found = 0;
|
||||
for (byte addr = 1; addr < 127; addr++) {
|
||||
Wire.beginTransmission(addr);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
Serial.print(" Found device at 0x");
|
||||
if (addr < 16) Serial.print("0");
|
||||
Serial.println(addr, HEX);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
Serial.print(" ");
|
||||
Serial.print(found);
|
||||
Serial.println(" device(s) on I2C bus.");
|
||||
Serial.println();
|
||||
|
||||
// ── I2C: Write/read EEPROM ──
|
||||
Serial.println("[I2C] EEPROM write/read test:");
|
||||
writeEEPROM(0, 42);
|
||||
writeEEPROM(1, 99);
|
||||
byte v0 = readEEPROM(0);
|
||||
byte v1 = readEEPROM(1);
|
||||
Serial.print(" Wrote 42, read ");
|
||||
Serial.print(v0);
|
||||
Serial.println(v0 == 42 ? " [OK]" : " [FAIL]");
|
||||
Serial.print(" Wrote 99, read ");
|
||||
Serial.print(v1);
|
||||
Serial.println(v1 == 99 ? " [OK]" : " [FAIL]");
|
||||
Serial.println();
|
||||
|
||||
// ── SPI: Transfer test ──
|
||||
Serial.println("[SPI] Transfer test:");
|
||||
byte spiData[] = {0xAA, 0x55, 0x42};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
byte rx = spiTransfer(spiData[i]);
|
||||
Serial.print(" TX=0x");
|
||||
if (spiData[i] < 16) Serial.print("0");
|
||||
Serial.print(spiData[i], HEX);
|
||||
Serial.print(" RX=0x");
|
||||
if (rx < 16) Serial.print("0");
|
||||
Serial.println(rx, HEX);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
Serial.println("Setup complete. Reading RTC...");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// ── Serial: Print RTC time every 2 seconds ──
|
||||
byte hr, min, sec;
|
||||
readRTC(hr, min, sec);
|
||||
|
||||
Serial.print("[RTC] ");
|
||||
if (hr < 10) Serial.print("0");
|
||||
Serial.print(hr);
|
||||
Serial.print(":");
|
||||
if (min < 10) Serial.print("0");
|
||||
Serial.print(min);
|
||||
Serial.print(":");
|
||||
if (sec < 10) Serial.print("0");
|
||||
Serial.print(sec);
|
||||
|
||||
Serial.print(" | Uptime: ");
|
||||
Serial.print(millis() / 1000);
|
||||
Serial.println("s");
|
||||
|
||||
delay(2000);
|
||||
}
|
||||
`,
|
||||
components: [
|
||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||
],
|
||||
wires: [],
|
||||
},
|
||||
];
|
||||
|
||||
// Get examples by category
|
||||
|
|
|
|||
|
|
@ -7,12 +7,17 @@ import { Link } from 'react-router-dom';
|
|||
import { CodeEditor } from '../components/editor/CodeEditor';
|
||||
import { EditorToolbar } from '../components/editor/EditorToolbar';
|
||||
import { SimulatorCanvas } from '../components/simulator/SimulatorCanvas';
|
||||
import { SerialMonitor } from '../components/simulator/SerialMonitor';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import '../App.css';
|
||||
|
||||
export const EditorPage: React.FC = () => {
|
||||
const [editorWidthPct, setEditorWidthPct] = useState(45);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const resizingRef = useRef(false);
|
||||
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
|
||||
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
|
||||
const [serialHeightPct, setSerialHeightPct] = useState(30);
|
||||
|
||||
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -58,6 +63,29 @@ export const EditorPage: React.FC = () => {
|
|||
</svg>
|
||||
Examples
|
||||
</Link>
|
||||
<button
|
||||
onClick={toggleSerialMonitor}
|
||||
className="serial-monitor-toggle"
|
||||
title="Toggle Serial Monitor"
|
||||
style={{
|
||||
background: serialMonitorOpen ? '#0e639c' : 'transparent',
|
||||
border: '1px solid #555',
|
||||
color: '#ccc',
|
||||
padding: '4px 10px',
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||
<path d="M8 21h8M12 17v4" />
|
||||
</svg>
|
||||
Serial
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -74,9 +102,16 @@ export const EditorPage: React.FC = () => {
|
|||
<div className="resize-handle-grip" />
|
||||
</div>
|
||||
|
||||
<div className="simulator-panel" style={{ width: `${100 - editorWidthPct}%` }}>
|
||||
<div className="simulator-panel" style={{ width: `${100 - editorWidthPct}%`, display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ flex: serialMonitorOpen ? `0 0 ${100 - serialHeightPct}%` : '1 1 auto', overflow: 'hidden', position: 'relative' }}>
|
||||
<SimulatorCanvas />
|
||||
</div>
|
||||
{serialMonitorOpen && (
|
||||
<div style={{ flex: `0 0 ${serialHeightPct}%`, minHeight: 100, display: 'flex', flexDirection: 'column' }}>
|
||||
<SerialMonitor />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { CPU, AVRTimer, timer0Config, timer1Config, timer2Config, AVRUSART, usart0Config, AVRIOPort, portBConfig, portCConfig, portDConfig, avrInstruction, AVRADC, adcConfig, AVRSPI, spiConfig } from 'avr8js';
|
||||
import { CPU, AVRTimer, timer0Config, timer1Config, timer2Config, AVRUSART, usart0Config, AVRIOPort, portBConfig, portCConfig, portDConfig, avrInstruction, AVRADC, adcConfig, AVRSPI, spiConfig, AVRTWI, twiConfig } from 'avr8js';
|
||||
import { PinManager } from './PinManager';
|
||||
import { hexToUint8Array } from '../utils/hexParser';
|
||||
import { I2CBusManager } from './I2CBusManager';
|
||||
import type { I2CDevice } from './I2CBusManager';
|
||||
|
||||
/**
|
||||
* AVRSimulator - Emulates Arduino Uno (ATmega328p) using avr8js
|
||||
|
|
@ -34,11 +36,17 @@ export class AVRSimulator {
|
|||
private portD: AVRIOPort | null = null;
|
||||
private adc: AVRADC | null = null;
|
||||
public spi: AVRSPI | null = null;
|
||||
public usart: AVRUSART | null = null;
|
||||
public twi: AVRTWI | null = null;
|
||||
public i2cBus: I2CBusManager | null = null;
|
||||
private program: Uint16Array | null = null;
|
||||
private running = false;
|
||||
private animationFrame: number | null = null;
|
||||
public pinManager: PinManager;
|
||||
private speed = 1.0; // Simulation speed multiplier
|
||||
|
||||
/** Serial output buffer — subscribers receive each byte or line */
|
||||
public onSerialData: ((char: string) => void) | null = null;
|
||||
private lastPortBValue = 0;
|
||||
private lastPortCValue = 0;
|
||||
private lastPortDValue = 0;
|
||||
|
|
@ -79,12 +87,25 @@ export class AVRSimulator {
|
|||
this.spi!.completeTransfer(value);
|
||||
};
|
||||
|
||||
// USART (Serial) — hook onByteTransmit to forward output
|
||||
this.usart = new AVRUSART(this.cpu, usart0Config, 16000000);
|
||||
this.usart.onByteTransmit = (value: number) => {
|
||||
if (this.onSerialData) {
|
||||
this.onSerialData(String.fromCharCode(value));
|
||||
}
|
||||
};
|
||||
|
||||
// TWI (I2C)
|
||||
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
||||
this.i2cBus = new I2CBusManager(this.twi);
|
||||
|
||||
this.peripherals = [
|
||||
new AVRTimer(this.cpu, timer0Config),
|
||||
new AVRTimer(this.cpu, timer1Config),
|
||||
new AVRTimer(this.cpu, timer2Config),
|
||||
new AVRUSART(this.cpu, usart0Config, 16000000),
|
||||
this.usart,
|
||||
this.spi,
|
||||
this.twi,
|
||||
];
|
||||
|
||||
// Initialize ADC (analogRead support)
|
||||
|
|
@ -233,11 +254,25 @@ export class AVRSimulator {
|
|||
console.log('Resetting AVR CPU...');
|
||||
|
||||
this.cpu = new CPU(this.program);
|
||||
|
||||
this.spi = new AVRSPI(this.cpu, spiConfig, 16000000);
|
||||
this.spi.onByte = (value) => { this.spi!.completeTransfer(value); };
|
||||
|
||||
this.usart = new AVRUSART(this.cpu, usart0Config, 16000000);
|
||||
this.usart.onByteTransmit = (value: number) => {
|
||||
if (this.onSerialData) this.onSerialData(String.fromCharCode(value));
|
||||
};
|
||||
|
||||
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
||||
this.i2cBus = new I2CBusManager(this.twi);
|
||||
|
||||
this.peripherals = [
|
||||
new AVRTimer(this.cpu, timer0Config),
|
||||
new AVRTimer(this.cpu, timer1Config),
|
||||
new AVRTimer(this.cpu, timer2Config),
|
||||
new AVRUSART(this.cpu, usart0Config, 16000000),
|
||||
this.usart,
|
||||
this.spi,
|
||||
this.twi,
|
||||
];
|
||||
this.adc = new AVRADC(this.cpu, adcConfig);
|
||||
|
||||
|
|
@ -287,4 +322,23 @@ export class AVRSimulator {
|
|||
this.portC.setPin(arduinoPin - 14, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a byte to the Arduino serial port (RX) — as if typed in the Serial Monitor.
|
||||
*/
|
||||
serialWrite(text: string): void {
|
||||
if (!this.usart) return;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
this.usart.writeByte(text.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a virtual I2C device on the bus (e.g. RTC, sensor).
|
||||
*/
|
||||
addI2CDevice(device: I2CDevice): void {
|
||||
if (this.i2cBus) {
|
||||
this.i2cBus.addDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* I2C Bus Manager — virtual I2C devices that attach to avr8js AVRTWI
|
||||
*
|
||||
* Each device registers at a 7-bit I2C address. When the Arduino sketch
|
||||
* does Wire.beginTransmission(addr) / Wire.requestFrom(addr, ...), the
|
||||
* TWI event handler routes events to the matching virtual device.
|
||||
*/
|
||||
|
||||
import type { AVRTWI, TWIEventHandler } from 'avr8js';
|
||||
|
||||
// ── Virtual I2C device interface ────────────────────────────────────────────
|
||||
|
||||
export interface I2CDevice {
|
||||
/** 7-bit I2C address (e.g. 0x27 for PCF8574 LCD backpack, 0x3C for SSD1306) */
|
||||
address: number;
|
||||
/** Called when master sends a byte after addressing this device for write */
|
||||
writeByte(value: number): boolean; // return true for ACK
|
||||
/** Called when master requests a byte from this device (read mode) */
|
||||
readByte(): number;
|
||||
/** Optional: called on STOP condition */
|
||||
stop?(): void;
|
||||
}
|
||||
|
||||
// ── I2C Bus Manager (TWIEventHandler for avr8js) ───────────────────────────
|
||||
|
||||
export class I2CBusManager implements TWIEventHandler {
|
||||
private devices: Map<number, I2CDevice> = new Map();
|
||||
private activeDevice: I2CDevice | null = null;
|
||||
private writeMode = true;
|
||||
|
||||
constructor(private twi: AVRTWI) {
|
||||
twi.eventHandler = this;
|
||||
}
|
||||
|
||||
/** Register a virtual I2C device on the bus */
|
||||
addDevice(device: I2CDevice): void {
|
||||
this.devices.set(device.address, device);
|
||||
}
|
||||
|
||||
/** Remove a device by address */
|
||||
removeDevice(address: number): void {
|
||||
this.devices.delete(address);
|
||||
}
|
||||
|
||||
// ── TWIEventHandler implementation ──────────────────────────────────────
|
||||
|
||||
start(_repeated: boolean): void {
|
||||
this.twi.completeStart();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.activeDevice?.stop) this.activeDevice.stop();
|
||||
this.activeDevice = null;
|
||||
this.twi.completeStop();
|
||||
}
|
||||
|
||||
connectToSlave(addr: number, write: boolean): void {
|
||||
const device = this.devices.get(addr);
|
||||
if (device) {
|
||||
this.activeDevice = device;
|
||||
this.writeMode = write;
|
||||
this.twi.completeConnect(true); // ACK
|
||||
} else {
|
||||
this.activeDevice = null;
|
||||
this.twi.completeConnect(false); // NACK — no such address
|
||||
}
|
||||
}
|
||||
|
||||
writeByte(value: number): void {
|
||||
if (this.activeDevice) {
|
||||
const ack = this.activeDevice.writeByte(value);
|
||||
this.twi.completeWrite(ack);
|
||||
} else {
|
||||
this.twi.completeWrite(false);
|
||||
}
|
||||
}
|
||||
|
||||
readByte(_ack: boolean): void {
|
||||
if (this.activeDevice) {
|
||||
const value = this.activeDevice.readByte();
|
||||
this.twi.completeRead(value);
|
||||
} else {
|
||||
this.twi.completeRead(0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Built-in virtual I2C devices ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Generic I2C memory / register device.
|
||||
* Emulates a device with 256 byte registers.
|
||||
* First write byte = register address, subsequent bytes = data.
|
||||
* Reads return register contents sequentially.
|
||||
*
|
||||
* Used to test I2C communication without a specific device implementation.
|
||||
*/
|
||||
export class I2CMemoryDevice implements I2CDevice {
|
||||
public registers = new Uint8Array(256);
|
||||
private regPointer = 0;
|
||||
private firstByte = true;
|
||||
|
||||
/** Callback fired whenever a register is written */
|
||||
public onRegisterWrite: ((reg: number, value: number) => void) | null = null;
|
||||
|
||||
constructor(public address: number) {}
|
||||
|
||||
writeByte(value: number): boolean {
|
||||
if (this.firstByte) {
|
||||
this.regPointer = value;
|
||||
this.firstByte = false;
|
||||
} else {
|
||||
this.registers[this.regPointer] = value;
|
||||
if (this.onRegisterWrite) {
|
||||
this.onRegisterWrite(this.regPointer, value);
|
||||
}
|
||||
this.regPointer = (this.regPointer + 1) & 0xFF;
|
||||
}
|
||||
return true; // ACK
|
||||
}
|
||||
|
||||
readByte(): number {
|
||||
const value = this.registers[this.regPointer];
|
||||
this.regPointer = (this.regPointer + 1) & 0xFF;
|
||||
return value;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.firstByte = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual DS1307 RTC — returns system time via I2C (address 0x68).
|
||||
* Supports Wire.requestFrom(0x68, 7) to read seconds..year in BCD.
|
||||
*/
|
||||
export class VirtualDS1307 implements I2CDevice {
|
||||
public address = 0x68;
|
||||
private regPointer = 0;
|
||||
private firstByte = true;
|
||||
|
||||
private toBCD(n: number): number {
|
||||
return ((Math.floor(n / 10) & 0xF) << 4) | (n % 10 & 0xF);
|
||||
}
|
||||
|
||||
writeByte(value: number): boolean {
|
||||
if (this.firstByte) {
|
||||
this.regPointer = value;
|
||||
this.firstByte = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
readByte(): number {
|
||||
const now = new Date();
|
||||
let val = 0;
|
||||
switch (this.regPointer) {
|
||||
case 0: val = this.toBCD(now.getSeconds()); break; // seconds
|
||||
case 1: val = this.toBCD(now.getMinutes()); break; // minutes
|
||||
case 2: val = this.toBCD(now.getHours()); break; // hours (24h)
|
||||
case 3: val = this.toBCD(now.getDay() + 1); break; // day of week (1=Sun)
|
||||
case 4: val = this.toBCD(now.getDate()); break; // date
|
||||
case 5: val = this.toBCD(now.getMonth() + 1); break; // month
|
||||
case 6: val = this.toBCD(now.getFullYear() % 100); break; // year
|
||||
default: val = 0;
|
||||
}
|
||||
this.regPointer = (this.regPointer + 1) & 0x3F;
|
||||
return val;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.firstByte = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual temperature / humidity sensor (address 0x48).
|
||||
* Returns fixed temperature (configurable) and humidity.
|
||||
*/
|
||||
export class VirtualTempSensor implements I2CDevice {
|
||||
public address = 0x48;
|
||||
private regPointer = 0;
|
||||
private firstByte = true;
|
||||
|
||||
/** Temperature in degrees C * 100 (e.g. 2350 = 23.50 C) */
|
||||
public temperature = 2350;
|
||||
/** Humidity in % * 100 */
|
||||
public humidity = 5500;
|
||||
|
||||
writeByte(value: number): boolean {
|
||||
if (this.firstByte) {
|
||||
this.regPointer = value;
|
||||
this.firstByte = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
readByte(): number {
|
||||
let val = 0;
|
||||
// Register 0: temp high byte, 1: temp low byte, 2: humidity high, 3: humidity low
|
||||
switch (this.regPointer) {
|
||||
case 0: val = (this.temperature >> 8) & 0xFF; break;
|
||||
case 1: val = this.temperature & 0xFF; break;
|
||||
case 2: val = (this.humidity >> 8) & 0xFF; break;
|
||||
case 3: val = this.humidity & 0xFF; break;
|
||||
default: val = 0xFF;
|
||||
}
|
||||
this.regPointer = (this.regPointer + 1) & 0xFF;
|
||||
return val;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.firstByte = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { create } from 'zustand';
|
|||
import { AVRSimulator } from '../simulation/AVRSimulator';
|
||||
import { RP2040Simulator } from '../simulation/RP2040Simulator';
|
||||
import { PinManager } from '../simulation/PinManager';
|
||||
import { VirtualDS1307, VirtualTempSensor, I2CMemoryDevice } from '../simulation/I2CBusManager';
|
||||
import type { Wire, WireInProgress, WireEndpoint } from '../types/wire';
|
||||
import { calculatePinPosition } from '../utils/pinPositionCalculator';
|
||||
|
||||
|
|
@ -47,6 +48,10 @@ interface SimulatorState {
|
|||
selectedWireId: string | null;
|
||||
wireInProgress: WireInProgress | null;
|
||||
|
||||
// Serial monitor state
|
||||
serialOutput: string;
|
||||
serialMonitorOpen: boolean;
|
||||
|
||||
// Actions
|
||||
initSimulator: () => void;
|
||||
loadHex: (hex: string) => void;
|
||||
|
|
@ -82,6 +87,11 @@ interface SimulatorState {
|
|||
// Wire position updates (auto-update when components move)
|
||||
updateWirePositions: (componentId: string) => void;
|
||||
recalculateAllWirePositions: () => void;
|
||||
|
||||
// Serial monitor
|
||||
toggleSerialMonitor: () => void;
|
||||
serialWrite: (text: string) => void;
|
||||
clearSerialOutput: () => void;
|
||||
}
|
||||
|
||||
export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||
|
|
@ -152,6 +162,8 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
],
|
||||
selectedWireId: null,
|
||||
wireInProgress: null,
|
||||
serialOutput: '',
|
||||
serialMonitorOpen: false,
|
||||
|
||||
setBoardType: (type: BoardType) => {
|
||||
const { running } = get();
|
||||
|
|
@ -161,7 +173,12 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
const simulator = type === 'arduino-uno'
|
||||
? new AVRSimulator(pinManager)
|
||||
: new RP2040Simulator(pinManager);
|
||||
set({ boardType: type, simulator, compiledHex: null });
|
||||
if (simulator instanceof AVRSimulator) {
|
||||
simulator.onSerialData = (char: string) => {
|
||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||
};
|
||||
}
|
||||
set({ boardType: type, simulator, compiledHex: null, serialOutput: '' });
|
||||
console.log(`Board switched to: ${type}`);
|
||||
},
|
||||
|
||||
|
|
@ -170,7 +187,12 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
const simulator = boardType === 'arduino-uno'
|
||||
? new AVRSimulator(pinManager)
|
||||
: new RP2040Simulator(pinManager);
|
||||
set({ simulator });
|
||||
if (simulator instanceof AVRSimulator) {
|
||||
simulator.onSerialData = (char: string) => {
|
||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||
};
|
||||
}
|
||||
set({ simulator, serialOutput: '' });
|
||||
console.log(`Simulator initialized: ${boardType}`);
|
||||
},
|
||||
|
||||
|
|
@ -207,6 +229,12 @@ export const useSimulatorStore = create<SimulatorState>((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)); // generic EEPROM at 0x50
|
||||
}
|
||||
simulator.start();
|
||||
set({ running: true });
|
||||
}
|
||||
|
|
@ -224,7 +252,13 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
const { simulator } = get();
|
||||
if (simulator) {
|
||||
simulator.reset();
|
||||
set({ running: false });
|
||||
// Re-wire serial callback after reset
|
||||
if (simulator instanceof AVRSimulator) {
|
||||
simulator.onSerialData = (char: string) => {
|
||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||
};
|
||||
}
|
||||
set({ running: false, serialOutput: '' });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -458,5 +492,21 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
|
||||
set({ wires: updatedWires });
|
||||
},
|
||||
|
||||
// Serial monitor actions
|
||||
toggleSerialMonitor: () => {
|
||||
set((s) => ({ serialMonitorOpen: !s.serialMonitorOpen }));
|
||||
},
|
||||
|
||||
serialWrite: (text: string) => {
|
||||
const { simulator } = get();
|
||||
if (simulator && simulator instanceof AVRSimulator) {
|
||||
simulator.serialWrite(text);
|
||||
}
|
||||
},
|
||||
|
||||
clearSerialOutput: () => {
|
||||
set({ serialOutput: '' });
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue