This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The ATtiny85 and the board page.

ATtiny85 chip contract (Phase 9)

The fifth board of Phase 9, and the first that is not a board: a bare 8-pin DIP chip that pushes into the breadboard across the center channel, like the 555 and the 74HC parts. One kernel component (attiny85 in crates/parts) wraps an ATtiny85 SoC model (crates/attiny85) built on the same AVR core as the Uno (crates/avr-core), with the core's Model cut down to the tinyAVR instruction set. Firmware is an AVR ELF (or raw flash image) that talks to the peripherals below at their real ATtiny85 addresses, so a program built for Mokxi runs on a real ATtiny85.

The catalog type is attiny85. Change this file first; the SoC, the part, the firmware runtime and the visual all follow it.

1. Chip pins (catalog order, DIP-8)

Package pin order, pin 1 first, counter-clockwise, which is how crates/parts/src/chip.rs numbers a DIP and how web/src/parts/dip.ts draws one:

package pin catalog name what it is
1 RESET PB5 / ADC0 / PCINT5, and the reset pin
2 PB3 ADC3 / /OC1B / PCINT3, the runtime's serial transmit
3 PB4 ADC2 / OC1B / PCINT4, the runtime's serial receive
4 GND ground
5 PB0 OC0A / /OC1A / DI / SDA / AREF / PCINT0
6 PB1 OC0B / OC1A / DO / PCINT1
7 PB2 INT0 / ADC1 / USCK / SCL / PCINT2
8 VCC supply

Electrical:

  • Power is a pin. VCC and GND are inout and held high-Z: the chip is a load on its supply, not a source. The chip is powered when both are driven and V(VCC) - V(GND) >= 1.8 V, the datasheet's minimum for an ATtiny85V. Unpowered, every pin is high-Z, nothing runs and the probe is 0: the same rule the 74HC parts and the 555 follow.
  • Levels come from the chip's own supply. A driven pin sources the VCC net's voltage or the GND net's through R_STRONG; an input reads high at half the supply. A chip on a 3.3 V rail behaves like a chip on a 3.3 V rail, and the ADC's VCC reference follows it.
  • RESET (PB5) carries a 10 k pull-up to VCC; below half the supply it holds the core in reset, and releasing it restarts at the reset vector with EXTRF in MCUSR. PB5 is a GPIO only with the RSTDISBL fuse programmed, which makes the part unprogrammable over ISP; Mokxi models it as the reset pin, so DDRB and PORTB bit 5 are stored and read back and the pad never moves.
  • PB0 to PB4 are inout GPIO. Output (DDR bit set): push-pull through R_STRONG. Input (DDR clear): high-Z, plus a 35 k pull-up to VCC when the PORTB bit is set and PUD is clear. Unknown and Floating read 0.
  • ADC: ADC1 (PB2), ADC2 (PB4) and ADC3 (PB3) read the net voltage at the pin against the selected reference. ADC0 is on the reset pin and reads whatever the reset net is at. PB0 doubles as AREF.

2. Memory map (real ATtiny85)

space address size
flash (program) word address 0x0000 8 KB (0x1000 words); SPM writes go straight to flash, 32 words a page
registers data 0x0000..0x001F R0..R31 (inside the core)
I/O data 0x0020..0x005F IN/OUT addresses 0x00..0x3F; there is no extended I/O window
SRAM data 0x0060..0x025F 512 bytes, RAMEND 0x025F
EEPROM separate, 512 bytes via EECR/EEDR/EEAR, read and write with no timing (writes complete immediately, EEPE reads 0)

loadProgram takes an ELF32 AVR executable (e_machine 83) or a raw binary flash image when the bytes are not an ELF. Reset: pc = 0, SP = 0x025F, SREG = 0, I/O at datasheet reset values, SRAM and registers zero. A load at run time resets the chip.

The instruction set is smaller than the Uno's. The tinyAVR core has no hardware multiplier and no 22-bit jump, so MUL, MULS, MULSU, FMUL, FMULS, FMULSU, JMP and CALL are not instructions on this part; executing one is an illegal opcode and crashes the chip with the line in section 5. MOVW, the three LPM forms, SPM, LDS/STS, ADIW/SBIW and everything else the ATmega328P has are here. avr_core::Model::ATTINY85 is where that is written down.

3. Peripherals (real addresses, data-space numbering, real bit positions)

Everything not listed reads its reset value and ignores writes, with a diagnostics counter.

3.1 Port B

data address register
0x36 0x37 0x38 PINB DDRB PORTB (bits 0..5 = PB0..PB5; 6 and 7 do not exist on this package)
0x35 PCMSK (PCINT5:0 bits 5..0)

Writing 1 to a PINB bit toggles the PORTB bit (datasheet 10.2.2). SBI/CBI on these addresses are read-modify-write at the register, as on the chip.

When more than one peripheral wants a pin, the order is the USI's DO, then timer 1, then timer 0, then the PORTB bit. It matters on PB1, which is DO, OC1A and OC0B at once, and on PB0, which is OC0A and /OC1A.

3.2 Timer/Counter0 (8-bit, the millis() timer)

data address register bits
0x4A TCCR0A COM0A1:0 bits 7..6, COM0B1:0 bits 5..4, WGM01:0 bits 1..0
0x53 TCCR0B FOC0A 7, FOC0B 6, WGM02 3, CS02:0 2..0
0x52 TCNT0
0x49 OCR0A
0x48 OCR0B

Modes (WGM02:0): 0 normal, 1 phase-correct PWM (top 0xFF), 2 CTC (top OCR0A), 3 fast PWM (top 0xFF), 5 phase-correct (top OCR0A), 7 fast PWM (top OCR0A), the same table as the ATmega328P's timer 0. Prescaler CS02:0: 0 stopped, 1 = 1, 2 = 8, 3 = 64, 4 = 256, 5 = 1024, 6/7 external on T0 (not modeled, count 0). Compare outputs: OC0A on PB0, OC0B on PB1; COM modes per the datasheet tables. The output override applies only while the DDR bit is set. Vectors: TIMER0_COMPA 10, TIMER0_COMPB 11, TIMER0_OVF 5.

3.3 Timer/Counter1 (the high-speed timer, 8-bit)

data address register bits
0x50 TCCR1 CTC1 7, PWM1A 6, COM1A1:0 bits 5..4, CS13:0 bits 3..0
0x4C GTCCR TSM 7, PWM1B 6, COM1B1:0 bits 5..4, FOC1B 3, FOC1A 2, PSR1 1, PSR0 0
0x4F TCNT1
0x4E OCR1A
0x4B OCR1B
0x4D OCR1C the counter's TOP
0x47 PLLCSR LSM 7, PCKE 2, PLLE 1, PLOCK 0

Nothing like an ATmega's timer 1: eight bits wide, counting up only, with its TOP in a register of its own. With CTC1, PWM1A or PWM1B set the counter runs 0..=OCR1C and restarts; otherwise it runs the full 0..=0xFF. TOV1 is set on the wrap. CS13:0 divides PCK by 2^(n-1): 0 stopped, 1 = 1, 2 = 2, up to 15 = 16384.

Compare outputs: OC1A on PB1 with its complement /OC1A on PB0, OC1B on PB4 with /OC1B on PB3. In PWM mode COM1x1:0 = 01 drives the pin and its complement, 10 drives the pin non-inverting only, 11 drives it inverting only; in non-PWM mode 01 toggles, 10 clears and 11 sets on a compare match, and the complementary pin is not used. Vectors: TIMER1_COMPA 3, TIMER1_COMPB 9, TIMER1_OVF 4.

The PLL is not modeled. On silicon PCKE swaps this timer's clock for a 64 MHz PLL output. Here PCK is the system clock: PLLCSR is stored and reads back with PLOCK set once PLLE has been written, so firmware that spins waiting for the lock gets going, and the timer keeps counting at 8 MHz. A sketch that sets PCKE gets a PWM frequency eight times lower than the real part would. The dead-time generator (DTPS 0x43, DT1B 0x44, DT1A 0x45) is stored and read back.

3.4 The shared timer interrupt registers

data address register bits
0x59 TIMSK OCIE1A 6, OCIE1B 5, OCIE0A 4, OCIE0B 3, TOIE1 2, TOIE0 1
0x58 TIFR OCF1A 6, OCF1B 5, OCF0A 4, OCF0B 3, TOV1 2, TOV0 1; write 1 clears

One mask register and one flag register for both timers, which is the thing an ATmega habit gets wrong here.

3.5 ADC

data address register bits
0x24 0x25 ADCL ADCH result, right-adjusted unless ADLAR
0x26 ADCSRA ADEN 7, ADSC 6, ADATE 5, ADIF 4, ADIE 3, ADPS2:0 2..0
0x23 ADCSRB stored
0x27 ADMUX REFS1:0 7..6, ADLAR 5, REFS2 4, MUX3:0 3..0
0x34 DIDR0 stored
0x28 ACSR stored; the analog comparator is not modeled

MUX3:0: 0 = ADC0 (PB5), 1 = ADC1 (PB2), 2 = ADC2 (PB4), 3 = ADC3 (PB3), 12 = the 1.1 V bandgap, 13 = 0 V, 15 = the temperature sensor (a fixed 300 mV, so about 279 against the bandgap). 4..11 are the differential pairs with their gain stages, which are not modeled and read 0.

The reference is REFS2:0, with REFS2 in bit 4 rather than beside the other two: 00x = VCC, 01x = the AREF pin (PB0), 100 = the 1.1 V bandgap, 110 and 111 = the 2.56 V reference, which an ATmega328P does not have. A floating AREF falls back to VCC.

A conversion takes 13 ADC clocks (25 for the first after ADEN) at 8 MHz / prescaler; ADSC reads 1 until done, then ADIF sets and the result is round(1023 * Vin / Vref) clamped, from the pin's net voltage at the time the conversion started. Vector: ADC 8.

3.6 The USI

data address register bits
0x30 USIBR the shift register as it was at the last counter overflow
0x2F USIDR the shift register; bit 7 is what DO presents
0x2E USISR USISIF 7, USIOIF 6, USIPF 5, USIDC 4, USICNT3:0 bits 3..0; write 1 clears a flag
0x2D USICR USISIE 7, USIOIE 6, USIWM1:0 bits 5..4, USICS1:0 bits 3..2, USICLK 1, USITC 0

A shift register, a four-bit counter and a clock multiplexer, on DI/SDA (PB0), DO (PB1) and USCK/SCL (PB2). The clock table (datasheet table 15-1) is implemented in full: software strobe on USICLK, a timer 0 compare match, and an external edge on USCK of either polarity, with the counter taking both edges or the USITC strobe as the table says. USITC toggles the USCK port bit, so the usual three-wire loop (write USICR twice a bit with USITC set) clocks the register on one toggle and the counter on both. The counter's overflow from 15 to 0 latches USIDR into USIBR and sets USIOIF.

In three-wire mode the USI drives DO. In two-wire mode it counts, shifts and reports the start and stop conditions on the bus, but it does not drive SDA: an open-drain output is not in the SoC's vocabulary of pad states. Vectors: USI_START 13, USI_OVF 14.

3.7 External and pin-change interrupts

data address register
0x5B GIMSK (INT0 6, PCIE 5)
0x5A GIFR (INTF0 6, PCIF 5; write 1 clears)
0x35 PCMSK (one bit per pin)

INT0 is PB2 and it is the only external interrupt this part has; its sense bits ISC01:00 live in MCUCR bits 1..0 (0 low level, 1 any change, 2 falling, 3 rising) rather than in an EICRA of its own. The pin-change interrupt is one vector for all six pins. Vectors: INT0 1, PCINT0 2.

3.8 Sleep, resets and the odds and ends

data address register
0x55 MCUCR (PUD bit 6, SE bit 5, SM1:0 bits 4..3, ISC01:00 bits 1..0)
0x54 MCUSR (reset flags; PORF set after power-on, EXTRF after a RESET pin reset)
0x41 WDTCR (stored; the watchdog never fires)
0x46 CLKPR (stored; the clock stays at 8 MHz)
0x51 OSCCAL (stored, reset value 0x80; the model runs at exactly 8 MHz whatever is written)
0x57 SPMCSR (SPMEN 0, PGERS 1, PGWRT 2, RFLB 3, CTPB 4)
0x40 PRR (stored; switching a peripheral's clock off changes nothing here)
0x42 DWDR (stored; debugWIRE is not modeled)
0x31 0x32 0x33 GPIOR0 GPIOR1 GPIOR2
0x5F 0x5E 0x5D SREG SPH SPL (inside the core, never reach the bus)

Sleep enable is in MCUCR, not in an SMCR: SLEEP with SE set parks the core until any enabled interrupt is pending, and every sleep mode behaves as idle. That is how an idle sketch becomes cheap; the runtime's delay() sleeps.

3.9 EEPROM

data address register
0x3C EECR (EEPM1:0 bits 5..4, EERIE 3, EEMPE 2, EEPE 1, EERE 0)
0x3D EEDR
0x3E 0x3F EEARL EEARH (9 bits: 512 bytes)

EEPM1:0 picks erase-and-write, erase only or write only, and all three take effect at once because there is no write time to model. The EE_RDY interrupt (vector 6) has no flag: it asserts whenever EERIE is set, exactly as it does on silicon, so a handler that does not clear EERIE runs for ever.

3.10 Interrupts

Fifteen vectors, one word apart: the slot holds an RJMP, not a JMP, because the whole of flash is within RJMP range. Getting that wrong shifts every vector, so it is the first thing to check when a handler does not run.

vector source vector source
0 RESET 8 ADC
1 INT0 9 TIMER1_COMPB
2 PCINT0 10 TIMER0_COMPA
3 TIMER1_COMPA 11 TIMER0_COMPB
4 TIMER1_OVF 12 WDT
5 TIMER0_OVF 13 USI_START
6 EE_RDY 14 USI_OVF
7 ANA_COMP

The core's set_interrupt(vector, pending) is a level per vector: the SoC asserts a vector while its flag bit and its enable bit are both set, and the core clears the flag on entry for the timer, external, pin-change, ADC and USI vectors exactly as the hardware does.

4. Host channel

host = "utf-8 bytes on a 9600 baud software serial port on PB3 and PB4".

An ATtiny85 has no USART, so there is nothing on the chip to carry a serial monitor and inventing one would make firmware that only runs in a simulator. Instead the part watches the two pins the way a USB-serial adapter clipped to them would:

  • PB3, transmit. The part decodes 8N1 frames off the pin, sampling in the middle of each bit, and drops a frame whose stop bit is not high. So a PB3 being used as an ordinary pin produces nothing rather than rubbish.
  • PB4, receive. A byte from the host is driven onto the pin as a start bit, eight data bits and a stop bit at the same rate. Between bytes the part leaves PB4 alone (high-Z) rather than holding it at the UART's idle high, so PB4 is a free pin unless somebody types into the monitor. That is the one deliberate difference from a real adapter, and it is what keeps a circuit that uses PB4 for something else working.

The rate is the part's baud property, default 9600, and it has to match what the sketch calls Serial.begin with, exactly like plugging an adapter in at the wrong rate.

Two things follow from the port being bit-banged rather than a peripheral, and both are true of the real chip:

  • It is half duplex in practice. Echoing a byte holds interrupts off for a whole byte time, so a burst typed into the monitor loses the characters that land inside one. A byte at a time gets through; a pasted line does not.
  • The two pins have to be on nets. The adapter is clipped to PB3 and PB4; a pin wired to nothing carries nothing. A chip pushed into a breadboard has a net on every pin, so this only comes up in a circuit built without one.

The transmit side is the exception: the decoder watches the chip's own pad rather than the net, so PB3 with nothing on it still reaches the monitor: the same convenience the Uno gets from its USART bytes not needing pin 1 wired.

5. Time model and the part

8 MHz from the internal RC oscillator, which is what an ATtiny85 runs on unless a crystal is fitted. A factory-fresh part has the CKDIV8 fuse programmed and runs at 1 MHz; Mokxi models the part with CKDIV8 unprogrammed, which is the fuse setting every ATtiny85 project sets first. A cycle is exactly 125 ns. Slices of 10 us = 80 cycles. Timers are advanced analytically with exact event times, so a PWM edge or a timer interrupt lands on the exact cycle, and pin changes carry their in-slice cycle offset into drive_after, which is what makes the bit-banged serial port believable.

Probe: bit 0 = running (1 while executing, 0 in reset, unpowered or halted), bit 1 = sleeping, bit 2 = PB0 high (the pin every example puts its LED on), bit 3 = serial transmit activity in the last 20 ms, bit 4 = serial receive activity in the last 20 ms. So probe is a small bitmask, 0 with no program and 0 unpowered. Poke: 2 = hold RESET, 3 = release.

Crash handling as in docs/uno.md section 5: an AVR cannot trap, so "crashed" means the core executed an illegal opcode (which on this part includes the whole multiply family, JMP and CALL) or the PC left the loaded program. The part halts and prints firmware crashed at pc=0x0123 (word address), reason once on the host channel.

6. Firmware runtime (firmware/attiny/)

A freestanding C and C++ runtime for the ATtiny85 built with clang (--target=avr -mmcu=attiny85 -Os) and lld, no avr-libc, no ATTinyCore, no GPL. A sibling of firmware/uno/, not a shared library with it: the vector table is a different shape, the multiplies have to be written out because the part has no MUL, and Serial is a different thing entirely.

  • crt0.S: the 15 one-word RJMP vectors, _start (copy .data from flash with LPM, clear .bss, set SP to RAMEND, run the static constructors, call main). There is no CALL on this part, so every call is RCALL.
  • support.S: the compiler-support routines clang calls. The divides are the Uno's; __mulqi3, __mulhi3 and __mulsi3 are shift-and-add, because there is no multiplier.
  • include/attiny85.h: the registers above as macros with the real names.
  • The arduino.h surface: pinMode, digitalWrite, digitalRead, shiftOut, analogRead, analogReference, analogWrite, millis, micros, delay, delayMicroseconds, attachInterrupt, attachPinChangeInterrupt, interrupts, noInterrupts, Serial, map, random, and the Print overloads. Pins are 0..5 and they are PB0..PB5: no board numbering to translate.
  • millis()/micros() from the timer 0 overflow at 2048 us with the fractional correction; delay() sleeps in idle mode between ticks and busy-waits the last couple of milliseconds; delayMicroseconds is a cycle-counted loop.
  • analogWrite on pins 0, 1 and 4, all at 8e6 / (64 x 256) = 488.28 Hz: one PWM frequency, not the Uno's two, because timer 1's TOP is a register and can be set to 256 states.
  • Serial is the software serial port of section 4: 8N1 on PB3 and PB4, 2400 to 115200 baud, transmit bit-banged with interrupts off and receive sampled from the pin-change interrupt into a sixteen-byte ring. A byte costs a real 1.04 ms at 9600 baud, which is the point.

Four examples under firmware/attiny/examples/<name>/sketch.ino, built to web/public/firmware/attiny/<name>.elf with an index.json beside them: blink (an LED on PB0), button (a pushbutton on PB2 through INT0), knob (a potentiometer on PB2 dimming an LED on PB0), hello (the serial port printing and echoing). The UI picks the firmware list by the part's catalog program string: "elf32 avr attiny85" reads /firmware/attiny/index.json.

7. What this model is sure of, and what it is not

Three groups, the same sorting docs/esp32c6.md section 7 uses. It exists because a chip model that quietly guesses an address is worse than one that says it did not know.

Taken from the data sheet. Every address, bit position and reset value in section 3 is from the ATtiny25/45/85 Data Sheet (DS40002519), in data-space numbering, and none of it is a guess: this part's register map is public and stable. The DIP-8 pinout, the four ADC channels and their pin mapping, and the two timers' prescaler tables are the data sheet's. crates/attiny85/tests/runtime.rs runs the shipped blink and knob ELFs through port B, timer 0, timer 1 and the ADC.

Modeled, but not held to the data sheet's numbers. Behaviors rather than addresses, and each is an approximation:

  • The clock is 8 MHz exactly, with CKDIV8 unprogrammed. A factory-fresh ATtiny85 runs at 1 MHz because that fuse is set; this model assumes the setting every project changes first. The internal RC oscillator is also the one thing on this chip nobody should trust: the data sheet calibrates it to +/-10% at the factory and it moves several percent with supply and temperature. Here it is exact, with no jitter and no drift, which is precisely the number the bit-banged serial port in section 4 depends on. A real ATtiny85 needs its OSCCAL trimmed before 115200 baud works at all.
  • The PLL is a stub. PLLCSR is stored and reads back with PLOCK set once PLLE is written, but PCK runs at the system clock, so timer 1's 64 MHz fast mode gives 8 MHz here. A sketch using timer 1 for high-frequency PWM is eight times slow.
  • The ADC has no error in it. round(1023 x Vin / Vref) sampled at the instant the conversion started: no sample-and-hold, no input impedance, no nonlinearity, no noise and no offset.
  • RESET carries a 10 kohm pull-up that is not inside a real chip. A real ATtiny85 needs one fitted, and a floating reset pin is the commonest wiring slip on this part. The model provides it so a chip on a breadboard runs, and says so here.
  • The pads are three states and a threshold. Push-pull at the supply, high-Z, or a 35 kohm pull-up (the data sheet gives 20 kohm to 50 kohm), read back against half the supply, with no hysteresis, no output resistance, no rise time and no current limit. The chip runs on any supply span of 1.8 V or more, which is the ATtiny85V's minimum, and the span is both the logic levels and the ADC's VCC reference.
  • Startup is a microsecond, where a real part has a fuse-selected start-up time of up to 64 ms plus the brown-out delay.
  • The serial port is a part, not a peripheral. Section 4's 8N1 decode lives in the chip part in crates/parts, standing in for the USB-serial adapter a real ATtiny85 needs clipped to PB3 and PB4.

Deliberately absent. Left out rather than invented; their registers read their reset value and count as strays: the PLL's 64 MHz clock (see above), the analog comparator, the watchdog, the clock prescaler, the differential ADC channels and their gain stages, the brown-out detector, debugWIRE, high-voltage serial programming, the fuses, and the USI's two-wire output driver.