feat: enhance RP2040 and AVR simulators with serial baud rate handling; update editor toolbar and library manager modal for improved state management and UI
This commit is contained in:
parent
4ba2ccb877
commit
9b8747349f
|
|
@ -224,6 +224,13 @@ class ArduinoCLIService:
|
||||||
|
|
||||||
# arduino-cli requires sketch name to match directory name
|
# arduino-cli requires sketch name to match directory name
|
||||||
sketch_file = sketch_dir / "sketch.ino"
|
sketch_file = sketch_dir / "sketch.ino"
|
||||||
|
|
||||||
|
# For RP2040 boards, redirect Serial (USB CDC) to Serial1 (UART0)
|
||||||
|
# The emulator captures UART0 output, but the arduino-pico core
|
||||||
|
# defaults Serial to USB CDC which isn't emulated.
|
||||||
|
if "rp2040" in board_fqbn:
|
||||||
|
code = "#define Serial Serial1\n" + code
|
||||||
|
|
||||||
sketch_file.write_text(code)
|
sketch_file.write_text(code)
|
||||||
print(f"Created sketch file: {sketch_file}")
|
print(f"Created sketch file: {sketch_file}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,18 @@ import { useEditorStore } from '../../store/useEditorStore';
|
||||||
import { useSimulatorStore, BOARD_FQBN, BOARD_LABELS } from '../../store/useSimulatorStore';
|
import { useSimulatorStore, BOARD_FQBN, BOARD_LABELS } from '../../store/useSimulatorStore';
|
||||||
import { compileCode } from '../../services/compilation';
|
import { compileCode } from '../../services/compilation';
|
||||||
import { LibraryManagerModal } from '../simulator/LibraryManagerModal';
|
import { LibraryManagerModal } from '../simulator/LibraryManagerModal';
|
||||||
import { CompilationConsole } from './CompilationConsole';
|
|
||||||
import { parseCompileResult } from '../../utils/compilationLogger';
|
import { parseCompileResult } from '../../utils/compilationLogger';
|
||||||
import type { CompilationLog } from '../../utils/compilationLogger';
|
import type { CompilationLog } from '../../utils/compilationLogger';
|
||||||
import './EditorToolbar.css';
|
import './EditorToolbar.css';
|
||||||
|
|
||||||
export const EditorToolbar = () => {
|
interface EditorToolbarProps {
|
||||||
|
consoleOpen: boolean;
|
||||||
|
setConsoleOpen: (open: boolean | ((v: boolean) => boolean)) => void;
|
||||||
|
compileLogs: CompilationLog[];
|
||||||
|
setCompileLogs: (logs: CompilationLog[] | ((prev: CompilationLog[]) => CompilationLog[])) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compileLogs, setCompileLogs }: EditorToolbarProps) => {
|
||||||
const { code } = useEditorStore();
|
const { code } = useEditorStore();
|
||||||
const {
|
const {
|
||||||
boardType,
|
boardType,
|
||||||
|
|
@ -23,12 +29,10 @@ export const EditorToolbar = () => {
|
||||||
const [compiling, setCompiling] = useState(false);
|
const [compiling, setCompiling] = useState(false);
|
||||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
const [libManagerOpen, setLibManagerOpen] = useState(false);
|
const [libManagerOpen, setLibManagerOpen] = useState(false);
|
||||||
const [consoleOpen, setConsoleOpen] = useState(false);
|
|
||||||
const [compileLogs, setCompileLogs] = useState<CompilationLog[]>([]);
|
|
||||||
|
|
||||||
const addLog = useCallback((log: CompilationLog) => {
|
const addLog = useCallback((log: CompilationLog) => {
|
||||||
setCompileLogs((prev) => [...prev, log]);
|
setCompileLogs((prev: CompilationLog[]) => [...prev, log]);
|
||||||
}, []);
|
}, [setCompileLogs]);
|
||||||
|
|
||||||
const handleCompile = async () => {
|
const handleCompile = async () => {
|
||||||
setCompiling(true);
|
setCompiling(true);
|
||||||
|
|
@ -45,7 +49,7 @@ export const EditorToolbar = () => {
|
||||||
|
|
||||||
// Parse the full result into log entries
|
// Parse the full result into log entries
|
||||||
const resultLogs = parseCompileResult(result, boardLabel);
|
const resultLogs = parseCompileResult(result, boardLabel);
|
||||||
setCompileLogs((prev) => [...prev, ...resultLogs]);
|
setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (result.hex_content) {
|
if (result.hex_content) {
|
||||||
|
|
@ -203,18 +207,6 @@ export const EditorToolbar = () => {
|
||||||
<div className="toolbar-error-detail">{message.text}</div>
|
<div className="toolbar-error-detail">{message.text}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Compilation Console */}
|
|
||||||
{consoleOpen && (
|
|
||||||
<div style={{ height: 200, flexShrink: 0 }}>
|
|
||||||
<CompilationConsole
|
|
||||||
isOpen={consoleOpen}
|
|
||||||
onClose={() => setConsoleOpen(false)}
|
|
||||||
logs={compileLogs}
|
|
||||||
onClear={() => setCompileLogs([])}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,12 @@
|
||||||
animation: spin 0.8s linear infinite;
|
animation: spin 0.8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lib-spinner-center {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|
|
||||||
|
|
@ -33,26 +33,29 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Fetch installed list when modal opens (to cross-reference in search tab)
|
// Reset state when modal closes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (!isOpen) {
|
||||||
fetchInstalled();
|
setSearchQuery('');
|
||||||
|
setSearchResults([]);
|
||||||
|
setStatusMsg(null);
|
||||||
}
|
}
|
||||||
}, [isOpen, fetchInstalled]);
|
}, [isOpen]);
|
||||||
|
|
||||||
// Also refresh when switching to the installed tab
|
// Fetch installed list when modal opens or switching to installed tab
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && activeTab === 'installed') {
|
if (isOpen && activeTab === 'installed') fetchInstalled();
|
||||||
fetchInstalled();
|
|
||||||
}
|
|
||||||
}, [isOpen, activeTab, fetchInstalled]);
|
}, [isOpen, activeTab, fetchInstalled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchQuery.trim()) {
|
if (isOpen) fetchInstalled();
|
||||||
setSearchResults([]);
|
}, [isOpen, fetchInstalled]);
|
||||||
return;
|
|
||||||
}
|
// Search: immediate on open (empty query), debounced when typing
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||||
|
const delay = searchQuery ? 400 : 0;
|
||||||
debounceRef.current = setTimeout(async () => {
|
debounceRef.current = setTimeout(async () => {
|
||||||
setLoadingSearch(true);
|
setLoadingSearch(true);
|
||||||
setStatusMsg(null);
|
setStatusMsg(null);
|
||||||
|
|
@ -65,10 +68,10 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingSearch(false);
|
setLoadingSearch(false);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, delay);
|
||||||
|
|
||||||
return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
|
return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
|
||||||
}, [searchQuery]);
|
}, [searchQuery, isOpen]);
|
||||||
|
|
||||||
const handleInstall = async (libName: string) => {
|
const handleInstall = async (libName: string) => {
|
||||||
setInstallingLib(libName);
|
setInstallingLib(libName);
|
||||||
|
|
@ -89,9 +92,6 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setStatusMsg(null);
|
|
||||||
setSearchQuery('');
|
|
||||||
setSearchResults([]);
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -193,18 +193,20 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="lib-list">
|
<div className="lib-list">
|
||||||
{!searchQuery.trim() && (
|
{loadingSearch && (
|
||||||
<div className="lib-empty">
|
<div className="lib-empty">
|
||||||
<p>Type a library name to search</p>
|
<svg className="lib-spinner lib-spinner-center" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
||||||
<p className="lib-empty-sub">e.g. "ArduinoJson", "Servo", "LiquidCrystal"</p>
|
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||||
|
</svg>
|
||||||
|
<p className="lib-empty-sub">{searchQuery ? `Searching "${searchQuery}"…` : 'Loading libraries…'}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{searchQuery.trim() && searchResults.length === 0 && !loadingSearch && (
|
{!loadingSearch && searchResults.length === 0 && (
|
||||||
<div className="lib-empty">
|
<div className="lib-empty">
|
||||||
<p>No libraries found for "{searchQuery}"</p>
|
<p>{searchQuery ? `No libraries found for "${searchQuery}"` : 'No libraries available'}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{searchResults.map((lib, i) => (
|
{!loadingSearch && searchResults.map((lib, i) => (
|
||||||
<div key={i} className="lib-item">
|
<div key={i} className="lib-item">
|
||||||
<div className="lib-item-info">
|
<div className="lib-item-info">
|
||||||
<div className="lib-item-header">
|
<div className="lib-item-header">
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { useSimulatorStore } from '../../store/useSimulatorStore';
|
||||||
|
|
||||||
export const SerialMonitor: React.FC = () => {
|
export const SerialMonitor: React.FC = () => {
|
||||||
const serialOutput = useSimulatorStore((s) => s.serialOutput);
|
const serialOutput = useSimulatorStore((s) => s.serialOutput);
|
||||||
|
const serialBaudRate = useSimulatorStore((s) => s.serialBaudRate);
|
||||||
const running = useSimulatorStore((s) => s.running);
|
const running = useSimulatorStore((s) => s.running);
|
||||||
const serialWrite = useSimulatorStore((s) => s.serialWrite);
|
const serialWrite = useSimulatorStore((s) => s.serialWrite);
|
||||||
const clearSerialOutput = useSimulatorStore((s) => s.clearSerialOutput);
|
const clearSerialOutput = useSimulatorStore((s) => s.clearSerialOutput);
|
||||||
|
|
@ -49,6 +50,9 @@ export const SerialMonitor: React.FC = () => {
|
||||||
<div style={styles.header}>
|
<div style={styles.header}>
|
||||||
<span style={styles.title}>Serial Monitor</span>
|
<span style={styles.title}>Serial Monitor</span>
|
||||||
<div style={styles.headerControls}>
|
<div style={styles.headerControls}>
|
||||||
|
{serialBaudRate > 0 && (
|
||||||
|
<span style={styles.baudRate}>{serialBaudRate.toLocaleString()} baud</span>
|
||||||
|
)}
|
||||||
<label style={styles.autoscrollLabel}>
|
<label style={styles.autoscrollLabel}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -127,6 +131,15 @@ const styles: Record<string, React.CSSProperties> = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
|
baudRate: {
|
||||||
|
color: '#569cd6',
|
||||||
|
fontSize: 11,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
background: '#1e1e1e',
|
||||||
|
border: '1px solid #3a3a3a',
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: '1px 6px',
|
||||||
|
},
|
||||||
autoscrollLabel: {
|
autoscrollLabel: {
|
||||||
color: '#999',
|
color: '#999',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,35 @@ import React, { useRef, useState, useCallback } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { CodeEditor } from '../components/editor/CodeEditor';
|
import { CodeEditor } from '../components/editor/CodeEditor';
|
||||||
import { EditorToolbar } from '../components/editor/EditorToolbar';
|
import { EditorToolbar } from '../components/editor/EditorToolbar';
|
||||||
|
import { CompilationConsole } from '../components/editor/CompilationConsole';
|
||||||
import { SimulatorCanvas } from '../components/simulator/SimulatorCanvas';
|
import { SimulatorCanvas } from '../components/simulator/SimulatorCanvas';
|
||||||
import { SerialMonitor } from '../components/simulator/SerialMonitor';
|
import { SerialMonitor } from '../components/simulator/SerialMonitor';
|
||||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||||
|
import type { CompilationLog } from '../utils/compilationLogger';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
|
|
||||||
|
const BOTTOM_PANEL_MIN = 80;
|
||||||
|
const BOTTOM_PANEL_MAX = 600;
|
||||||
|
const BOTTOM_PANEL_DEFAULT = 200;
|
||||||
|
|
||||||
|
const resizeHandleStyle: React.CSSProperties = {
|
||||||
|
height: 5,
|
||||||
|
flexShrink: 0,
|
||||||
|
cursor: 'row-resize',
|
||||||
|
background: '#2a2d2e',
|
||||||
|
borderTop: '1px solid #3c3c3c',
|
||||||
|
borderBottom: '1px solid #3c3c3c',
|
||||||
|
};
|
||||||
|
|
||||||
export const EditorPage: React.FC = () => {
|
export const EditorPage: React.FC = () => {
|
||||||
const [editorWidthPct, setEditorWidthPct] = useState(45);
|
const [editorWidthPct, setEditorWidthPct] = useState(45);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const resizingRef = useRef(false);
|
const resizingRef = useRef(false);
|
||||||
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
|
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
|
||||||
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
|
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
|
||||||
const [serialHeightPct, setSerialHeightPct] = useState(30);
|
const [consoleOpen, setConsoleOpen] = useState(false);
|
||||||
|
const [compileLogs, setCompileLogs] = useState<CompilationLog[]>([]);
|
||||||
|
const [bottomPanelHeight, setBottomPanelHeight] = useState(BOTTOM_PANEL_DEFAULT);
|
||||||
|
|
||||||
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -44,6 +61,27 @@ export const EditorPage: React.FC = () => {
|
||||||
document.addEventListener('mouseup', handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleBottomPanelResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const startY = e.clientY;
|
||||||
|
const startHeight = bottomPanelHeight;
|
||||||
|
|
||||||
|
const onMove = (ev: MouseEvent) => {
|
||||||
|
const delta = startY - ev.clientY;
|
||||||
|
setBottomPanelHeight(Math.max(BOTTOM_PANEL_MIN, Math.min(BOTTOM_PANEL_MAX, startHeight + delta)));
|
||||||
|
};
|
||||||
|
const onUp = () => {
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.removeEventListener('mousemove', onMove);
|
||||||
|
document.removeEventListener('mouseup', onUp);
|
||||||
|
};
|
||||||
|
document.body.style.cursor = 'row-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}, [bottomPanelHeight]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<header className="app-header">
|
<header className="app-header">
|
||||||
|
|
@ -90,11 +128,33 @@ export const EditorPage: React.FC = () => {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="app-container" ref={containerRef}>
|
<div className="app-container" ref={containerRef}>
|
||||||
<div className="editor-panel" style={{ width: `${editorWidthPct}%` }}>
|
<div className="editor-panel" style={{ width: `${editorWidthPct}%`, display: 'flex', flexDirection: 'column' }}>
|
||||||
<EditorToolbar />
|
<EditorToolbar
|
||||||
<div className="editor-wrapper">
|
consoleOpen={consoleOpen}
|
||||||
|
setConsoleOpen={setConsoleOpen}
|
||||||
|
compileLogs={compileLogs}
|
||||||
|
setCompileLogs={setCompileLogs}
|
||||||
|
/>
|
||||||
|
<div className="editor-wrapper" style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
|
||||||
<CodeEditor />
|
<CodeEditor />
|
||||||
</div>
|
</div>
|
||||||
|
{consoleOpen && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onMouseDown={handleBottomPanelResizeMouseDown}
|
||||||
|
style={resizeHandleStyle}
|
||||||
|
title="Drag to resize"
|
||||||
|
/>
|
||||||
|
<div style={{ height: bottomPanelHeight, flexShrink: 0 }}>
|
||||||
|
<CompilationConsole
|
||||||
|
isOpen={consoleOpen}
|
||||||
|
onClose={() => setConsoleOpen(false)}
|
||||||
|
logs={compileLogs}
|
||||||
|
onClear={() => setCompileLogs([])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Resize handle */}
|
{/* Resize handle */}
|
||||||
|
|
@ -103,13 +163,20 @@ export const EditorPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="simulator-panel" style={{ width: `${100 - editorWidthPct}%`, display: 'flex', flexDirection: 'column' }}>
|
<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' }}>
|
<div style={{ flex: 1, overflow: 'hidden', position: 'relative', minHeight: 0 }}>
|
||||||
<SimulatorCanvas />
|
<SimulatorCanvas />
|
||||||
</div>
|
</div>
|
||||||
{serialMonitorOpen && (
|
{serialMonitorOpen && (
|
||||||
<div style={{ flex: `0 0 ${serialHeightPct}%`, minHeight: 100, display: 'flex', flexDirection: 'column' }}>
|
<>
|
||||||
<SerialMonitor />
|
<div
|
||||||
</div>
|
onMouseDown={handleBottomPanelResizeMouseDown}
|
||||||
|
style={resizeHandleStyle}
|
||||||
|
title="Drag to resize"
|
||||||
|
/>
|
||||||
|
<div style={{ height: bottomPanelHeight, flexShrink: 0 }}>
|
||||||
|
<SerialMonitor />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ export class AVRSimulator {
|
||||||
|
|
||||||
/** Serial output buffer — subscribers receive each byte or line */
|
/** Serial output buffer — subscribers receive each byte or line */
|
||||||
public onSerialData: ((char: string) => void) | null = null;
|
public onSerialData: ((char: string) => void) | null = null;
|
||||||
|
/** Fires whenever the sketch changes Serial baud rate (Serial.begin) */
|
||||||
|
public onBaudRateChange: ((baudRate: number) => void) | null = null;
|
||||||
private lastPortBValue = 0;
|
private lastPortBValue = 0;
|
||||||
private lastPortCValue = 0;
|
private lastPortCValue = 0;
|
||||||
private lastPortDValue = 0;
|
private lastPortDValue = 0;
|
||||||
|
|
@ -94,6 +96,11 @@ export class AVRSimulator {
|
||||||
this.onSerialData(String.fromCharCode(value));
|
this.onSerialData(String.fromCharCode(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.usart.onConfigurationChange = () => {
|
||||||
|
if (this.onBaudRateChange && this.usart) {
|
||||||
|
this.onBaudRateChange(this.usart.baudRate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// TWI (I2C)
|
// TWI (I2C)
|
||||||
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
||||||
|
|
@ -262,6 +269,11 @@ export class AVRSimulator {
|
||||||
this.usart.onByteTransmit = (value: number) => {
|
this.usart.onByteTransmit = (value: number) => {
|
||||||
if (this.onSerialData) this.onSerialData(String.fromCharCode(value));
|
if (this.onSerialData) this.onSerialData(String.fromCharCode(value));
|
||||||
};
|
};
|
||||||
|
this.usart.onConfigurationChange = () => {
|
||||||
|
if (this.onBaudRateChange && this.usart) {
|
||||||
|
this.onBaudRateChange(this.usart.baudRate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
this.twi = new AVRTWI(this.cpu, twiConfig, 16000000);
|
||||||
this.i2cBus = new I2CBusManager(this.twi);
|
this.i2cBus = new I2CBusManager(this.twi);
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,16 @@ export class RP2040Simulator {
|
||||||
this.rp2040.core.PC = 0x10000000;
|
this.rp2040.core.PC = 0x10000000;
|
||||||
|
|
||||||
// ── Wire UART0 (default Serial port for Arduino-Pico) ────────────
|
// ── Wire UART0 (default Serial port for Arduino-Pico) ────────────
|
||||||
|
let serialBuffer = '';
|
||||||
this.rp2040.uart[0].onByte = (value: number) => {
|
this.rp2040.uart[0].onByte = (value: number) => {
|
||||||
|
const ch = String.fromCharCode(value);
|
||||||
|
serialBuffer += ch;
|
||||||
|
if (ch === '\n') {
|
||||||
|
console.log('[RP2040 UART0]', serialBuffer.trimEnd());
|
||||||
|
serialBuffer = '';
|
||||||
|
}
|
||||||
if (this.onSerialData) {
|
if (this.onSerialData) {
|
||||||
this.onSerialData(String.fromCharCode(value));
|
this.onSerialData(ch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ function setAdcVoltage(simulator: AnySimulator, pin: number, voltage: number): b
|
||||||
const channel = pin - 26;
|
const channel = pin - 26;
|
||||||
// RP2040 ADC: 12-bit, 3.3V reference
|
// RP2040 ADC: 12-bit, 3.3V reference
|
||||||
const adcValue = Math.round((voltage / 3.3) * 4095);
|
const adcValue = Math.round((voltage / 3.3) * 4095);
|
||||||
|
console.log(`[setAdcVoltage] RP2040 ch${channel} = ${adcValue} (${voltage.toFixed(3)}V)`);
|
||||||
simulator.setADCValue(channel, adcValue);
|
simulator.setADCValue(channel, adcValue);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
console.warn(`[setAdcVoltage] RP2040 pin ${pin} is not an ADC pin (26-29)`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// AVR: pins 14-19 → ADC channels 0-5
|
// AVR: pins 14-19 → ADC channels 0-5
|
||||||
|
|
@ -94,15 +96,21 @@ PartSimulationRegistry.register('rgb-led', {
|
||||||
PartSimulationRegistry.register('potentiometer', {
|
PartSimulationRegistry.register('potentiometer', {
|
||||||
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
||||||
const pin = getArduinoPinHelper('SIG');
|
const pin = getArduinoPinHelper('SIG');
|
||||||
if (pin === null) return () => { };
|
console.log(`[Potentiometer] attachEvents called, SIG pin resolved to: ${pin}`);
|
||||||
|
if (pin === null) {
|
||||||
|
console.warn('[Potentiometer] No SIG pin found — skipping ADC attachment');
|
||||||
|
return () => { };
|
||||||
|
}
|
||||||
|
|
||||||
// Determine reference voltage based on board type
|
// Determine reference voltage based on board type
|
||||||
const isRP2040 = simulator instanceof RP2040Simulator;
|
const isRP2040 = simulator instanceof RP2040Simulator;
|
||||||
const refVoltage = isRP2040 ? 3.3 : 5.0;
|
const refVoltage = isRP2040 ? 3.3 : 5.0;
|
||||||
|
console.log(`[Potentiometer] Board type: ${isRP2040 ? 'RP2040' : 'AVR'}, refV: ${refVoltage}`);
|
||||||
|
|
||||||
const onInput = () => {
|
const onInput = () => {
|
||||||
const raw = parseInt((element as any).value || '0', 10);
|
const raw = parseInt((element as any).value || '0', 10);
|
||||||
const volts = (raw / 1023.0) * refVoltage;
|
const volts = (raw / 1023.0) * refVoltage;
|
||||||
|
console.log(`[Potentiometer] pin=${pin}, raw=${raw}, volts=${volts.toFixed(3)}`);
|
||||||
if (!setAdcVoltage(simulator, pin, volts)) {
|
if (!setAdcVoltage(simulator, pin, volts)) {
|
||||||
console.warn(`[Potentiometer] ADC not available for pin ${pin}`);
|
console.warn(`[Potentiometer] ADC not available for pin ${pin}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ interface SimulatorState {
|
||||||
|
|
||||||
// Serial monitor state
|
// Serial monitor state
|
||||||
serialOutput: string;
|
serialOutput: string;
|
||||||
|
serialBaudRate: number;
|
||||||
serialMonitorOpen: boolean;
|
serialMonitorOpen: boolean;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
@ -164,6 +165,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
selectedWireId: null,
|
selectedWireId: null,
|
||||||
wireInProgress: null,
|
wireInProgress: null,
|
||||||
serialOutput: '',
|
serialOutput: '',
|
||||||
|
serialBaudRate: 0,
|
||||||
serialMonitorOpen: false,
|
serialMonitorOpen: false,
|
||||||
|
|
||||||
setBoardType: (type: BoardType) => {
|
setBoardType: (type: BoardType) => {
|
||||||
|
|
@ -178,7 +180,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
simulator.onSerialData = (char: string) => {
|
simulator.onSerialData = (char: string) => {
|
||||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||||
};
|
};
|
||||||
set({ boardType: type, simulator, compiledHex: null, serialOutput: '' });
|
if (simulator instanceof AVRSimulator) {
|
||||||
|
simulator.onBaudRateChange = (baudRate: number) => set({ serialBaudRate: baudRate });
|
||||||
|
}
|
||||||
|
set({ boardType: type, simulator, compiledHex: null, serialOutput: '', serialBaudRate: 0 });
|
||||||
console.log(`Board switched to: ${type}`);
|
console.log(`Board switched to: ${type}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -191,7 +196,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
simulator.onSerialData = (char: string) => {
|
simulator.onSerialData = (char: string) => {
|
||||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||||
};
|
};
|
||||||
set({ simulator, serialOutput: '' });
|
if (simulator instanceof AVRSimulator) {
|
||||||
|
simulator.onBaudRateChange = (baudRate: number) => set({ serialBaudRate: baudRate });
|
||||||
|
}
|
||||||
|
set({ simulator, serialOutput: '', serialBaudRate: 0 });
|
||||||
console.log(`Simulator initialized: ${boardType}`);
|
console.log(`Simulator initialized: ${boardType}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -239,7 +247,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
simulator.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice);
|
simulator.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice);
|
||||||
}
|
}
|
||||||
simulator.start();
|
simulator.start();
|
||||||
set({ running: true });
|
set({ running: true, serialMonitorOpen: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -259,7 +267,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
simulator.onSerialData = (char: string) => {
|
simulator.onSerialData = (char: string) => {
|
||||||
set((s) => ({ serialOutput: s.serialOutput + char }));
|
set((s) => ({ serialOutput: s.serialOutput + char }));
|
||||||
};
|
};
|
||||||
set({ running: false, serialOutput: '' });
|
if (simulator instanceof AVRSimulator) {
|
||||||
|
simulator.onBaudRateChange = (baudRate: number) => set({ serialBaudRate: baudRate });
|
||||||
|
}
|
||||||
|
set({ running: false, serialOutput: '', serialBaudRate: 0 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue