aboutsummaryrefslogtreecommitdiff
path: root/src/timer.ts
blob: 7203574aa44bfa545063422c188c425995205524 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
 * AVR-8 Timers
 * 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
 */

import { CPU } from './cpu';
import { avrInterrupt } from './interrupt';

const dividers = {
  0: 0,
  1: 1,
  2: 8,
  3: 64,
  4: 256,
  5: 1024,
  6: 0, // TODO: External clock source on T0 pin. Clock on falling edge.
  7: 0 // TODO: External clock source on T0 pin. Clock on rising edge.
};

type u8 = number;

interface AVRTimerConfig {
  bits: 8 | 16;
  captureInterrupt: u8;
  compAInterrupt: u8;
  compBInterrupt: u8;
  ovfInterrupt: u8;

  // Register addresses
  TIFR: u8;
  OCRA: u8;
  OCRB: u8;
  ICR: u8;
  TCNT: u8;
  TCCRA: u8;
  TCCRB: u8;
  TCCRC: u8;
  TIMSK: u8;
}

export const timer0Config: AVRTimerConfig = {
  bits: 8,
  captureInterrupt: 0, // not available
  compAInterrupt: 0x1c,
  compBInterrupt: 0x1e,
  ovfInterrupt: 0x20,
  TIFR: 0x35,
  OCRA: 0x47,
  OCRB: 0x48,
  ICR: 0, // not available
  TCNT: 0x46,
  TCCRA: 0x44,
  TCCRB: 0x45,
  TCCRC: 0, // not available
  TIMSK: 0x6e
};

export const timer1Config: AVRTimerConfig = {
  bits: 16,
  captureInterrupt: 0x14,
  compAInterrupt: 0x16,
  compBInterrupt: 0x18,
  ovfInterrupt: 0x1a,
  TIFR: 0x36,
  OCRA: 0x88,
  OCRB: 0x8a,
  ICR: 0x86,
  TCNT: 0x84,
  TCCRA: 0x80,
  TCCRB: 0x81,
  TCCRC: 0x82,
  TIMSK: 0x6f
};

export const timer2Config: AVRTimerConfig = {
  bits: 8,
  captureInterrupt: 0, // not available
  compAInterrupt: 0x0e,
  compBInterrupt: 0x10,
  ovfInterrupt: 0x12,
  TIFR: 0x37,
  OCRA: 0xb3,
  OCRB: 0xb4,
  ICR: 0, // not available
  TCNT: 0xb2,
  TCCRA: 0xb0,
  TCCRB: 0xb1,
  TCCRC: 0, // not available
  TIMSK: 0x70
};

export class AVRTimer {
  private mask = (1 << this.config.bits) - 1;
  private lastCycle = 0;

  constructor(private cpu: CPU, private config: AVRTimerConfig) {}

  get TIFR() {
    return this.cpu.data[this.config.TIFR];
  }

  set TIFR(value: u8) {
    this.cpu.data[this.config.TIFR] = value;
  }

  get TCNT() {
    return this.cpu.data[this.config.TCNT];
  }

  set TCNT(value: u8) {
    this.cpu.data[this.config.TCNT] = value;
  }

  get TCCRB() {
    return this.cpu.data[this.config.TCCRB];
  }

  get TIMSK() {
    return this.cpu.data[this.config.TIMSK];
  }

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

  tick() {
    const divider = dividers[this.CS];
    const delta = this.cpu.cycles - this.lastCycle;
    if (divider && delta >= divider) {
      const counterDelta = Math.floor(delta / divider);
      this.lastCycle += counterDelta * divider;
      const val = this.TCNT;
      const newVal = (val + counterDelta) & this.mask;
      this.TCNT = newVal;
      if (val > newVal) {
        this.TIFR |= 1; // TOV
      }
    }
    if (this.TIFR & 0x1 && this.TIMSK & 0x1 && this.cpu.interruptsEnabled) {
      avrInterrupt(this.cpu, this.config.ovfInterrupt);
      this.TIFR &= ~0x1;
    }
  }
}