ATtiny85 Timer0 OVF fires correctly but ATTinyCore delay() never advances
Body (pegar tal cual):
Summary
Configuring the generic AVRTimer for ATtiny85's Timer0 (per the README's "highly configurable" claim) lets the overflow interrupt fire at the correct cadence, but
ATTinyCore-compiled sketches that call delay() or read millis() never progress past the first iteration of loop(). The LED in a Blink sketch stays stuck either HIGH
or LOW depending on which phase the firmware was in when the first OVF arrived.
Environment
avr8js@2.1.0 (also reproduced on 2.0.x)
- ATTinyCore
1.5.2, board ATTinyCore:avr:attinyx5:chip=85,clock=16pll
- Hand-built setup — no dedicated
ATtinyTimer0 class exists, so the generic AVRTimer is the only option per the README
Repro
Arduino sketch compiled with ATTinyCore:
void setup() {
pinMode(1, OUTPUT); // PB1
}
void loop() {
digitalWrite(1, HIGH);
delay(500);
digitalWrite(1, LOW);
delay(500);
}
Minimal simulator config:
import {
CPU, AVRIOPort, AVRTimer, ATtinyTimer1, attinyTimer1Config, avrInstruction,
} from 'avr8js';
const attiny85PortB = { PIN: 0x36, DDR: 0x37, PORT: 0x38, externalInterrupts: [] };
// ATtiny85 register addresses (from <avr/iotnx5.h>).
// Vectors are 1-word RJMP on ATtiny85, so the "address" field is the raw
// _VECTOR(N) index — not vector*2 like ATmega328P's 2-word JMPs.
const attiny85Timer0Config = {
bits: 8,
ovfInterrupt: 0x05, // _VECTOR(5) TIMER0_OVF
compAInterrupt: 0x0a, // _VECTOR(10) TIMER0_COMPA
compBInterrupt: 0x0b, // _VECTOR(11) TIMER0_COMPB
captureInterrupt: 0, compCInterrupt: 0,
TIFR: 0x58, TIMSK: 0x59,
TCCRA: 0x4f, TCCRB: 0x53, TCCRC: 0,
TCNT: 0x52, OCRA: 0x56, OCRB: 0x5c, OCRC: 0, ICR: 0,
TOV: 0b00000010, OCFA: 0b00010000, OCFB: 0b00001000, OCFC: 0,
TOIE: 0b00000010, OCIEA: 0b00010000, OCIEB: 0b00001000, OCIEC: 0,
compPortA: 0x38, compPinA: 0, // PB0 = OC0A
compPortB: 0x38, compPinB: 1, // PB1 = OC0B
compPortC: 0, compPinC: 0,
externalClockPort: 0x36, externalClockPin: 2, // T0 = PB2
dividers: { 0: 0, 1: 1, 2: 8, 3: 64, 4: 256, 5: 1024, 6: 0, 7: 0 },
};
const cpu = new CPU(program, /* sram */ 512);
new AVRIOPort(cpu, attiny85PortB);
new AVRTimer(cpu, attiny85Timer0Config); // Timer0
new ATtinyTimer1(cpu, attinyTimer1Config); // Timer1 (chains TIFR/TIMSK hooks)
while (running) { avrInstruction(cpu); cpu.tick(); }
Expected
LED toggles every 500 ms (matches real silicon).
Actual
LED stays stuck at the first state the firmware wrote (HIGH or LOW), never toggles. CPU keeps running normally — PC varies across many addresses, cycles advance, firmware
is NOT stuck on a single instruction.
Diagnostic
Instrumented AVRTimer.count() to log every OVF fire on Timer0:
[T0 OVF #1] cycles=16463, TIMSK=10, TIFR=11010, TCCRA=0, TCCRB=11, MIE=1
[T0 OVF #2] cycles=32844, TIMSK=10, TIFR=1111110, TCCRA=0, TCCRB=11, MIE=1
[T0 OVF #3] cycles=49228, ...
[T0 OVF #4] cycles=65612, ...
[T0 OVF #5] cycles=81996, ...
... (continues at the same rate indefinitely)
- Delta between fires: ~16384 cycles ≈ 256 × 64 prescaler → 1.024 ms simulated (matches what
millis() expects).
TIMSK = 0b10 → TOIE0 is set ✓
MIE (global I-bit) is set ✓
TCCRB = 0b11 → CS = 3 → prescaler 64 ✓
So Timer0 IS overflowing at the right rate. But the ATTinyCore ISR body millis_timer_overflow_count++; millis_timer_millis += MILLIS_INC; ... apparently doesn't advance
the millis counter visibly, because the firmware's while ((millis() - start) < ms) yield(); busy-wait never exits.
Hypothesis
After cpu.tick() calls avrInterrupt(this, interrupt.address) it then runs this.clearInterrupt(interrupt). That removes the entry from pendingInterrupts but leaves
the TIFR bit set in cpu.data[flagRegister]. On real hardware, TOV0 is auto-cleared by hardware when the ISR vector is entered.
The TIFR bit lingering may cause subtle re-entry or interfere with how subsequent OVFs are queued / dispatched.
Workaround attempts (none worked)
- Manually
cpu.data[0x58] &= ~0x02 via a clock event scheduled 8 cycles after queueInterrupt(OVF). No visible behavior change.
- Patched
count() to log every OVF — confirmed firings are at the right cadence.
- Verified vector address: ATtiny85 uses 1-word RJMP vectors, so
ovfInterrupt: 0x05 (not 0x0a = vector*2 like ATmega328P). Matches attinyTimer1Config.ovfInterrupt: 0x04 (= _VECTOR(4) TIMER1_OVF).
Related
ATtiny85 Timer0 OVF fires correctly but ATTinyCore delay() never advances
Body (pegar tal cual):
Summary
Configuring the generic
AVRTimerfor ATtiny85's Timer0 (per the README's "highly configurable" claim) lets the overflow interrupt fire at the correct cadence, butATTinyCore-compiled sketches that call
delay()or readmillis()never progress past the first iteration ofloop(). The LED in a Blink sketch stays stuck either HIGHor LOW depending on which phase the firmware was in when the first OVF arrived.
Environment
avr8js@2.1.0(also reproduced on2.0.x)1.5.2, boardATTinyCore:avr:attinyx5:chip=85,clock=16pllATtinyTimer0class exists, so the genericAVRTimeris the only option per the READMERepro
Arduino sketch compiled with ATTinyCore:
Minimal simulator config:
Expected
LED toggles every 500 ms (matches real silicon).
Actual
LED stays stuck at the first state the firmware wrote (HIGH or LOW), never toggles. CPU keeps running normally — PC varies across many addresses, cycles advance, firmware
is NOT stuck on a single instruction.
Diagnostic
Instrumented
AVRTimer.count()to log every OVF fire on Timer0:millis()expects).TIMSK = 0b10→TOIE0is set ✓MIE(global I-bit) is set ✓TCCRB = 0b11→ CS = 3 → prescaler 64 ✓So Timer0 IS overflowing at the right rate. But the ATTinyCore ISR body
millis_timer_overflow_count++; millis_timer_millis += MILLIS_INC; ...apparently doesn't advancethe millis counter visibly, because the firmware's
while ((millis() - start) < ms) yield();busy-wait never exits.Hypothesis
After
cpu.tick()callsavrInterrupt(this, interrupt.address)it then runsthis.clearInterrupt(interrupt). That removes the entry frompendingInterruptsbut leavesthe TIFR bit set in
cpu.data[flagRegister]. On real hardware, TOV0 is auto-cleared by hardware when the ISR vector is entered.The TIFR bit lingering may cause subtle re-entry or interfere with how subsequent OVFs are queued / dispatched.
Workaround attempts (none worked)
cpu.data[0x58] &= ~0x02via a clock event scheduled 8 cycles afterqueueInterrupt(OVF). No visible behavior change.count()to log every OVF — confirmed firings are at the right cadence.ovfInterrupt: 0x05(not0x0a= vector*2 like ATmega328P). MatchesattinyTimer1Config.ovfInterrupt: 0x04(=_VECTOR(4)TIMER1_OVF).Related
0af3bb4but Timer0 path untouched)