Skip to content

Commit

Permalink
Merge pull request #30 from wokwi/16bit-timer-fix
Browse files Browse the repository at this point in the history
Implement 16-bit timers
  • Loading branch information
urish committed Apr 16, 2020
2 parents f34f825 + daf3ac7 commit 9f06d4e
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 27 deletions.
10 changes: 7 additions & 3 deletions demo/src/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AVRTimer,
CPU,
timer0Config,
timer1Config,
AVRIOPort,
AVRUSART,
portBConfig,
Expand All @@ -19,7 +20,8 @@ const FLASH = 0x8000;
export class AVRRunner {
readonly program = new Uint16Array(FLASH);
readonly cpu: CPU;
readonly timer: AVRTimer;
readonly timer0: AVRTimer;
readonly timer1: AVRTimer;
readonly portB: AVRIOPort;
readonly portC: AVRIOPort;
readonly portD: AVRIOPort;
Expand All @@ -31,7 +33,8 @@ export class AVRRunner {
constructor(hex: string) {
loadHex(hex, new Uint8Array(this.program.buffer));
this.cpu = new CPU(this.program);
this.timer = new AVRTimer(this.cpu, timer0Config);
this.timer0 = new AVRTimer(this.cpu, timer0Config);
this.timer1 = new AVRTimer(this.cpu, timer1Config);
this.portB = new AVRIOPort(this.cpu, portBConfig);
this.portC = new AVRIOPort(this.cpu, portCConfig);
this.portD = new AVRIOPort(this.cpu, portDConfig);
Expand All @@ -44,7 +47,8 @@ export class AVRRunner {
const cyclesToRun = this.cpu.cycles + this.workUnitCycles;
while (this.cpu.cycles < cyclesToRun) {
avrInstruction(this.cpu);
this.timer.tick();
this.timer0.tick();
this.timer1.tick();
this.usart.tick();
}

Expand Down
62 changes: 61 additions & 1 deletion src/peripherals/timer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CPU } from '../cpu/cpu';
import { AVRTimer, timer0Config, timer2Config } from './timer';
import { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer';

describe('timer', () => {
let cpu: CPU;
Expand Down Expand Up @@ -201,4 +201,64 @@ describe('timer', () => {
timer.tick();
expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2
});

describe('16 bit timers', () => {
it('should increment 16-bit TCNT by 1', () => {
const timer = new AVRTimer(cpu, timer1Config);
cpu.writeData(0x85, 0x22); // TCNT1 <- 0x2233
cpu.writeData(0x84, 0x33); // ...
expect(timer.TCNT).toEqual(0x2233);
cpu.writeData(0x80, 0x0); // WGM1 <- 0 (Normal)
cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1
cpu.cycles = 1;
timer.tick();
expect(cpu.dataView.getUint16(0x84, true)).toEqual(0x2234); // TCNT1 should increment
});

it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => {
const timer = new AVRTimer(cpu, timer1Config);
cpu.writeData(0x84, 0xee); // TCNT1 <- 0x10ee
cpu.writeData(0x85, 0x10); // ...
cpu.writeData(0x88, 0xef); // OCR1A <- 0x10ef
cpu.writeData(0x89, 0x10); // ...
cpu.writeData(0x80, 0x0); // TCCR1A <- 0 (Normal Mode)
cpu.writeData(0x81, 0x1); // TCCR1B <- CS10
cpu.cycles = 1;
timer.tick();
expect(cpu.data[0x36]).toEqual(2); // TIFR1 should have OCF1A bit on
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
});

it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => {
const timer = new AVRTimer(cpu, timer1Config);
cpu.writeData(0x85, 0x3); // TCNT1 <- 0x3ff
cpu.writeData(0x84, 0xff); // ...
cpu.writeData(0x80, 0x3); // TCCR1A <- WGM10 | WGM11 (Fast PWM, 10-bit)
cpu.writeData(0x81, 0x9); // TCCR1B <- WGM12 | CS10
console.log(timer.CS);
cpu.data[0x6f] = 0x1; // TIMSK1: TOIE1
cpu.data[95] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0
expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear
expect(cpu.pc).toEqual(0x1a);
expect(cpu.cycles).toEqual(3);
});

it('should reset the timer once it reaches ICR value in mode 12', () => {
const timer = new AVRTimer(cpu, timer1Config);
cpu.writeData(0x85, 0x50); // TCNT1 <- 0x500f
cpu.writeData(0x84, 0x0f); // ...
cpu.writeData(0x87, 0x50); // ICR1 <- 0x5010
cpu.writeData(0x86, 0x10); // ...
cpu.writeData(0x81, 0x19); // TCCR1B <- WGM13 | WGM12 | CS10
cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1
timer.tick();
expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0
expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear
expect(cpu.cycles).toEqual(2);
});
});
});
153 changes: 130 additions & 23 deletions src/peripherals/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Part of AVR8js
* Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
*
* Copyright (C) 2019, Uri Shaked
* Copyright (C) 2019, 2020, Uri Shaked
*/

import { CPU } from '../cpu/cpu';
Expand All @@ -20,11 +20,6 @@ const timer01Dividers = {
7: 0 // TODO: External clock source on T0 pin. Clock on rising edge.
};

const WGM_NORMAL = 0;
const WGM_PWM_PHASE_CORRECT = 1;
const WGM_CTC = 2;
const WGM_FASTPWM = 3;

const TOV = 1;
const OCFA = 2;
const OCFB = 4;
Expand All @@ -34,6 +29,7 @@ const OCIEA = 2;
const OCIEB = 4;

type u8 = number;
type u16 = number;

interface TimerDividers {
0: number;
Expand Down Expand Up @@ -130,24 +126,95 @@ export const timer2Config: AVRTimerConfig = {
}
};

/* All the following types and constants are related to WGM (Waveform Generation Mode) bits: */
enum TimerMode {
Normal,
PWMPhaseCorrect,
CTC,
FastPWM,
PWMPhaseFrequencyCorrect,
Reserved
}

enum TOVUpdateMode {
Max,
Top,
Bottom
}

enum OCRUpdateMode {
Immediate,
Top,
Bottom
}

const TopOCRA = 1;
const TopICR = 2;
type TimerTopValue = 0xff | 0x1ff | 0x3ff | 0xffff | typeof TopOCRA | typeof TopICR;

type WGMConfig = [TimerMode, TimerTopValue, OCRUpdateMode, TOVUpdateMode];

const wgmModes8Bit: WGMConfig[] = [
/*0*/ [TimerMode.Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*1*/ [TimerMode.PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*2*/ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*3*/ [TimerMode.FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max],
/*4*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*5*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*6*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*7*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top]
];

// Table 16-4 in the datasheet
const wgmModes16Bit: WGMConfig[] = [
/*0 */ [TimerMode.Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*1 */ [TimerMode.PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*2 */ [TimerMode.PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*3 */ [TimerMode.PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*4 */ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*5 */ [TimerMode.FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
/*6 */ [TimerMode.FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
/*7 */ [TimerMode.FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
/*8 */ [TimerMode.PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom],
/*9 */ [TimerMode.PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom],
/*10*/ [TimerMode.PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*11*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom],
/*12*/ [TimerMode.CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*13*/ [TimerMode.Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max],
/*14*/ [TimerMode.FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top],
/*15*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top]
];

export class AVRTimer {
private mask = (1 << this.config.bits) - 1;
private lastCycle = 0;
private ocrA: u8 = 0;
private ocrB: u8 = 0;
private ocrA: u16 = 0;
private ocrB: u16 = 0;
private timerMode: TimerMode;
private topValue: TimerTopValue;

constructor(private cpu: CPU, private config: AVRTimerConfig) {
cpu.writeHooks[config.TCNT] = (value: u8) => {
this.updateWGMConfig();
this.registerHook(config.TCNT, (value: u16) => {
this.TCNT = value;
this.timerUpdated(value);
return true;
};
cpu.writeHooks[config.OCRA] = (value: u8) => {
});
this.registerHook(config.OCRA, (value: u16) => {
// TODO implement buffering when timer running in PWM mode
this.ocrA = value;
};
cpu.writeHooks[config.OCRB] = (value: u8) => {
});
this.registerHook(config.OCRB, (value: u16) => {
this.ocrB = value;
});
cpu.writeHooks[config.TCCRA] = (value) => {
this.cpu.data[config.TCCRA] = value;
this.updateWGMConfig();
return true;
};
cpu.writeHooks[config.TCCRB] = (value) => {
this.cpu.data[config.TCCRB] = value;
this.updateWGMConfig();
return true;
};
}

Expand All @@ -166,11 +233,16 @@ export class AVRTimer {
}

get TCNT() {
return this.cpu.data[this.config.TCNT];
return this.config.bits === 16
? this.cpu.dataView.getUint16(this.config.TCNT, true)
: this.cpu.data[this.config.TCNT];
}

set TCNT(value: u8) {
this.cpu.data[this.config.TCNT] = value;
set TCNT(value: u16) {
this.cpu.data[this.config.TCNT] = value & 0xff;
if (this.config.bits === 16) {
this.cpu.data[this.config.TCNT + 1] = (value >> 8) & 0xff;
}
}

get TCCRA() {
Expand All @@ -185,12 +257,45 @@ export class AVRTimer {
return this.cpu.data[this.config.TIMSK];
}

get ICR() {
// Only available for 16-bit timers
return (this.cpu.data[this.config.ICR + 1] << 8) | this.cpu.data[this.config.ICR];
}

get CS() {
return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
}

get WGM() {
return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3);
const mask = this.config.bits === 16 ? 0x18 : 0x8;
return ((this.TCCRB & mask) >> 1) | (this.TCCRA & 0x3);
}

get TOP() {
switch (this.topValue) {
case TopOCRA:
return this.ocrA;
case TopICR:
return this.ICR;
default:
return this.topValue;
}
}

private registerHook(address: number, hook: (value: u16) => void) {
if (this.config.bits === 16) {
this.cpu.writeHooks[address] = (value: u8) => hook((this.cpu.data[address + 1] << 8) | value);
this.cpu.writeHooks[address + 1] = (value: u8) => hook((value << 8) | this.cpu.data[address]);
} else {
this.cpu.writeHooks[address] = hook;
}
}

private updateWGMConfig() {
const wgmModes = this.config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
const [timerMode, topValue] = wgmModes[this.WGM];
this.timerMode = timerMode;
this.topValue = topValue;
}

tick() {
Expand All @@ -200,13 +305,15 @@ export class AVRTimer {
const counterDelta = Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.TCNT;
const newVal = (val + counterDelta) & this.mask;
const newVal = (val + counterDelta) % (this.TOP + 1);
this.TCNT = newVal;
this.timerUpdated(newVal);
const { timerMode } = this;
if (
(this.WGM === WGM_NORMAL ||
this.WGM === WGM_PWM_PHASE_CORRECT ||
this.WGM === WGM_FASTPWM) &&
(timerMode === TimerMode.Normal ||
timerMode === TimerMode.PWMPhaseCorrect ||
timerMode === TimerMode.PWMPhaseFrequencyCorrect ||
timerMode === TimerMode.FastPWM) &&
val > newVal
) {
this.TIFR |= TOV;
Expand All @@ -231,7 +338,7 @@ export class AVRTimer {
private timerUpdated(value: u8) {
if (this.ocrA && value === this.ocrA) {
this.TIFR |= OCFA;
if (this.WGM === WGM_CTC) {
if (this.timerMode === TimerMode.CTC) {
// Clear Timer on Compare Match (CTC) Mode
this.TCNT = 0;
this.TIFR |= TOV;
Expand Down

0 comments on commit 9f06d4e

Please sign in to comment.