Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 16-bit timers #30

Merged
merged 3 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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