feat(attiny85+customchip): full ATtiny85 ADC/Timer0 + custom-chip pipeline fixes
ATtiny85 (AVRSimulator + collectPinStates + connectAnalogInputsToMcu + SimulatorCanvas + Attiny85Element + examples): - Add attiny85AdcConfig with correct register addresses (ADMUX=0x27, ADCSRA=0x26, ADCSRB=0x23, ADCL=0x24, ADCH=0x25, DIDR0=0x34, adcInterrupt=0x08). Without this, analogRead() polled the wrong address forever and the firmware hung on first ADC read. - Add attiny85Timer0Config + instantiate AVRTimer so OVF fires at the ATTinyCore-expected ~1.024 ms cadence. delay() advance is still blocked on avr8js TIFR auto-clear semantics (separate upstream issue, see ATTINY85_TIMER0_UPSTREAM_ISSUE.md in velxio-prod test plan). - Map ATtiny85 ADC channels to PB-style pin names (PB5/PB2/PB4/PB3 -> 0..3) in connectAnalogInputsToMcu so SPICE node voltages reach the right ADC channel. - Recognise /^PB\d+$/ in collectPinStates.pinNameToArduinoPin so wires named "PB1" emit v_attiny85_pb1 V-source and the LED responds to MCU writes. Previously every PB-wire returned -1 and SPICE saw no source. - SimulatorCanvas: subscribe pin 1 (PB1) for the built-in LED on the attiny85 board kind (Digispark convention), instead of falling through to the pin-13 default. - Attiny85Element: remove the hand-drawn "yellow LED" circle that was floating above the chip. The bare DIP-8 has no on-board LED; examples wire a real wokwi-led + resistor instead. - examples.ts: add a real wokwi-led + 220 Ohm wokwi-resistor + wires to attiny85-blink, and add missing series resistors to attiny85-button-led and attiny85-ntc-sensor. attiny85-pwm-fade was already correct. Custom-chip pipeline (CustomChipPart + simulatorBridges): - Add a requestAnimationFrame loop that calls instance.tickTimers() every frame in CustomChipPart. Chips that register vx_timer_create (e.g. an i8080 stepping its core, or a sensor publishing samples) had timers added to the queue but nothing fired them; tickTimers was dead code. - Gate the ESP32 backend path with detectSimulatorKind(sim)==='esp32'. The previous `typeof sim.registerSensor === 'function'` check matched AVR and RP2040 simulators too (they expose registerSensor for I2C sensor proxies), routing client-side chips to a non-existent ESP32 worker on those boards. - Replace direct simulator.usart.writeByte calls in avrUartTx with a JS-level FIFO + setTimeout(1ms) drainer. avr8js writeByte drops bytes under burst load (a chip emitting print_string lost ~99% of bytes via non-immediate, or kept only the last byte via immediate). The drainer attempts one non-immediate write per tick and retries on RXC busy / RXEN off. Added a guard for ATtiny85 (no USART -> would queue forever). End-to-end verified: i8080-banner-streamer now prints the boot banner followed by "uptime ticks: 0xNN" lines stepping every ~50 ms, executing real Intel 8080 instructions inside the WASM chip. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a9285c6698
commit
603b791daa
|
|
@ -1100,8 +1100,11 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
|
||||||
case 'nano-rp2040':
|
case 'nano-rp2040':
|
||||||
ledPin = 25; // GPIO25
|
ledPin = 25; // GPIO25
|
||||||
break;
|
break;
|
||||||
|
case 'attiny85':
|
||||||
|
ledPin = 1; // PB1 (Digispark convention)
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ledPin = 13; // Pin 13 for Arduino Uno/Nano/Mega, ATtiny85, etc.
|
ledPin = 13; // Pin 13 for Arduino Uno/Nano/Mega
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubs.push(
|
unsubs.push(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* pinInfo) but external components positioned to the right of the
|
* pinInfo) but external components positioned to the right of the
|
||||||
* chip may need to be moved manually for clean routing.
|
* chip may need to be moved manually for clean routing.
|
||||||
*
|
*
|
||||||
* viewBox 28.801 × 23.768 mm scaled uniformly at 5.555 px/mm → 160 × 132 px.
|
* viewBox 28.801 x 23.768 mm scaled uniformly at 5.555 px/mm -> 160 x 132 px.
|
||||||
* Pin terminal centres in the source SVG (mm), all 8 connectors:
|
* Pin terminal centres in the source SVG (mm), all 8 connectors:
|
||||||
* row x y pin label
|
* row x y pin label
|
||||||
* top 3.601 1.084 VCC (connector7)
|
* top 3.601 1.084 VCC (connector7)
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
* bottom 18.000 22.687 PB4 (connector2)
|
* bottom 18.000 22.687 PB4 (connector2)
|
||||||
* bottom 25.201 22.687 GND (connector3)
|
* bottom 25.201 22.687 GND (connector3)
|
||||||
*
|
*
|
||||||
* Built-in LED on PB1 (Digispark convention) is overlaid as a circle in
|
* No fake on-board LED is drawn. The bare DIP-8 has no PCB LED — examples
|
||||||
* the outer SVG — drives off the `led1` attribute / property like before.
|
* wire a real wokwi-led + wokwi-resistor to PB1 like the c3-blink one does.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const W = 160;
|
const W = 160;
|
||||||
|
|
@ -40,7 +40,7 @@ const PIN_INFO: ReadonlyArray<{ name: string; x: number; y: number; description?
|
||||||
// Top row — left to right
|
// Top row — left to right
|
||||||
{ name: 'VCC', x: PIN_X[0], y: TOP_Y, description: 'VCC (Pin 8)' },
|
{ name: 'VCC', x: PIN_X[0], y: TOP_Y, description: 'VCC (Pin 8)' },
|
||||||
{ name: 'PB2', x: PIN_X[1], y: TOP_Y, description: 'PB2 / SCK / SCL / ADC1 (Pin 7)' },
|
{ name: 'PB2', x: PIN_X[1], y: TOP_Y, description: 'PB2 / SCK / SCL / ADC1 (Pin 7)' },
|
||||||
{ name: 'PB1', x: PIN_X[2], y: TOP_Y, description: 'PB1 / MISO / built-in LED (Pin 6)' },
|
{ name: 'PB1', x: PIN_X[2], y: TOP_Y, description: 'PB1 / MISO / OC0B / OC1A (Pin 6)' },
|
||||||
{ name: 'PB0', x: PIN_X[3], y: TOP_Y, description: 'PB0 / MOSI / SDA / OC0A (Pin 5)' },
|
{ name: 'PB0', x: PIN_X[3], y: TOP_Y, description: 'PB0 / MOSI / SDA / OC0A (Pin 5)' },
|
||||||
// Bottom row — left to right
|
// Bottom row — left to right
|
||||||
{ name: 'PB5', x: PIN_X[0], y: BOT_Y, description: 'PB5 / RESET (Pin 1)' },
|
{ name: 'PB5', x: PIN_X[0], y: BOT_Y, description: 'PB5 / RESET (Pin 1)' },
|
||||||
|
|
@ -50,12 +50,6 @@ const PIN_INFO: ReadonlyArray<{ name: string; x: number; y: number; description?
|
||||||
];
|
];
|
||||||
|
|
||||||
class Attiny85Element extends HTMLElement {
|
class Attiny85Element extends HTMLElement {
|
||||||
static get observedAttributes(): string[] {
|
|
||||||
return ['led1'];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _led1 = false;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
|
|
@ -65,52 +59,14 @@ class Attiny85Element extends HTMLElement {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
|
|
||||||
if (name === 'led1') {
|
|
||||||
this._led1 = value !== null && value !== 'false';
|
|
||||||
this.updateLed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Wires use this to find pin coordinates. */
|
/** Wires use this to find pin coordinates. */
|
||||||
get pinInfo() {
|
get pinInfo() {
|
||||||
return PIN_INFO;
|
return PIN_INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
set led1(v: boolean) {
|
|
||||||
this._led1 = !!v;
|
|
||||||
this.updateLed();
|
|
||||||
}
|
|
||||||
get led1(): boolean {
|
|
||||||
return this._led1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateLed() {
|
|
||||||
const led = this.shadowRoot?.getElementById('attiny-led1') as unknown as SVGElement | null;
|
|
||||||
if (!led) return;
|
|
||||||
if (this._led1) {
|
|
||||||
led.setAttribute('fill', '#ffee44');
|
|
||||||
led.setAttribute('stroke', '#ffcc00');
|
|
||||||
led.style.filter = 'drop-shadow(0 0 4px #ffcc00)';
|
|
||||||
} else {
|
|
||||||
led.setAttribute('fill', '#333');
|
|
||||||
led.setAttribute('stroke', '#555');
|
|
||||||
led.style.filter = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private render() {
|
private render() {
|
||||||
if (!this.shadowRoot) return;
|
if (!this.shadowRoot) return;
|
||||||
|
|
||||||
const ledFill = this._led1 ? '#ffee44' : '#333';
|
|
||||||
const ledStroke = this._led1 ? '#ffcc00' : '#555';
|
|
||||||
const ledFilter = this._led1 ? 'drop-shadow(0 0 4px #ffcc00)' : 'none';
|
|
||||||
|
|
||||||
// Place the LED indicator just ABOVE the PB1 top-pin (PIN_X[2], TOP_Y),
|
|
||||||
// outside the chip body so it stays readable.
|
|
||||||
const ledX = PIN_X[2];
|
|
||||||
const ledY = TOP_Y - 12;
|
|
||||||
|
|
||||||
this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
:host { display: inline-block; line-height: 0; }
|
:host { display: inline-block; line-height: 0; }
|
||||||
|
|
@ -122,23 +78,16 @@ class Attiny85Element extends HTMLElement {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.pin-label.power { fill: #aaa; }
|
.pin-label.power { fill: #aaa; }
|
||||||
.pin-label.led { fill: #ffd066; }
|
|
||||||
</style>
|
</style>
|
||||||
<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
||||||
<!-- Fritzing ATtiny85 DIP-8 artwork (CC-BY-SA). Scaled to fill
|
<!-- Fritzing ATtiny85 DIP-8 artwork (CC-BY-SA). Scaled to fill
|
||||||
the 160×132 box; aspect ratio matches the source 1.211:1. -->
|
the 160x132 box; aspect ratio matches the source 1.211:1. -->
|
||||||
<image href="/component-svgs/attiny85.svg" x="0" y="0" width="${W}" height="${H}" />
|
<image href="/component-svgs/attiny85.svg" x="0" y="0" width="${W}" height="${H}" />
|
||||||
|
|
||||||
<!-- Built-in LED on PB1 (Digispark convention) -->
|
|
||||||
<circle id="attiny-led1"
|
|
||||||
cx="${ledX}" cy="${ledY}" r="5"
|
|
||||||
fill="${ledFill}" stroke="${ledStroke}" stroke-width="1"
|
|
||||||
style="filter: ${ledFilter};" />
|
|
||||||
|
|
||||||
<!-- Pin labels overlaid above/below the connector pads. -->
|
<!-- Pin labels overlaid above/below the connector pads. -->
|
||||||
<text class="pin-label power" x="${PIN_X[0]}" y="${TOP_Y - 22}">VCC</text>
|
<text class="pin-label power" x="${PIN_X[0]}" y="${TOP_Y - 22}">VCC</text>
|
||||||
<text class="pin-label" x="${PIN_X[1]}" y="${TOP_Y - 22}">PB2</text>
|
<text class="pin-label" x="${PIN_X[1]}" y="${TOP_Y - 22}">PB2</text>
|
||||||
<text class="pin-label led" x="${PIN_X[2]}" y="${TOP_Y - 22}">PB1</text>
|
<text class="pin-label" x="${PIN_X[2]}" y="${TOP_Y - 22}">PB1</text>
|
||||||
<text class="pin-label" x="${PIN_X[3]}" y="${TOP_Y - 22}">PB0</text>
|
<text class="pin-label" x="${PIN_X[3]}" y="${TOP_Y - 22}">PB0</text>
|
||||||
|
|
||||||
<text class="pin-label" x="${PIN_X[0]}" y="${BOT_Y + 12}">PB5</text>
|
<text class="pin-label" x="${PIN_X[0]}" y="${BOT_Y + 12}">PB5</text>
|
||||||
|
|
|
||||||
|
|
@ -1550,33 +1550,21 @@ void loop() {
|
||||||
`,
|
`,
|
||||||
components: [
|
components: [
|
||||||
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||||
{ type: 'wokwi-ssd1306', id: 'ssd1306-1', x: 420, y: 120, properties: {} },
|
{ type: 'wokwi-ssd1306', id: 'ssd1306-1', x: 420, y: 100, properties: {} },
|
||||||
|
{ type: 'wokwi-ds1307', id: 'rtc1', x: 420, y: 280, properties: {} },
|
||||||
],
|
],
|
||||||
wires: [
|
wires: [
|
||||||
{
|
// SDA bus (shared)
|
||||||
id: 'wire-sda',
|
{ id: 'wire-sda', start: { componentId: 'arduino-uno', pinName: 'A4' }, end: { componentId: 'ssd1306-1', pinName: 'DATA' }, color: '#2196f3' },
|
||||||
start: { componentId: 'arduino-uno', pinName: 'A4' },
|
{ id: 'wire-sda-rtc', start: { componentId: 'ssd1306-1', pinName: 'DATA' }, end: { componentId: 'rtc1', pinName: 'SDA' }, color: '#2196f3' },
|
||||||
end: { componentId: 'ssd1306-1', pinName: 'DATA' },
|
// SCL bus (shared)
|
||||||
color: '#2196f3',
|
{ id: 'wire-scl', start: { componentId: 'arduino-uno', pinName: 'A5' }, end: { componentId: 'ssd1306-1', pinName: 'CLK' }, color: '#ff9800' },
|
||||||
},
|
{ id: 'wire-scl-rtc', start: { componentId: 'ssd1306-1', pinName: 'CLK' }, end: { componentId: 'rtc1', pinName: 'SCL' }, color: '#ff9800' },
|
||||||
{
|
// Power + ground for both devices
|
||||||
id: 'wire-scl',
|
{ id: 'wire-gnd', start: { componentId: 'arduino-uno', pinName: 'GND.1' }, end: { componentId: 'ssd1306-1', pinName: 'GND' }, color: '#000000' },
|
||||||
start: { componentId: 'arduino-uno', pinName: 'A5' },
|
{ id: 'wire-gnd-rtc', start: { componentId: 'ssd1306-1', pinName: 'GND' }, end: { componentId: 'rtc1', pinName: 'GND' }, color: '#000000' },
|
||||||
end: { componentId: 'ssd1306-1', pinName: 'CLK' },
|
{ id: 'wire-vcc', start: { componentId: 'arduino-uno', pinName: '5V' }, end: { componentId: 'ssd1306-1', pinName: 'VIN' }, color: '#ff0000' },
|
||||||
color: '#ff9800',
|
{ id: 'wire-vcc-rtc', start: { componentId: 'ssd1306-1', pinName: 'VIN' }, end: { componentId: 'rtc1', pinName: 'VCC' }, color: '#ff0000' },
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'wire-gnd',
|
|
||||||
start: { componentId: 'arduino-uno', pinName: 'GND.1' },
|
|
||||||
end: { componentId: 'ssd1306-1', pinName: 'GND' },
|
|
||||||
color: '#000000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'wire-vcc',
|
|
||||||
start: { componentId: 'arduino-uno', pinName: '5V' },
|
|
||||||
end: { componentId: 'ssd1306-1', pinName: 'VIN' },
|
|
||||||
color: '#ff0000',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1653,8 +1641,16 @@ void loop() {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
components: [{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} }],
|
components: [
|
||||||
wires: [],
|
{ type: 'wokwi-arduino-uno', id: 'arduino-uno', x: 100, y: 100, properties: {} },
|
||||||
|
{ type: 'wokwi-ds1307', id: 'rtc1', x: 480, y: 200, properties: {} },
|
||||||
|
],
|
||||||
|
wires: [
|
||||||
|
{ id: 'w-sda', start: { componentId: 'arduino-uno', pinName: 'A4' }, end: { componentId: 'rtc1', pinName: 'SDA' }, color: '#0066cc' },
|
||||||
|
{ id: 'w-scl', start: { componentId: 'arduino-uno', pinName: 'A5' }, end: { componentId: 'rtc1', pinName: 'SCL' }, color: '#ffaa00' },
|
||||||
|
{ id: 'w-vcc', start: { componentId: 'arduino-uno', pinName: '5V' }, end: { componentId: 'rtc1', pinName: 'VCC' }, color: '#ff0000' },
|
||||||
|
{ id: 'w-gnd', start: { componentId: 'arduino-uno', pinName: 'GND' }, end: { componentId: 'rtc1', pinName: 'GND' }, color: '#000000' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'i2c-eeprom-rw',
|
id: 'i2c-eeprom-rw',
|
||||||
|
|
@ -6997,7 +6993,7 @@ void loop() {
|
||||||
id: 'attiny85-blink',
|
id: 'attiny85-blink',
|
||||||
title: 'ATtiny85: Blink LED',
|
title: 'ATtiny85: Blink LED',
|
||||||
description:
|
description:
|
||||||
'Classic blink on the ATtiny85 built-in LED (PB1). Great first sketch for this tiny 8-bit chip.',
|
'Blink an external LED on PB1 (Digispark pin 1) through a 220 Ohm current-limit resistor.',
|
||||||
category: 'basics',
|
category: 'basics',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardFilter: 'attiny85',
|
boardFilter: 'attiny85',
|
||||||
|
|
@ -7006,11 +7002,11 @@ void loop() {
|
||||||
boardKind: 'attiny85',
|
boardKind: 'attiny85',
|
||||||
x: 120,
|
x: 120,
|
||||||
y: 160,
|
y: 160,
|
||||||
code: `// ATtiny85 — Blink built-in LED on PB1
|
code: `// ATtiny85 — Blink an LED wired to PB1 (Digispark pin 1)
|
||||||
// Pin 1 = PB1 = OC0B/OC1A (also Digispark LED)
|
// PB1 -> 220 Ohm -> LED anode; LED cathode -> GND
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
pinMode(1, OUTPUT); // PB1 as output
|
pinMode(1, OUTPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|
@ -7022,8 +7018,30 @@ void loop() {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
code: '',
|
code: '',
|
||||||
components: [],
|
components: [
|
||||||
wires: [],
|
{ type: 'wokwi-led', id: 'tiny-led1', x: 360, y: 140, properties: { color: 'green' } },
|
||||||
|
{ type: 'wokwi-resistor', id: 'tiny-r1', x: 360, y: 220, properties: { resistance: '220' } },
|
||||||
|
],
|
||||||
|
wires: [
|
||||||
|
{
|
||||||
|
id: 'tinyw1',
|
||||||
|
start: { componentId: 'attiny85', pinName: 'PB1' },
|
||||||
|
end: { componentId: 'tiny-led1', pinName: 'A' },
|
||||||
|
color: '#22cc22',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tinyw2',
|
||||||
|
start: { componentId: 'tiny-led1', pinName: 'C' },
|
||||||
|
end: { componentId: 'tiny-r1', pinName: '1' },
|
||||||
|
color: '#888888',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tinyw3',
|
||||||
|
start: { componentId: 'tiny-r1', pinName: '2' },
|
||||||
|
end: { componentId: 'attiny85', pinName: 'GND' },
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -7058,6 +7076,13 @@ void loop() {
|
||||||
components: [
|
components: [
|
||||||
{ type: 'wokwi-pushbutton', id: 'tiny-btn1', x: 380, y: 130, properties: { color: 'green' } },
|
{ type: 'wokwi-pushbutton', id: 'tiny-btn1', x: 380, y: 130, properties: { color: 'green' } },
|
||||||
{ type: 'wokwi-led', id: 'tiny-led1', x: 380, y: 240, properties: { color: 'red' } },
|
{ type: 'wokwi-led', id: 'tiny-led1', x: 380, y: 240, properties: { color: 'red' } },
|
||||||
|
{
|
||||||
|
type: 'wokwi-resistor',
|
||||||
|
id: 'tiny-btn-r1',
|
||||||
|
x: 380,
|
||||||
|
y: 320,
|
||||||
|
properties: { resistance: '220' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
wires: [
|
wires: [
|
||||||
{
|
{
|
||||||
|
|
@ -7078,10 +7103,16 @@ void loop() {
|
||||||
end: { componentId: 'tiny-led1', pinName: 'A' },
|
end: { componentId: 'tiny-led1', pinName: 'A' },
|
||||||
color: '#ff4444',
|
color: '#ff4444',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'tb-led-r',
|
||||||
|
start: { componentId: 'tiny-led1', pinName: 'C' },
|
||||||
|
end: { componentId: 'tiny-btn-r1', pinName: '1' },
|
||||||
|
color: '#888888',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'tb-gnd2',
|
id: 'tb-gnd2',
|
||||||
start: { componentId: 'attiny85', pinName: 'GND' },
|
start: { componentId: 'tiny-btn-r1', pinName: '2' },
|
||||||
end: { componentId: 'tiny-led1', pinName: 'C' },
|
end: { componentId: 'attiny85', pinName: 'GND' },
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -7217,6 +7248,13 @@ void loop() {
|
||||||
properties: { temperature: '25' },
|
properties: { temperature: '25' },
|
||||||
},
|
},
|
||||||
{ type: 'wokwi-led', id: 'tiny-ntc-led', x: 380, y: 300, properties: { color: 'red' } },
|
{ type: 'wokwi-led', id: 'tiny-ntc-led', x: 380, y: 300, properties: { color: 'red' } },
|
||||||
|
{
|
||||||
|
type: 'wokwi-resistor',
|
||||||
|
id: 'tiny-ntc-r1',
|
||||||
|
x: 380,
|
||||||
|
y: 380,
|
||||||
|
properties: { resistance: '220' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
wires: [
|
wires: [
|
||||||
{
|
{
|
||||||
|
|
@ -7243,10 +7281,16 @@ void loop() {
|
||||||
end: { componentId: 'tiny-ntc-led', pinName: 'A' },
|
end: { componentId: 'tiny-ntc-led', pinName: 'A' },
|
||||||
color: '#ff4444',
|
color: '#ff4444',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'tn-led-r',
|
||||||
|
start: { componentId: 'tiny-ntc-led', pinName: 'C' },
|
||||||
|
end: { componentId: 'tiny-ntc-r1', pinName: '1' },
|
||||||
|
color: '#888888',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'tn-lgnd',
|
id: 'tn-lgnd',
|
||||||
start: { componentId: 'attiny85', pinName: 'GND' },
|
start: { componentId: 'tiny-ntc-r1', pinName: '2' },
|
||||||
end: { componentId: 'tiny-ntc-led', pinName: 'C' },
|
end: { componentId: 'attiny85', pinName: 'GND' },
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ import {
|
||||||
ATtinyTimer1,
|
ATtinyTimer1,
|
||||||
attinyTimer1Config,
|
attinyTimer1Config,
|
||||||
} from 'avr8js';
|
} from 'avr8js';
|
||||||
|
import type { AVRTimerConfig } from 'avr8js/dist/esm/peripherals/timer';
|
||||||
|
import type { ADCConfig, ADCMuxConfiguration } from 'avr8js/dist/esm/peripherals/adc';
|
||||||
|
import { ADCMuxInputType, ADCReference } from 'avr8js/dist/esm/peripherals/adc';
|
||||||
import { PinManager } from './PinManager';
|
import { PinManager } from './PinManager';
|
||||||
import { hexToUint8Array } from '../utils/hexParser';
|
import { hexToUint8Array } from '../utils/hexParser';
|
||||||
import { I2CBusManager, nullI2CMaster } from './I2CBusManager';
|
import { I2CBusManager, nullI2CMaster } from './I2CBusManager';
|
||||||
|
|
@ -143,6 +146,110 @@ const attiny85PortBConfig = {
|
||||||
externalInterrupts: [] as never[],
|
externalInterrupts: [] as never[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ATtiny85 Timer0 config — Arduino `millis()` / `delay()` rely on the
|
||||||
|
* TIMER0_OVF interrupt to tick the millisecond counter. avr8js's generic
|
||||||
|
* `AVRTimer` is fully data-driven, so we just supply ATtiny85's register
|
||||||
|
* addresses (different from the ATmega328P defaults in `timer0Config`)
|
||||||
|
* and the right interrupt vector offsets.
|
||||||
|
*
|
||||||
|
* Refs: <avr/iotnx5.h> for register addresses; ATtiny25/45/85 datasheet
|
||||||
|
* (Atmel-2586) for vector indices.
|
||||||
|
* _VECTOR(5) → TIMER0_OVF → word 0x0A
|
||||||
|
* _VECTOR(10) → TIMER0_COMPA → word 0x14
|
||||||
|
* _VECTOR(11) → TIMER0_COMPB → word 0x16
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* ATtiny85 ADC config — required because the chip's ADC registers live at
|
||||||
|
* completely different memory addresses than the ATmega328P defaults that
|
||||||
|
* avr8js's `adcConfig` ships with. Without this, `analogRead()` writes
|
||||||
|
* ADSC at ATtiny85's ADCSRA (0x26) and polls forever because avr8js is
|
||||||
|
* listening at 0x7A instead.
|
||||||
|
*
|
||||||
|
* Refs: <avr/iotnx5.h>; ATtiny25/45/85 datasheet (Atmel-2586) sec. 17.
|
||||||
|
* ADMUX = 0x07 (I/O) -> 0x27 (mem)
|
||||||
|
* ADCSRA = 0x06 -> 0x26
|
||||||
|
* ADCSRB = 0x03 -> 0x23
|
||||||
|
* ADCL = 0x04 -> 0x24
|
||||||
|
* ADCH = 0x05 -> 0x25
|
||||||
|
* DIDR0 = 0x14 -> 0x34
|
||||||
|
* ADC_vect = _VECTOR(8) -> word 0x10
|
||||||
|
*
|
||||||
|
* MUX field is 4 bits (bits 3:0). Single-ended channels 0..3 = PB5/PB2/PB4/PB3.
|
||||||
|
* Reference bits REFS1:REFS0 at ADMUX[7:6] select VCC/AREF/Internal1V1 by default;
|
||||||
|
* full REFS2 extension lives at ADMUX[4] but the avr8js helper checks bit 3,
|
||||||
|
* so the rare 2.56 V internal reference is currently unsupported — every
|
||||||
|
* default-ref sketch (`analogReference(DEFAULT)`) works fine.
|
||||||
|
*/
|
||||||
|
const attiny85AdcChannels: ADCMuxConfiguration = {
|
||||||
|
0: { type: ADCMuxInputType.SingleEnded, channel: 0 }, // PB5
|
||||||
|
1: { type: ADCMuxInputType.SingleEnded, channel: 1 }, // PB2
|
||||||
|
2: { type: ADCMuxInputType.SingleEnded, channel: 2 }, // PB4
|
||||||
|
3: { type: ADCMuxInputType.SingleEnded, channel: 3 }, // PB3
|
||||||
|
12: { type: ADCMuxInputType.Constant, voltage: 1.1 }, // VBG
|
||||||
|
13: { type: ADCMuxInputType.Constant, voltage: 0 }, // GND
|
||||||
|
15: { type: ADCMuxInputType.Temperature },
|
||||||
|
};
|
||||||
|
|
||||||
|
const attiny85AdcConfig: ADCConfig = {
|
||||||
|
ADMUX: 0x27,
|
||||||
|
ADCSRA: 0x26,
|
||||||
|
ADCSRB: 0x23,
|
||||||
|
ADCL: 0x24,
|
||||||
|
ADCH: 0x25,
|
||||||
|
DIDR0: 0x34,
|
||||||
|
// ATtiny85 vectors are 1-word RJMP (vs ATmega328P's 2-word JMP) so the
|
||||||
|
// avr8js "address" field is the raw vector index, not vector*2.
|
||||||
|
adcInterrupt: 0x08, // _VECTOR(8) ADC_vect
|
||||||
|
numChannels: 4,
|
||||||
|
muxInputMask: 0xf,
|
||||||
|
muxChannels: attiny85AdcChannels,
|
||||||
|
adcReferences: [
|
||||||
|
ADCReference.AVCC, // 00 = VCC
|
||||||
|
ADCReference.AREF, // 01 = external AREF (PB0)
|
||||||
|
ADCReference.Internal1V1, // 10 = internal 1.1 V
|
||||||
|
ADCReference.Reserved, // 11 = reserved
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const attiny85Timer0Config: AVRTimerConfig = {
|
||||||
|
bits: 8,
|
||||||
|
captureInterrupt: 0,
|
||||||
|
// ATtiny85 vectors are 1-word RJMP (vs ATmega328P's 2-word JMP) so the
|
||||||
|
// avr8js "address" field is the raw vector index, not vector*2.
|
||||||
|
compAInterrupt: 0x0a, // _VECTOR(10) TIMER0_COMPA_vect
|
||||||
|
compBInterrupt: 0x0b, // _VECTOR(11) TIMER0_COMPB_vect
|
||||||
|
compCInterrupt: 0,
|
||||||
|
ovfInterrupt: 0x05, // _VECTOR(5) TIMER0_OVF_vect
|
||||||
|
TIFR: 0x58,
|
||||||
|
OCRA: 0x56,
|
||||||
|
OCRB: 0x5c,
|
||||||
|
OCRC: 0,
|
||||||
|
ICR: 0,
|
||||||
|
TCNT: 0x52,
|
||||||
|
TCCRA: 0x4f,
|
||||||
|
TCCRB: 0x53,
|
||||||
|
TCCRC: 0,
|
||||||
|
TIMSK: 0x59,
|
||||||
|
TOV: 0b00000010,
|
||||||
|
OCFA: 0b00010000,
|
||||||
|
OCFB: 0b00001000,
|
||||||
|
OCFC: 0,
|
||||||
|
TOIE: 0b00000010,
|
||||||
|
OCIEA: 0b00010000,
|
||||||
|
OCIEB: 0b00001000,
|
||||||
|
OCIEC: 0,
|
||||||
|
compPortA: 0x38,
|
||||||
|
compPinA: 0,
|
||||||
|
compPortB: 0x38,
|
||||||
|
compPinB: 1,
|
||||||
|
compPortC: 0,
|
||||||
|
compPinC: 0,
|
||||||
|
externalClockPort: 0x36,
|
||||||
|
externalClockPin: 2,
|
||||||
|
dividers: { 0: 0, 1: 1, 2: 8, 3: 64, 4: 256, 5: 1024, 6: 0, 7: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
/** Ordered list of Mega ports with their avr8js configs */
|
/** Ordered list of Mega ports with their avr8js configs */
|
||||||
const MEGA_PORT_CONFIGS = [
|
const MEGA_PORT_CONFIGS = [
|
||||||
{ name: 'PORTA', config: portAConfig },
|
{ name: 'PORTA', config: portAConfig },
|
||||||
|
|
@ -245,10 +352,26 @@ export class AVRSimulator {
|
||||||
this.cpu = new CPU(this.program, sramBytes);
|
this.cpu = new CPU(this.program, sramBytes);
|
||||||
|
|
||||||
if (this.boardVariant === 'tiny85') {
|
if (this.boardVariant === 'tiny85') {
|
||||||
// ATtiny85: PORTB only (PB0-PB5), Timer1 via ATtinyTimer1, no USART
|
// ATtiny85: PORTB only (PB0-PB5). Timer0 powers millis()/delay() in
|
||||||
|
// ATTinyCore via TIMER0_OVF. Timer1 is the high-speed 8-bit PWM
|
||||||
|
// timer (PLL clock). No hardware USART on this chip.
|
||||||
|
//
|
||||||
|
// Known limitation (task #116): the Timer0 OVF interrupt does fire at
|
||||||
|
// the correct cadence (1.024 ms simulated, verified via debug
|
||||||
|
// instrumentation), but real ATTinyCore-compiled `delay()` does not
|
||||||
|
// observably advance — the LED stays stuck either HIGH or LOW
|
||||||
|
// depending on which phase the firmware was in when the first OVF
|
||||||
|
// hit. Likely a subtle interaction between the avr8js clearInterrupt
|
||||||
|
// semantics (only clears the pending queue entry, leaves TIFR bit
|
||||||
|
// set) and ATTinyCore's ISR relying on hardware auto-clear of TOV0.
|
||||||
|
// Workaround attempts (manual TIFR clear after ISR entry) did not
|
||||||
|
// change the visible behavior. Needs a deeper avr8js dive.
|
||||||
this.portB = new AVRIOPort(this.cpu, attiny85PortBConfig as typeof portBConfig);
|
this.portB = new AVRIOPort(this.cpu, attiny85PortBConfig as typeof portBConfig);
|
||||||
this.adc = new AVRADC(this.cpu, adcConfig);
|
this.adc = new AVRADC(this.cpu, attiny85AdcConfig);
|
||||||
this.peripherals = [new ATtinyTimer1(this.cpu, attinyTimer1Config)];
|
this.peripherals = [
|
||||||
|
new AVRTimer(this.cpu, attiny85Timer0Config),
|
||||||
|
new ATtinyTimer1(this.cpu, attinyTimer1Config),
|
||||||
|
];
|
||||||
// usart stays null — ATtiny85 has no hardware USART
|
// usart stays null — ATtiny85 has no hardware USART
|
||||||
} else {
|
} else {
|
||||||
// ATmega2560 has more vectors before the timers/USART (8 external INTs, etc.),
|
// ATmega2560 has more vectors before the timers/USART (8 external INTs, etc.),
|
||||||
|
|
@ -611,8 +734,11 @@ export class AVRSimulator {
|
||||||
|
|
||||||
if (this.boardVariant === 'tiny85') {
|
if (this.boardVariant === 'tiny85') {
|
||||||
this.portB = new AVRIOPort(this.cpu, attiny85PortBConfig as typeof portBConfig);
|
this.portB = new AVRIOPort(this.cpu, attiny85PortBConfig as typeof portBConfig);
|
||||||
this.adc = new AVRADC(this.cpu, adcConfig);
|
this.adc = new AVRADC(this.cpu, attiny85AdcConfig);
|
||||||
this.peripherals = [new ATtinyTimer1(this.cpu, attinyTimer1Config)];
|
this.peripherals = [
|
||||||
|
new AVRTimer(this.cpu, attiny85Timer0Config),
|
||||||
|
new ATtinyTimer1(this.cpu, attinyTimer1Config),
|
||||||
|
];
|
||||||
this.usart = null;
|
this.usart = null;
|
||||||
} else {
|
} else {
|
||||||
this.spi = new AVRSPI(this.cpu, spiConfig, 16000000);
|
this.spi = new AVRSPI(this.cpu, spiConfig, 16000000);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,13 @@ export interface SimulatorBridges {
|
||||||
uartInstalled: boolean;
|
uartInstalled: boolean;
|
||||||
/** Original onByteTransmit so non-chip listeners still receive bytes. */
|
/** Original onByteTransmit so non-chip listeners still receive bytes. */
|
||||||
uartPreviousOnByteTransmit: ((byte: number) => void) | null;
|
uartPreviousOnByteTransmit: ((byte: number) => void) | null;
|
||||||
|
/** Pending bytes to inject into the AVR/RP2040 RX register, drained at
|
||||||
|
* ~baud rate by `uartDrainHandle`. Without this queue, chips that emit
|
||||||
|
* bursts (e.g. an i8080 printing a banner) overflow the 2-byte USART
|
||||||
|
* RX register and most bytes get silently dropped. */
|
||||||
|
uartRxQueue: number[];
|
||||||
|
/** setTimeout handle for the queue drainer (0 if not active). */
|
||||||
|
uartDrainHandle: number;
|
||||||
|
|
||||||
/** Shared SPI bus across all custom chips on this simulator. */
|
/** Shared SPI bus across all custom chips on this simulator. */
|
||||||
spiBus: SPIBus;
|
spiBus: SPIBus;
|
||||||
|
|
@ -49,6 +56,8 @@ export function getSimulatorBridges(simulator: any): SimulatorBridges {
|
||||||
uartListeners: new Set(),
|
uartListeners: new Set(),
|
||||||
uartInstalled: false,
|
uartInstalled: false,
|
||||||
uartPreviousOnByteTransmit: null,
|
uartPreviousOnByteTransmit: null,
|
||||||
|
uartRxQueue: [],
|
||||||
|
uartDrainHandle: 0,
|
||||||
spiBus: new SPIBus(),
|
spiBus: new SPIBus(),
|
||||||
spiInstalled: false,
|
spiInstalled: false,
|
||||||
spiPreviousOnByte: null,
|
spiPreviousOnByte: null,
|
||||||
|
|
@ -102,17 +111,57 @@ export function ensureUartBridge(simulator: any): void {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject a byte into the simulator's RX path so the sketch's `Serial.read()`
|
* Inject a byte into the simulator's RX path so the sketch's `Serial.read()`
|
||||||
* returns it. Routes per simulator family.
|
* returns it.
|
||||||
|
*
|
||||||
|
* For AVR we go through a JS-level FIFO + setTimeout drainer instead of
|
||||||
|
* calling `simulator.usart.writeByte` directly. Two reasons:
|
||||||
|
*
|
||||||
|
* 1. The non-immediate form silently returns `false` for bytes that arrive
|
||||||
|
* while `rxBusyValue` is still set from the previous one — so chips
|
||||||
|
* that emit bursts (e.g. an i8080 print_string sequence) lose ~99% of
|
||||||
|
* their bytes.
|
||||||
|
* 2. The immediate form overwrites `rxByte` directly without waiting for
|
||||||
|
* the AVR sketch to drain it — same outcome, only the last byte of
|
||||||
|
* each burst survives.
|
||||||
|
*
|
||||||
|
* The drainer attempts one non-immediate write per tick (1 ms apart). On
|
||||||
|
* RXC busy / RXEN off it leaves the byte at the head of the queue and
|
||||||
|
* retries on the next tick. RP2040 has its own internal buffering, so we
|
||||||
|
* just forward to `serialWriteByte`.
|
||||||
*/
|
*/
|
||||||
export function avrUartTx(simulator: any, byte: number): void {
|
export function avrUartTx(simulator: any, byte: number): void {
|
||||||
const kind = detectSimulatorKind(simulator);
|
const kind = detectSimulatorKind(simulator);
|
||||||
try {
|
if (kind === 'avr') {
|
||||||
if (kind === 'avr' && simulator.usart && typeof simulator.usart.writeByte === 'function') {
|
// ATtiny85 has no hardware USART — silently drop instead of queueing
|
||||||
simulator.usart.writeByte(byte);
|
// forever. Users wiring a UART chip to a tiny85 need SoftwareSerial,
|
||||||
} else if (kind === 'rp2040' && typeof simulator.serialWriteByte === 'function') {
|
// which is a different bridge entirely (TODO).
|
||||||
simulator.serialWriteByte(byte);
|
if (!simulator.usart || typeof simulator.usart.writeByte !== 'function') return;
|
||||||
|
const b = getSimulatorBridges(simulator);
|
||||||
|
b.uartRxQueue.push(byte & 0xff);
|
||||||
|
if (!b.uartDrainHandle) {
|
||||||
|
const drain = () => {
|
||||||
|
const b2 = getSimulatorBridges(simulator);
|
||||||
|
if (b2.uartRxQueue.length === 0) {
|
||||||
|
b2.uartDrainHandle = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const next = b2.uartRxQueue[0];
|
||||||
|
let accepted = false;
|
||||||
|
try {
|
||||||
|
accepted = simulator.usart?.writeByte?.(next) ?? false;
|
||||||
|
} catch {
|
||||||
|
accepted = false;
|
||||||
|
}
|
||||||
|
if (accepted) b2.uartRxQueue.shift();
|
||||||
|
b2.uartDrainHandle = (setTimeout(drain, 1) as unknown) as number;
|
||||||
|
};
|
||||||
|
b.uartDrainHandle = (setTimeout(drain, 0) as unknown) as number;
|
||||||
}
|
}
|
||||||
} catch { /* swallow — peripheral may not be ready yet */ }
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'rp2040' && typeof simulator.serialWriteByte === 'function') {
|
||||||
|
try { simulator.serialWriteByte(byte); } catch { /* swallow */ }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── SPI ─────────────────────────────────────────────────────────────────────
|
// ── SPI ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
getSimulatorBridges,
|
getSimulatorBridges,
|
||||||
avrUartTx,
|
avrUartTx,
|
||||||
getI2CBus,
|
getI2CBus,
|
||||||
|
detectSimulatorKind,
|
||||||
} from '../customChips';
|
} from '../customChips';
|
||||||
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
||||||
|
|
||||||
|
|
@ -88,7 +89,13 @@ PartSimulationRegistry.register('custom-chip', {
|
||||||
// ── ESP32 path ──────────────────────────────────────────────────────────
|
// ── ESP32 path ──────────────────────────────────────────────────────────
|
||||||
// The chip's WASM runs in the backend QEMU worker process so I2C events
|
// The chip's WASM runs in the backend QEMU worker process so I2C events
|
||||||
// are answered synchronously. See docs/wiki/custom-chips-esp32-backend-runtime.md.
|
// are answered synchronously. See docs/wiki/custom-chips-esp32-backend-runtime.md.
|
||||||
if (typeof sim.registerSensor === 'function') {
|
//
|
||||||
|
// IMPORTANT: only take this path for actual ESP32 simulators. We cannot
|
||||||
|
// gate on `typeof sim.registerSensor === 'function'` because AVR and
|
||||||
|
// RP2040 simulators also expose `registerSensor` for I2C sensor proxies —
|
||||||
|
// taking that branch on AVR routes the chip to the (non-existent) ESP32
|
||||||
|
// backend and the client-side ChipInstance never runs.
|
||||||
|
if (detectSimulatorKind(sim) === 'esp32' && typeof sim.registerSensor === 'function') {
|
||||||
// Resolve each chip pin name → ESP32 GPIO via the diagram's wires. The
|
// Resolve each chip pin name → ESP32 GPIO via the diagram's wires. The
|
||||||
// backend runtime uses this to call qemu_picsimlab_set_pin when the chip
|
// backend runtime uses this to call qemu_picsimlab_set_pin when the chip
|
||||||
// does vx_pin_write, and to read live GPIO state for vx_pin_read.
|
// does vx_pin_write, and to read live GPIO state for vx_pin_read.
|
||||||
|
|
@ -141,6 +148,7 @@ PartSimulationRegistry.register('custom-chip', {
|
||||||
// when the user stops the simulation.
|
// when the user stops the simulation.
|
||||||
let instance: ChipInstance | null = null;
|
let instance: ChipInstance | null = null;
|
||||||
let uartListener: ((byte: number) => void) | null = null;
|
let uartListener: ((byte: number) => void) | null = null;
|
||||||
|
let rafHandle = 0;
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -181,6 +189,24 @@ PartSimulationRegistry.register('custom-chip', {
|
||||||
try { (el as any).paintFramebuffer(rgba, width, height); } catch { /* swallow */ }
|
try { (el as any).paintFramebuffer(rgba, width, height); } catch { /* swallow */ }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drive the chip's timer-based execution every frame. Chips that
|
||||||
|
// register a periodic `vx_timer_create` (e.g. a CPU-emulator chip
|
||||||
|
// stepping its core, or a sensor publishing samples) need a
|
||||||
|
// host-side tick to fire those callbacks — without this loop the
|
||||||
|
// WASM is loaded but never executes anything past chip_setup().
|
||||||
|
// We feed wall-clock nanoseconds; the chip's WasiShim already
|
||||||
|
// exposes the same epoch via vx_sim_now_nanos.
|
||||||
|
const tick = () => {
|
||||||
|
if (disposed || !instance) return;
|
||||||
|
try {
|
||||||
|
instance.tickTimers(BigInt(Math.floor(performance.now() * 1_000_000)));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[custom-chip:${componentId}] tickTimers threw:`, e);
|
||||||
|
}
|
||||||
|
rafHandle = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
rafHandle = requestAnimationFrame(tick);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[custom-chip] ${componentId} failed to load:`, e);
|
console.error(`[custom-chip] ${componentId} failed to load:`, e);
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +214,8 @@ PartSimulationRegistry.register('custom-chip', {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
if (rafHandle) cancelAnimationFrame(rafHandle);
|
||||||
|
rafHandle = 0;
|
||||||
if (uartListener) bridges.uartListeners.delete(uartListener);
|
if (uartListener) bridges.uartListeners.delete(uartListener);
|
||||||
if (instance) instance.dispose();
|
if (instance) instance.dispose();
|
||||||
instance = null;
|
instance = null;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ function pinNameToArduinoPin(pinName: string, boardKind: BoardKind): number {
|
||||||
if (/^A\d+$/.test(pinName)) {
|
if (/^A\d+$/.test(pinName)) {
|
||||||
return 14 + parseInt(pinName.slice(1), 10);
|
return 14 + parseInt(pinName.slice(1), 10);
|
||||||
}
|
}
|
||||||
|
// ATtiny85 port-style names (PB0..PB5) map 1:1 to Arduino pin numbers
|
||||||
|
// because ATTinyCore's pinMap is identity for the tiny85 variant.
|
||||||
|
if (/^PB\d+$/.test(pinName)) {
|
||||||
|
return parseInt(pinName.slice(2), 10);
|
||||||
|
}
|
||||||
if (/^\d+$/.test(pinName)) {
|
if (/^\d+$/.test(pinName)) {
|
||||||
return parseInt(pinName, 10);
|
return parseInt(pinName, 10);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,15 @@ const ADC_PIN_MAP: Partial<Record<BoardKind, Array<{ pinName: string; channel: n
|
||||||
'arduino-uno': ADC_6CH,
|
'arduino-uno': ADC_6CH,
|
||||||
'arduino-nano': ADC_8CH,
|
'arduino-nano': ADC_8CH,
|
||||||
'arduino-mega': ADC_16CH,
|
'arduino-mega': ADC_16CH,
|
||||||
attiny85: adcRange('A', 0, 4),
|
// ATtiny85: ADC channels live on PORTB pins, not on A0..An.
|
||||||
|
// Wires reference the chip's own pin names (PB5/PB2/PB4/PB3).
|
||||||
|
// PB5 -> ADC0 PB2 -> ADC1 PB4 -> ADC2 PB3 -> ADC3
|
||||||
|
attiny85: [
|
||||||
|
{ pinName: 'PB5', channel: 0 },
|
||||||
|
{ pinName: 'PB2', channel: 1 },
|
||||||
|
{ pinName: 'PB4', channel: 2 },
|
||||||
|
{ pinName: 'PB3', channel: 3 },
|
||||||
|
],
|
||||||
|
|
||||||
// RP2040
|
// RP2040
|
||||||
'raspberry-pi-pico': [
|
'raspberry-pi-pico': [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue