fix(sim): correct ATtiny85 Timer0 PWM register addresses
AVRSimulator used wrong ATtiny85 Timer0 data-space addresses: OCR0A 0x56 (=PINB), OCR0B 0x5c (=EECR), TCCR0A 0x4f (=TCNT1). analogWrite() writes OCR0B at data 0x48, so pollPwmRegisters() read the wrong register and PWM duty was never seen — attiny85-pwm-fade showed no fade. Corrected both PWM_PINS_TINY85 and attiny85Timer0Config to TCCR0A=0x4A/OCR0A=0x49/OCR0B=0x48 (verified against the ATTinyCore analogWrite disassembly). delay()/millis (overflow-based) was unaffected. Tests updated off the old 0x5c/0x56.
This commit is contained in:
parent
2dc0e61801
commit
c0b1e577b4
|
|
@ -11,7 +11,7 @@
|
|||
* - setPinState() drives PB0-PB5 correctly
|
||||
* - No PORTC/PORTD (ATtiny85 only has PORTB)
|
||||
* - No hardware USART
|
||||
* - PWM OCR0B at address 0x5C triggers pin 1 update
|
||||
* - PWM OCR0B at address 0x48 triggers pin 1 update
|
||||
* - boardPinMapping: PB0-PB5 → 0-5, GND/VCC → -1
|
||||
*
|
||||
* END-TO-END test (requires arduino-cli + ATTinyCore):
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
* ATtiny85 PINB = 0x36 (ATmega328P PINB = 0x23)
|
||||
* ATtiny85 DDRB = 0x37 (ATmega328P DDRB = 0x24)
|
||||
* ATtiny85 PORTB = 0x38 (ATmega328P PORTB = 0x25)
|
||||
* ATtiny85 OCR0B = 0x5C (ATmega328P OCR0B = 0x48)
|
||||
* ATtiny85 OCR0B = 0x48 (I/O 0x28) [0x5C is EECR — do not use]
|
||||
*
|
||||
* ─── ATtiny85 blink HEX (hand-assembled) ─────────────────────────────────────
|
||||
* LDI r16, 0xFF ; 0F EF — r16 = 0xFF (all outputs)
|
||||
|
|
@ -88,15 +88,15 @@ const TINY85_BLINK_HEX = ':0A0000000FEF07BB02E008BBFFCFC3\n' + ':00000001FF\n';
|
|||
const TINY85_PB0_HEX = ':0A0000000FEF07BB01E008BBFFCFC4\n' + ':00000001FF\n';
|
||||
|
||||
/**
|
||||
* TINY85_PWM_HEX — writes 0x80 to OCR0B (I/O 0x3C → mem 0x5C) to test PWM.
|
||||
* LDI r16, 0xFF → OUT DDRB (0x17) → LDI r16, 0x80 → OUT OCR0B (0x3C) → RJMP .-2
|
||||
* TINY85_PWM_HEX — writes 0x80 to OCR0B (I/O 0x28 → mem 0x48) to test PWM.
|
||||
* LDI r16, 0xFF → OUT DDRB (0x17) → LDI r16, 0x80 → OUT OCR0B (0x28) → RJMP .-2
|
||||
*
|
||||
* OUT 0x3C, r16 encoding:
|
||||
* A = 0x3C = 0b111100; A[5:4]=0b11, A[3:0]=0b1100; r=16
|
||||
* word = 1011 1 11 10000 1100 = 0xBF0C → bytes 0x0C 0xBF
|
||||
* Checksum: 0xB5
|
||||
* OUT 0x28, r16 encoding:
|
||||
* A = 0x28 = 0b101000; A[5:4]=0b10, A[3:0]=0b1000; r=16
|
||||
* word = 1011 1 10 1 0000 1000 = 0xBD08 → bytes 0x08 0xBD
|
||||
* Checksum: 0xBB
|
||||
*/
|
||||
const TINY85_PWM_HEX = ':0A0000000FEF07BB00E80CBFFFCFB5\n' + ':00000001FF\n';
|
||||
const TINY85_PWM_HEX = ':0A0000000FEF07BB00E808BDFFCFBB\n' + ':00000001FF\n';
|
||||
|
||||
// ─── Lifecycle ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ describe('ATtiny85 — setPinState()', () => {
|
|||
// ─── PWM — OCR0B ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('ATtiny85 — PWM monitoring', () => {
|
||||
it('PinManager receives PWM update on pin 1 when OCR0B (0x5C) is written', () => {
|
||||
it('PinManager receives PWM update on pin 1 when OCR0B (0x48) is written', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'tiny85');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
|
@ -276,9 +276,10 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
const pwmCb = vi.fn();
|
||||
pm.onPwmChange(1, pwmCb); // PB1 = OCR0B pin
|
||||
|
||||
// Directly write a PWM value to OCR0B in CPU data memory
|
||||
// Directly write a PWM value to OCR0B in CPU data memory.
|
||||
// ATtiny85 OCR0B = I/O 0x08 → data 0x48 (NOT 0x5C, which is EECR).
|
||||
const cpu = (sim as any).cpu;
|
||||
cpu.data[0x5c] = 128; // 50% duty cycle
|
||||
cpu.data[0x48] = 128; // 50% duty cycle
|
||||
|
||||
sim.start();
|
||||
sim.stop();
|
||||
|
|
@ -286,7 +287,7 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
expect(pwmCb).toHaveBeenCalledWith(1, 128 / 255);
|
||||
});
|
||||
|
||||
it('PinManager receives PWM update on pin 0 when OCR0A (0x56) is written', () => {
|
||||
it('PinManager receives PWM update on pin 0 when OCR0A (0x49) is written', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'tiny85');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
|
@ -294,8 +295,9 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
const pwmCb = vi.fn();
|
||||
pm.onPwmChange(0, pwmCb); // PB0 = OCR0A pin
|
||||
|
||||
// ATtiny85 OCR0A = I/O 0x09 → data 0x49 (NOT 0x56, which is PINB).
|
||||
const cpu = (sim as any).cpu;
|
||||
cpu.data[0x56] = 64;
|
||||
cpu.data[0x49] = 64;
|
||||
|
||||
sim.start();
|
||||
sim.stop();
|
||||
|
|
@ -305,8 +307,8 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
|
||||
it('ATtiny85 PWM covers 4 pins (OCR0A/OCR0B/OCR1A/OCR1B)', () => {
|
||||
const TINY85_PWM_MAP = [
|
||||
{ addr: 0x56, pin: 0 }, // OCR0A → PB0
|
||||
{ addr: 0x5c, pin: 1 }, // OCR0B → PB1
|
||||
{ addr: 0x49, pin: 0 }, // OCR0A → PB0
|
||||
{ addr: 0x48, pin: 1 }, // OCR0B → PB1
|
||||
{ addr: 0x4e, pin: 1 }, // OCR1A → PB1
|
||||
{ addr: 0x4b, pin: 4 }, // OCR1B → PB4
|
||||
];
|
||||
|
|
|
|||
|
|
@ -129,11 +129,16 @@ const MEGA_PIN_TO_PORT = (() => {
|
|||
})();
|
||||
|
||||
// OCR register addresses → ATtiny85 pin mapping for PWM
|
||||
// Timer0: OC0A→PB0, OC0B→PB1 (ATtiny85 Timer0 OCR regs at 0x56, 0x5C)
|
||||
// Timer0: OC0A→PB0, OC0B→PB1. ATtiny85 OCR0A = I/O 0x09 → data 0x49,
|
||||
// OCR0B = I/O 0x08 → data 0x48 (verified vs the ATTinyCore
|
||||
// analogWrite disassembly: `out 0x29,OCR0A` / `out 0x28,OCR0B`).
|
||||
// The old 0x56/0x5C values were WRONG — they point at PINB (0x56)
|
||||
// and EECR (0x5C), so analogWrite() duty was never read and PWM
|
||||
// examples (e.g. attiny85-pwm-fade) showed no fade.
|
||||
// Timer1: OC1A→PB1, OC1B→PB4 (ATtinyTimer1 OCR regs from attinyTimer1Config)
|
||||
const PWM_PINS_TINY85 = [
|
||||
{ ocrAddr: 0x56, pin: 0, label: 'OCR0A' }, // Timer0A → PB0
|
||||
{ ocrAddr: 0x5c, pin: 1, label: 'OCR0B' }, // Timer0B → PB1
|
||||
{ ocrAddr: 0x49, pin: 0, label: 'OCR0A' }, // Timer0A → PB0
|
||||
{ ocrAddr: 0x48, pin: 1, label: 'OCR0B' }, // Timer0B → PB1
|
||||
{ ocrAddr: 0x4e, pin: 1, label: 'OCR1A' }, // Timer1A → PB1 (attinyTimer1Config.OCR1A)
|
||||
{ ocrAddr: 0x4b, pin: 4, label: 'OCR1B' }, // Timer1B → PB4 (attinyTimer1Config.OCR1B)
|
||||
];
|
||||
|
|
@ -241,12 +246,16 @@ const attiny85Timer0Config: AVRTimerConfig = {
|
|||
compCInterrupt: 0,
|
||||
ovfInterrupt: 0x05, // _VECTOR(5) TIMER0_OVF_vect
|
||||
TIFR: 0x58,
|
||||
OCRA: 0x56,
|
||||
OCRB: 0x5c,
|
||||
// ATtiny85 Timer0 data-space addresses (I/O + 0x20), verified against the
|
||||
// ATTinyCore disassembly: TCCR0A `out 0x2a`→0x4A, OCR0A `out 0x29`→0x49,
|
||||
// OCR0B `out 0x28`→0x48. The old 0x4f/0x56/0x5c were wrong (TCNT1/PINB/EECR)
|
||||
// which broke analogWrite()/PWM on the ATtiny85.
|
||||
OCRA: 0x49,
|
||||
OCRB: 0x48,
|
||||
OCRC: 0,
|
||||
ICR: 0,
|
||||
TCNT: 0x52,
|
||||
TCCRA: 0x4f,
|
||||
TCCRA: 0x4a,
|
||||
TCCRB: 0x53,
|
||||
TCCRC: 0,
|
||||
TIMSK: 0x59,
|
||||
|
|
|
|||
Loading…
Reference in New Issue