From 6c58877a5cea60732d305babd6db1ba21bb1de86 Mon Sep 17 00:00:00 2001 From: ttbombadil Date: Sat, 24 Oct 2026 10:45:56 +0200 Subject: [PATCH 1/3] fix(emulator): drive the timer 1 PWM waveform of the ATtiny85 avr8js 0.21.1 drives no OC1A/OC1B signal when CTC1 is set, as ATTinyCore configures analogWrite on PB1/PB4, and counts phase-correct without CTC1. On the chip the counter runs from 0 to OCR1C in PWM mode and OC1x is high until the match with OCR1x (datasheet 12.2.2). The profile now drives both outputs from the counter and counts that sawtooth. Members are replaced as plain properties: an accessor on the instance slowed the emulation from 11x to 8x. Co-Authored-By: Claude Opus 5.5 --- docs/proposals/browser-emulation-plan.md | 4 +- packages/avr-emulator/README.md | 9 +- packages/avr-emulator/src/mcu/attiny85.ts | 87 ++++++++++++++++++- .../tests/attiny85-profile.test.ts | 65 +++++++++++++- 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/docs/proposals/browser-emulation-plan.md b/docs/proposals/browser-emulation-plan.md index bc0973393..fc8a4a580 100644 --- a/docs/proposals/browser-emulation-plan.md +++ b/docs/proposals/browser-emulation-plan.md @@ -1140,8 +1140,8 @@ Umgesetzt (`mcu/attiny85.ts`, exportiert als `ATTINY85`; registriert erst mit der Board-Hardware in P6.4). Weitere Funde: INT0 und PCINT teilen GIMSK/GIFR, avr8js verdrahtet das nur für je einen (das Profil umhüllt die Hooks); `ATtinyTimer1` erzeugt in der PWM-Konfiguration von ATTinyCore kein Signal an -OC1x (Register stimmen, das Tastverhältnis kommt aus ihnen; offen, siehe -README des Pakets). Die Meldungen an avr8js macht ein Mensch. +OC1x und zählt ohne CTC1 phasenkorrekt statt als Sägezahn (das Profil korrigiert +beides, siehe README des Pakets). Die Meldungen an avr8js macht ein Mensch. **P6.3 Software-UART (M).** TX-Pin des Cores dekodieren, Baudrate aus der Bitbreite (größte diff --git a/packages/avr-emulator/README.md b/packages/avr-emulator/README.md index 04702053d..c14d1083f 100644 --- a/packages/avr-emulator/README.md +++ b/packages/avr-emulator/README.md @@ -249,12 +249,13 @@ at least 90 % line coverage for `src/`. GIFR to the pin-change interrupt alone, so enabling PCIE did not raise a pending pin change and writing 1 to INTF0 did not clear INT0. The profile wraps both hooks. -- **Timer 1 PWM on the ATtiny85 (open):** `ATtinyTimer1` models PWM mode as +- **Timer 1 PWM on the ATtiny85:** `ATtinyTimer1` models PWM mode as phase-correct counting and drives no OC1A/OC1B waveform when CTC1 is set, which is how ATTinyCore configures it for `analogWrite` on PB1/PB4. On the - chip the counter runs from 0 to OCR1C, OC1x is set at 0 and cleared at OCR1x - (COM1x = 10). The registers are right, so a duty cycle derived from them is - right; a sketch that reads the pin level of a timer-1 PWM pin sees LOW. + chip the counter runs from 0 to OCR1C either way, OC1x is set at 0 and + cleared at OCR1x (COM1x = 01 or 10; inverted with 11). The profile drives + OC1A/OC1B from the counter and replaces the phase-correct count by that + sawtooth. Not modelled: the inverted outputs on PB0/PB3 of COM1x = 01. ## ATmega2560 profile diff --git a/packages/avr-emulator/src/mcu/attiny85.ts b/packages/avr-emulator/src/mcu/attiny85.ts index 121d0a884..64bebb502 100644 --- a/packages/avr-emulator/src/mcu/attiny85.ts +++ b/packages/avr-emulator/src/mcu/attiny85.ts @@ -85,6 +85,91 @@ function shareInterruptRegisters(cpu: CPU): void { }; } +/** The members of avr8js' `ATtinyTimer1` (private in 0.21.1, pinned) that `correctTimer1Pwm` replaces or reads. */ +interface ATtinyTimer1Internals { + readonly TOP: number; + readonly ocrA: number; + readonly ocrB: number; + readonly comA: number; + readonly comB: number; + readonly tcntUpdated: boolean; + readonly OVF: InterruptConfig; + readonly OCFA: InterruptConfig; + readonly OCFB: InterruptConfig; + phasePwmCount(value: number, delta: number): number; + timerUpdated(value: number, prevValue: number): void; +} + +const PWM1A = 0x40; // TCCR1 +const PWM1B = 0x40; // GTCCR +const OC1A_PIN = 1; // PB1 +const OC1B_PIN = 4; // PB4 +/** `PinOverrideMode.Set` and `.Clear` of avr8js; the package does not export the enum. */ +type PinOverrideMode = Parameters[1]; +const OVERRIDE_SET = 2 as PinOverrideMode; +const OVERRIDE_CLEAR = 3 as PinOverrideMode; + +/** + * In PWM mode timer 1 of the ATtiny85 counts up from 0 to OCR1C and starts at 0 again, + * with or without CTC1 (datasheet 12.2.2; ATTinyCore sets OCR1C = 255 because "this module + * doesn't have a 8bit PWM mode"). OC1x is high from 0 until the match with OCR1x + * (COM1x = 01 or 10) and inverted with COM1x = 11. avr8js drives no waveform with CTC1 set, + * as ATTinyCore configures `analogWrite`, and counts phase-correct without it. The + * correction drives OC1A (PB1) and OC1B (PB4) from the counter after every count with CTC1 + * (avr8js counts that sawtooth right), and replaces the phase-correct count by the sawtooth + * with its flags. Not modelled: the inverted outputs on PB0 and PB3 of COM1x = 01. + * Members are replaced as plain properties: an accessor on the instance slows the whole + * timer down in V8 (ATtiny85 emulation 11 × → 8 × real time). + */ +function correctTimer1Pwm(cpu: CPU, timer: ATtinyTimer1): void { + const internals = timer as unknown as ATtinyTimer1Internals; + const tccr1 = attinyTimer1Config.TCCR1; + const gtccr = attinyTimer1Config.GTCCR; + const port = () => cpu.gpioByPort[attinyTimer1Config.compPortB]; + + const output = (pin: number, com: number, ocr: number, prev: number, count: number) => { + if (com === 0 || (count !== 0 && (prev < ocr) === (count < ocr))) return; + const high = (count < ocr) === (com !== 3); + port()?.timerOverridePin(pin, high ? OVERRIDE_SET : OVERRIDE_CLEAR); + }; + /** OC1A and OC1B for a step of the counter from `prev` to `count`. */ + const outputs = (prev: number, count: number) => { + if (cpu.data[tccr1] & PWM1A) output(OC1A_PIN, internals.comA, internals.ocrA, prev, count); + if (cpu.data[gtccr] & PWM1B) output(OC1B_PIN, internals.comB, internals.ocrB, prev, count); + }; + + const timerUpdated = internals.timerUpdated.bind(timer); + internals.timerUpdated = (value, prevValue) => { + timerUpdated(value, prevValue); + outputs(prevValue, value); + }; + + /** Flags of the sawtooth for the counter value just reached (avr8js sets them only on the CTC path). */ + const flags = (count: number) => { + if (count === 0) cpu.setInterruptFlag(internals.OVF); + if (count === internals.ocrA) cpu.setInterruptFlag(internals.OCFA); + if (count === internals.ocrB) cpu.setInterruptFlag(internals.OCFB); + }; + internals.phasePwmCount = (value, delta) => { + const top = internals.TOP; + let count = value; + for (let step = 0; step < delta; step++) { + const prev = count; + count = count >= top ? 0 : count + 1; + if (internals.tcntUpdated) continue; + flags(count); + outputs(prev, count); + } + return count & 0xff; + }; +} + +function createTimer1(cpu: CPU): ATtinyTimer1 { + const timer = new ATtinyTimer1(cpu, attinyTimer1Config); + correctTimer1Pwm(cpu, timer); + return timer; +} + /** Timer 0: own registers and vectors; OC0A on PB0, OC0B on PB1, T0 on PB2. */ const timer0: AVRTimerConfig = { ...timer0Config, @@ -190,7 +275,7 @@ export const ATTINY85: McuProfile = { shareInterruptRegisters(cpu); return { ports: new Map([["B", port]]), - timers: [new AVRTimer(cpu, timer0), new ATtinyTimer1(cpu, attinyTimer1Config)], + timers: [new AVRTimer(cpu, timer0), createTimer1(cpu)], usarts: [], adc: new Attiny85ADC(cpu, adc), eeprom: new AVREEPROM(cpu, new ArrayEepromBackend(eeprom), { diff --git a/packages/avr-emulator/tests/attiny85-profile.test.ts b/packages/avr-emulator/tests/attiny85-profile.test.ts index 20d2f9bc0..9d666f6aa 100644 --- a/packages/avr-emulator/tests/attiny85-profile.test.ts +++ b/packages/avr-emulator/tests/attiny85-profile.test.ts @@ -112,8 +112,10 @@ describe("ATtiny85 profile (plan P6.2)", () => { expect(data[0x4b]).toBe(200); // OCR1B expect(data[0x4d]).toBe(255); // OCR1C = TOP expect(data[0x4c] & 0x70).toBe(0x60); // GTCCR: PWM1B, COM1B1:0 = 10 - // avr8js 0.21.1 drives no OC1B waveform in this mode (README, known differences); the duty - // shown in UnoSim comes from these registers, not from the pin. + }); + + it("drives the timer 1 waveform on PB4: high from 0 to OCR1B, period OCR1C + 1 (avr8js alone: none)", () => { + expect(highShare(probe, 4)).toBeCloseTo(200 / 256, 1); }); it("drives fast PWM on PB0 with timer 0", () => { @@ -169,6 +171,65 @@ describe("ATtiny85 profile (plan P6.2)", () => { }); }); + describe("timer 1 PWM (datasheet 12.2.2; avr8js counts phase-correct and drives no pin with CTC1)", () => { + const TCCR1 = 0x50; + const GTCCR = 0x4c; + const OCR1A = 0x4e; + const OCR1B = 0x4b; + const OCR1C = 0x4d; + const TIFR = 0x58; + const DDRB = 0x37; + + /** Level of `pin` after each of the next `cycles` CPU cycles, as 0/1 string, timer 1 at prescaler 1. */ + function levels({ cpu, peripherals }: Tiny, pin: number, cycles: number): string { + const port = peripherals.ports.get("B"); + let out = ""; + for (let i = 0; i < cycles; i++) { + cpu.cycles++; + cpu.tick(); + out += port?.pinState(pin) === PinState.High ? "1" : "0"; + } + return out; + } + + function pwmTiny(setup: (cpu: CPU) => void): Tiny { + const probe = tiny(); + probe.cpu.writeData(DDRB, (1 << 1) | (1 << 4)); + probe.cpu.writeData(OCR1C, 9); // period 10 counts + setup(probe.cpu); + levels(probe, 1, 25); // settle into the first periods + return probe; + } + + it("sets OC1A at 0 and clears it on OCR1A, period OCR1C + 1, with CTC1 as ATTinyCore sets it", () => { + const probe = pwmTiny((cpu) => { + cpu.writeData(OCR1A, 3); + cpu.writeData(TCCR1, 0x80 | 0x40 | 0x20 | 0x01); // CTC1, PWM1A, COM1A = 10, CK/1 + }); + const wave = levels(probe, 1, 40); + expect(wave.match(/1+/g)?.slice(0, 3).map((run) => run.length)).toEqual([3, 3, 3]); + expect(wave.match(/0+/g)?.slice(1, 3).map((run) => run.length)).toEqual([7, 7]); + }); + + it("counts the same sawtooth without CTC1 (not phase-correct)", () => { + const probe = pwmTiny((cpu) => { + cpu.writeData(OCR1A, 3); + cpu.writeData(TCCR1, 0x40 | 0x20 | 0x01); // PWM1A, COM1A = 10, CK/1 + }); + expect(levels(probe, 1, 40).match(/1+/g)?.slice(0, 3).map((run) => run.length)).toEqual([3, 3, 3]); + }); + + it("inverts OC1B with COM1B = 11 and sets TOV1 at the wrap to 0", () => { + const probe = pwmTiny((cpu) => { + cpu.writeData(OCR1B, 3); + cpu.writeData(GTCCR, 0x40 | 0x30); // PWM1B, COM1B = 11 + cpu.writeData(TCCR1, 0x01); // CK/1 + }); + expect(levels(probe, 4, 40).match(/1+/g)?.slice(1, 3).map((run) => run.length)).toEqual([7, 7]); + expect(probe.cpu.data[TIFR] & (1 << 2)).toBeTruthy(); // TOV1 + }); + }); + it("resets on a 15 ms watchdog timeout and reports it in MCUSR", () => { const probe = tiny("tiny_watchdog"); run(probe, 100); From f8c236912cb4d291759cb127f7a4f5671f4fb2ed Mon Sep 17 00:00:00 2001 From: ttbombadil Date: Sat, 24 Oct 2026 10:49:08 +0200 Subject: [PATCH 2/3] docs(status): record the timer 1 PWM correction of the ATtiny85 Co-Authored-By: Claude Opus 5.5 --- docs/proposals/browser-emulation-status.md | 4 ++-- docs/proposals/report-m3-acceptance.md | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/proposals/browser-emulation-status.md b/docs/proposals/browser-emulation-status.md index 1479764e8..457fa0859 100644 --- a/docs/proposals/browser-emulation-status.md +++ b/docs/proposals/browser-emulation-status.md @@ -68,11 +68,11 @@ | P7.1 | erledigt bis auf die Messung am Referenz-Host (wartet auf Mensch) | #332 | Compile-Phase in `npm run capacity:calibrate` (vier Schritte, Tabelle im Bericht, `--skip-compile`), Compile-Container tragen im Testprofil das Label des Kapazitätslaufs; CAPACITY_MODEL „Compile capacity (browser emulation)“: Modell, Kenngrößen nach 4.12, Messung auf M2 Pro (30 gleichzeitige neue Compiles p95 6,3 s, ≈ 275 Compiles/min, unverändertes Beispiel 8 ms); Messung auf der Referenz-VM: [human-tests.md, Teil 3](human-tests.md) | | P2.3 | erledigt | #333 | ATTinyCore 1.5.2 gepinnt (Version, HTTPS-Archiv, SHA-256), Installation ohne Board-Manager ins Sketchbook mit `platform.local.txt` gegen die Post-Build-Hooks (Fund: sie schreiben in Core und Sketch; HEX identisch mit und ohne), Prüfskript und Canary-Test; Compile-Sandbox-Image mit ATTinyCore und vorkompiliertem Core je FQBN; CI (Toolchain- und E2E-Job, Cache-Schlüssel, Deployment-Prüfschritt); Toolchain-Gate `toolchain-cores.test.ts` (alle vier Boards); INSTALL_LOCAL, README (Lizenzen LGPL 2.1). Abweichung: kein Board-Manager, weil `drazzy.com` und `azduino.com` abgelaufene Zertifikate liefern | | P6.1 | erledigt | #333 (mit P2.3) | ATTinyCore im Compile-Sandbox-Image; Vertragstest kompiliert für den ATtiny85 in der Sandbox | -| P6.2 | erledigt | #334 | `mcu/attiny85.ts`: Port B mit INT0 (PB2) und PCINT0, Timer 0 mit eigenen Registern und Ein-Wort-Vektoren, `ATtinyTimer1`, ADC mit 4 Kanälen und REFS2 an Bit 4 (avr8js-Fehler), EEPROM 512 B mit Schreibzeit für 8 MHz, Watchdog, Taktteiler, kein USART; RAMEND 0x25F. Tests `attiny85-profile.test.ts` (13) mit drei ATTinyCore-Fixtures (Ergebnisse im EEPROM). Funde: GIMSK/GIFR werden von INT0 und PCINT geteilt, avr8js verdrahtet nur je einen (behoben); `ATtinyTimer1` erzeugt in der PWM-Konfiguration von ATTinyCore kein OC1x-Signal (offen, Register stimmen); Spike-Spanne für `delay(587)` war zu eng (585–589 ms abgeleitet). Registriert mit P6.4. Upstream-Meldungen an avr8js: wartet auf Mensch | +| P6.2 | erledigt | #334, #341 | `mcu/attiny85.ts`: Port B mit INT0 (PB2) und PCINT0, Timer 0 mit eigenen Registern und Ein-Wort-Vektoren, `ATtinyTimer1`, ADC mit 4 Kanälen und REFS2 an Bit 4 (avr8js-Fehler), EEPROM 512 B mit Schreibzeit für 8 MHz, Watchdog, Taktteiler, kein USART; RAMEND 0x25F. Tests `attiny85-profile.test.ts` (13) mit drei ATTinyCore-Fixtures (Ergebnisse im EEPROM). Funde: GIMSK/GIFR werden von INT0 und PCINT geteilt, avr8js verdrahtet nur je einen (behoben); `ATtinyTimer1` erzeugt in der PWM-Konfiguration von ATTinyCore kein OC1x-Signal und zählt ohne CTC1 phasenkorrekt (behoben in #341: Signal aus dem Zählerstand, Sägezahn nach Datenblatt); Spike-Spanne für `delay(587)` war zu eng (585–589 ms abgeleitet). Registriert mit P6.4. Upstream-Meldungen an avr8js: wartet auf Mensch | | P6.3 | erledigt (Modell) | #335 | `io/software-uart.ts`: TX-Decoder an PB0 (nur Ausgang, nicht OC0A) mit Baudraten-Erkennung (größte Standardrate, 90 % der Abstände ganzzahlige Bitvielfache) und gemessener Bitbreite, Rückfall auf die eingestellte Rate bei Ruhe, Startbit-Prüfung in Bitmitte; RX über ein Komparatormodell (ACSR, Bandlücke/AIN0 gegen PB1, Vektor 7), Empfänger aktiv mit `ACIE`; Schnittstelle wie `AVRUSART` für `SerialPorts`. Tests `software-uart.test.ts` (7: 9600/19200, UTF-8, Rückfall, Echo, gesperrte Eingabe, synthetische Flanken). Anbindung an Serial-Schicht und Serial-Monitor-Baudrate mit P6.4, Benutzerhandbuch mit P6.5 | | P6.4 | erledigt | #336 | `ATTINY85_BOARD` (Pins 0–5 = PB0–PB5, A0–A3 als Zweitnamen von 5/2/4/3, PWM 0/1/4, RESET PB5, `LED_BUILTIN` 1, `Serial` als Software-Port 0/1), Profil und Hardware registriert; `SerialPorts` mit USART oder `SoftwareUart` (`setFallbackBaud`); PWM-Tastverhältnis für Timer 1 des ATtiny85 (`pwm.ts`, `pwmChannelRegisters`); Shared: `analogName`, Parser rechnen `A` = `0x80 \| n` und `analogRead(n)` als Kanal wie ATTinyCore; Regeln RESET-Pin und Serial-Pins; Pinbelegung im Toolchain-Gate gegen `variants/tinyX5` (Konstanten über den Compiler ausgewertet). Freigegebene Teständerungen (Nutzer, 2026-10-23): fünf Tests nutzen für „Board ohne Hardware“ eine unbekannte ID statt `attiny85`. Board bleibt `planned` bis P6.5 | | P6.5 | erledigt | #337 | ATtiny85 `preview`: DIP-8-Ansicht (`npm run generate:attiny85-svg`), LED „L“ und Analog-Beschriftungen nach `LED_BUILTIN` bzw. Sketch-Namen des Boards, Analogdialog „2 (A1)“; Startbefehl mit `serialBaud` aus `Serial.begin()` (SSOT SimulationRuntime); 5 Korpus-Proben (`derived`); `e2e/attiny85.spec.ts` (3); Benutzerhandbuch „Board wählen“ mit Halbduplex, RESET-Pin, Serial-Pins; Funde: Baudraten-Erkennung rastete auf 19200 statt 9600 ein (jetzt Rahmenprüfung über alle passenden Raten), Byte 0x00 aus `digitalWrite(0)` ohne `Serial` (TX nur bei aktivem `Serial`). Freigegebene Teständerungen (Nutzer, 2026-10-23): sieben Tests auf Status `preview` des ATtiny85 umgestellt | -| M3 | erledigt (mit ausgewiesenen Ausnahmen) | #337 | ATtiny85 als `preview` freigegeben, Proben und E2E grün, 11 × Echtzeit; **keine Aufnahme an echter ATtiny85-Hardware** (alle Fälle `derived`), Timer-1-PWM-Signal in avr8js offen, Meldungen an avr8js warten auf Mensch; siehe [report-m3-acceptance.md](report-m3-acceptance.md) | +| M3 | erledigt (mit ausgewiesenen Ausnahmen) | #337 | ATtiny85 als `preview` freigegeben, Proben und E2E grün, 11 × Echtzeit; **keine Aufnahme an echter ATtiny85-Hardware** (alle Fälle `derived`), Timer-1-PWM-Signal im Profil korrigiert (#341), Meldungen an avr8js warten auf Mensch; siehe [report-m3-acceptance.md](report-m3-acceptance.md) | | (Rest P4.4) Mobile-Layout je Board | erledigt | #338 | Geprüft mit Screenshots aller vier Boards bei 390 × 844: die schwebende Panel-Navigation überdeckte unten rechts Pins (Uno A0–A5, ATtiny85 PB0–PB2). Die mobile Board-Ansicht hält jetzt unten 20rem für die Navigation frei (`index.css`), das Board steht darüber; Uno, Nano, Mega und ATtiny85 sind frei bedienbar | | Anleitung | bereit | #290 | [human-tests.md](human-tests.md): P0.3 mit `npm run spike:p03` (Messseite für Browser auf Geräten), P1.14-Aufnahmen mit `npm run record:hardware`, P7.1 Compile-Kapazität auf dem Referenz-Host | diff --git a/docs/proposals/report-m3-acceptance.md b/docs/proposals/report-m3-acceptance.md index fd71aa244..11192dbec 100644 --- a/docs/proposals/report-m3-acceptance.md +++ b/docs/proposals/report-m3-acceptance.md @@ -18,9 +18,9 @@ alle ATtiny-Fälle sind `derived`. |---|---|---| | P6.1 Toolchain im Compile-Sandbox-Image | erfüllt | #333: ATTinyCore 1.5.2 mit gepinnter SHA-256 im Image, Core je FQBN vorkompiliert; Vertragstest kompiliert für den ATtiny85 in der Sandbox; Toolchain-Gate `toolchain-cores.test.ts` für alle vier Boards | | P6.2 MCU-Profil | erfüllt | #334: Port B mit INT0/PCINT0, Timer 0 und 1, ADC mit REFS2 an Bit 4, EEPROM, Watchdog; `attiny85-profile.test.ts` (13 Fälle) | -| P6.3 Software-UART | erfüllt | #335 und dieser PR: TX-Dekodierung mit Baudraten-Erkennung, RX über das Komparatormodell; `software-uart.test.ts` (8 Fälle) | +| P6.3 Software-UART | erfüllt | #335 und #337: TX-Dekodierung mit Baudraten-Erkennung, RX über das Komparatormodell; `software-uart.test.ts` (8 Fälle) | | P6.4 Board-Hardware und Regeln | erfüllt | #336: `ATTINY85_BOARD`, Serial-Port der Art `software`, PWM-Beobachtung Timer 1, Parser rechnen `A` wie ATTinyCore, Warnungen RESET-Pin und Serial-Pins; Pinbelegung im Toolchain-Gate gegen `variants/tinyX5` | -| P6.5 Freigabe `preview` | erfüllt | dieser PR: `shared/boards.ts` `attiny85` `preview`, Laufzeit `browser`; Boardansicht DIP-8 (`npm run generate:attiny85-svg`); Benutzerhandbuch „Board wählen“ mit den Grenzen des Boards | +| P6.5 Freigabe `preview` | erfüllt | #337: `shared/boards.ts` `attiny85` `preview`, Laufzeit `browser`; Boardansicht DIP-8 (`npm run generate:attiny85-svg`); Benutzerhandbuch „Board wählen“ mit den Grenzen des Boards | | Proben grün | erfüllt | 5 Korpus-Proben (`attiny_basics`, `attiny_adc_read`, `attiny_pwm_duty`, `attiny_serial_echo`, `attiny_interrupts`), dazu die Paket-Tests von Profil, UART und Board | | E2E grün | erfüllt | `e2e/attiny85.spec.ts`, 3 Szenarien (unten), 3 von 3 in 8,7 s; ganze Suite 54 von 54 | | Bundle | erfüllt | `npm run check:bundle`: initialer Ladepfad 691,0 kB von 710 kB (die Boardansicht lädt seit #326 mit eigenem Chunk); avr8js nur im Worker-Chunk | @@ -66,18 +66,15 @@ ist Arbeit für einen Menschen, eine Anleitung dafür fehlt noch. | avr8js liest REFS2 an Bit 3 | eigener Getter im Profil (#334, aus Spike P0.5) | | avr8js verdrahtet die gemeinsamen Register GIMSK/GIFR nur für INT0 bzw. PCINT | Hooks im Profil umhüllt (#334) | | `analogRead(A1)` wurde im Parser zu Pin 4 (Pin- und Kanalnummer überlagern sich) | Parser rechnen `A` = `0x80 \| n` wie ATTinyCore (#336) | -| LED „L“ und Analog-Beschriftungen der Boardansicht waren fest auf Pin 13 bzw. `A`-Labels | `LED_BUILTIN` und Sketch-Namen des Boards (dieser PR) | -| Baudraten-Erkennung rastete auf ein Vielfaches ein (19200 statt 9600) | Entscheidung erst, wenn jede passende Rate genug Rahmen hat; die Rate ohne Rahmenfehler gewinnt (dieser PR, Probe `attiny_basics`) | -| `digitalWrite(0, LOW)` ohne `Serial` erzeugte ein Byte 0x00 | TX wird nur dekodiert, solange `Serial` aktiv ist (dieser PR, Probe `attiny_pwm_duty`) | +| LED „L“ und Analog-Beschriftungen der Boardansicht waren fest auf Pin 13 bzw. `A`-Labels | `LED_BUILTIN` und Sketch-Namen des Boards (#337) | +| Baudraten-Erkennung rastete auf ein Vielfaches ein (19200 statt 9600) | Entscheidung erst, wenn jede passende Rate genug Rahmen hat; die Rate ohne Rahmenfehler gewinnt (#337, Probe `attiny_basics`) | +| `digitalWrite(0, LOW)` ohne `Serial` erzeugte ein Byte 0x00 | TX wird nur dekodiert, solange `Serial` aktiv ist (#337, Probe `attiny_pwm_duty`) | | Spanne für `delay(587)` aus dem Spike (586–588 ms) zu eng | abgeleitet 585–589 ms (#334) | +| avr8js erzeugt in der PWM-Konfiguration von ATTinyCore kein Signal an OC1A/OC1B (Timer 1) und zählt ohne CTC1 phasenkorrekt | Profil treibt OC1A/OC1B aus dem Zählerstand und zählt als Sägezahn wie das Datenblatt (#341; vier Tests, gleiche Geschwindigkeit) | ## Bekannte Restpunkte (nicht M3-blockierend) - **Keine Aufnahme an echter ATtiny85-Hardware** (alle Fälle `derived`). -- **Timer-1-PWM-Signal:** avr8js 0.21.1 erzeugt in der PWM-Konfiguration von - ATTinyCore kein Signal an OC1A/OC1B. Das Tastverhältnis in UnoSim kommt aus - den Registern und stimmt; ein Sketch, der den Pegel des Pins liest, sieht LOW - (README des Pakets). - **Meldungen an avr8js** (REFS2, GIMSK/GIFR, Timer-1-PWM): nach außen gerichtet, wartet auf einen Menschen. - **Halbduplex** des Software-UART ist Eigenschaft des Boards und im From 569f450ddccde1d9d81f25f5603292d78d027e3b Mon Sep 17 00:00:00 2001 From: ttbombadil Date: Sat, 24 Oct 2026 11:11:18 +0200 Subject: [PATCH 3/3] docs(emulator): name the datasheet section of the ATtiny85 timer 1 by title Co-Authored-By: Claude Opus 5.5 --- packages/avr-emulator/src/mcu/attiny85.ts | 2 +- packages/avr-emulator/tests/attiny85-profile.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/avr-emulator/src/mcu/attiny85.ts b/packages/avr-emulator/src/mcu/attiny85.ts index 64bebb502..1fbc0abb6 100644 --- a/packages/avr-emulator/src/mcu/attiny85.ts +++ b/packages/avr-emulator/src/mcu/attiny85.ts @@ -111,7 +111,7 @@ const OVERRIDE_CLEAR = 3 as PinOverrideMode; /** * In PWM mode timer 1 of the ATtiny85 counts up from 0 to OCR1C and starts at 0 again, - * with or without CTC1 (datasheet 12.2.2; ATTinyCore sets OCR1C = 255 because "this module + * with or without CTC1 (datasheet, "Timer/Counter1 in PWM Mode"; ATTinyCore sets OCR1C = 255 because "this module * doesn't have a 8bit PWM mode"). OC1x is high from 0 until the match with OCR1x * (COM1x = 01 or 10) and inverted with COM1x = 11. avr8js drives no waveform with CTC1 set, * as ATTinyCore configures `analogWrite`, and counts phase-correct without it. The diff --git a/packages/avr-emulator/tests/attiny85-profile.test.ts b/packages/avr-emulator/tests/attiny85-profile.test.ts index 9d666f6aa..8e47ba0ba 100644 --- a/packages/avr-emulator/tests/attiny85-profile.test.ts +++ b/packages/avr-emulator/tests/attiny85-profile.test.ts @@ -171,7 +171,7 @@ describe("ATtiny85 profile (plan P6.2)", () => { }); }); - describe("timer 1 PWM (datasheet 12.2.2; avr8js counts phase-correct and drives no pin with CTC1)", () => { + describe("timer 1 PWM (datasheet: Timer/Counter1 in PWM Mode; avr8js counts phase-correct and drives no pin with CTC1)", () => { const TCCR1 = 0x50; const GTCCR = 0x4c; const OCR1A = 0x4e;