Skip to content

ATtiny85 Timer0 OVF fires correctly but ATTinyCore delay() never advances #178

Description

@davidmonterocrespo24

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 = 0b10TOIE0 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)

  1. Manually cpu.data[0x58] &= ~0x02 via a clock event scheduled 8 cycles after queueInterrupt(OVF). No visible behavior change.
  2. Patched count() to log every OVF — confirmed firings are at the right cadence.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions