Skip to content

Commit 43fe0a5

Browse files
committed
feat(clock): Clock Prescale (CLKPR) support #68
close #68
1 parent 4196fbb commit 43fe0a5

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ export {
4141
} from './peripherals/eeprom';
4242
export * from './peripherals/twi';
4343
export { spiConfig, SPIConfig, SPITransferCallback, AVRSPI } from './peripherals/spi';
44+
export { AVRClock, AVRClockConfig, clockConfig } from './peripherals/clock';

src/peripherals/clock.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { CPU } from '../cpu/cpu';
2+
import { AVRClock, clockConfig } from './clock';
3+
4+
// Clock Registers
5+
const CLKPC = 0x61;
6+
7+
// Register bit names
8+
const CLKPCE = 128;
9+
10+
describe('Clock', () => {
11+
it('should set the prescaler when double-writing CLKPC', () => {
12+
const cpu = new CPU(new Uint16Array(0x1000));
13+
const clock = new AVRClock(cpu, 16e6, clockConfig);
14+
cpu.writeData(CLKPC, CLKPCE);
15+
cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
16+
expect(clock.frequency).toEqual(2e6); // 2MHz
17+
expect(cpu.readData(CLKPC)).toEqual(3);
18+
});
19+
20+
it('should not update the prescaler if CLKPCE was not set CLKPC', () => {
21+
const cpu = new CPU(new Uint16Array(0x1000));
22+
const clock = new AVRClock(cpu, 16e6, clockConfig);
23+
cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
24+
expect(clock.frequency).toEqual(16e6); // still 16MHz
25+
expect(cpu.readData(CLKPC)).toEqual(0);
26+
});
27+
28+
it('should not update the prescaler if more than 4 cycles passed since setting CLKPCE', () => {
29+
const cpu = new CPU(new Uint16Array(0x1000));
30+
const clock = new AVRClock(cpu, 16e6, clockConfig);
31+
cpu.writeData(CLKPC, CLKPCE);
32+
cpu.cycles += 6;
33+
cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
34+
expect(clock.frequency).toEqual(16e6); // still 16MHz
35+
expect(cpu.readData(CLKPC)).toEqual(0);
36+
});
37+
38+
describe('prescaler property', () => {
39+
it('should return the current prescaler value', () => {
40+
const cpu = new CPU(new Uint16Array(0x1000));
41+
const clock = new AVRClock(cpu, 16e6, clockConfig);
42+
cpu.writeData(CLKPC, CLKPCE);
43+
cpu.writeData(CLKPC, 5); // Divide by 32 (2^5)
44+
cpu.cycles = 16e6;
45+
expect(clock.prescaler).toEqual(32);
46+
});
47+
});
48+
49+
describe('time properties', () => {
50+
it('should return current number of microseconds, derived from base freq + prescaler', () => {
51+
const cpu = new CPU(new Uint16Array(0x1000));
52+
const clock = new AVRClock(cpu, 16e6, clockConfig);
53+
cpu.writeData(CLKPC, CLKPCE);
54+
cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
55+
cpu.cycles = 16e6;
56+
expect(clock.timeMillis).toEqual(4000); // 4 seconds
57+
});
58+
59+
it('should return current number of milliseconds, derived from base freq + prescaler', () => {
60+
const cpu = new CPU(new Uint16Array(0x1000));
61+
const clock = new AVRClock(cpu, 16e6, clockConfig);
62+
cpu.writeData(CLKPC, CLKPCE);
63+
cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
64+
cpu.cycles = 16e6;
65+
expect(clock.timeMicros).toEqual(4e6); // 4 seconds
66+
});
67+
68+
it('should return current number of nanoseconds, derived from base freq + prescaler', () => {
69+
const cpu = new CPU(new Uint16Array(0x1000));
70+
const clock = new AVRClock(cpu, 16e6, clockConfig);
71+
cpu.writeData(CLKPC, CLKPCE);
72+
cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
73+
cpu.cycles = 16e6;
74+
expect(clock.timeNanos).toEqual(4e9); // 4 seconds
75+
});
76+
77+
it('should correctly calculate time when changing the prescale value at runtime', () => {
78+
const cpu = new CPU(new Uint16Array(0x1000));
79+
const clock = new AVRClock(cpu, 16e6, clockConfig);
80+
cpu.cycles = 16e6; // run 1 second at 16MHz
81+
cpu.writeData(CLKPC, CLKPCE);
82+
cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
83+
cpu.cycles += 2 * 4e6; // run 2 more seconds at 4MhZ
84+
expect(clock.timeMillis).toEqual(3000); // 3 seconds in total
85+
86+
cpu.writeData(CLKPC, CLKPCE);
87+
cpu.writeData(CLKPC, 1); // Divide by 2 (2^1)
88+
cpu.cycles += 0.5 * 8e6; // run 0.5 more seconds at 8MhZ
89+
expect(clock.timeMillis).toEqual(3500); // 3.5 seconds in total
90+
});
91+
});
92+
});

src/peripherals/clock.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* AVR8 Clock
3+
* Part of AVR8js
4+
* Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
5+
*
6+
* Copyright (C) 2020, Uri Shaked
7+
*/
8+
9+
import { CPU } from '../cpu/cpu';
10+
import { u32, u8 } from '../types';
11+
12+
const CLKPCE = 128;
13+
14+
export interface AVRClockConfig {
15+
CLKPR: u8;
16+
}
17+
18+
export const clockConfig: AVRClockConfig = {
19+
CLKPR: 0x61,
20+
};
21+
22+
const prescalers = [
23+
1,
24+
2,
25+
4,
26+
8,
27+
16,
28+
32,
29+
64,
30+
128,
31+
256,
32+
33+
// The following values are "reserved" according to the datasheet, so we measured
34+
// with a scope to figure them out (on ATmega328p)
35+
2,
36+
4,
37+
8,
38+
16,
39+
32,
40+
64,
41+
128,
42+
];
43+
44+
export class AVRClock {
45+
private clockEnabledCycles = 0;
46+
private prescalerValue = 1;
47+
cyclesDelta = 0;
48+
49+
constructor(
50+
private cpu: CPU,
51+
private baseFreqHz: u32,
52+
private config: AVRClockConfig = clockConfig
53+
) {
54+
this.cpu.writeHooks[this.config.CLKPR] = (clkpr) => {
55+
if ((!this.clockEnabledCycles || this.clockEnabledCycles < cpu.cycles) && clkpr === CLKPCE) {
56+
this.clockEnabledCycles = this.cpu.cycles + 4;
57+
} else if (this.clockEnabledCycles && this.clockEnabledCycles >= cpu.cycles) {
58+
this.clockEnabledCycles = 0;
59+
const index = clkpr & 0xf;
60+
const oldPrescaler = this.prescalerValue;
61+
this.prescalerValue = prescalers[index];
62+
this.cpu.data[this.config.CLKPR] = index;
63+
if (oldPrescaler !== this.prescalerValue) {
64+
this.cyclesDelta =
65+
(cpu.cycles + this.cyclesDelta) * (oldPrescaler / this.prescalerValue) - cpu.cycles;
66+
}
67+
}
68+
69+
return true;
70+
};
71+
}
72+
73+
get frequency() {
74+
return this.baseFreqHz / this.prescalerValue;
75+
}
76+
77+
get prescaler() {
78+
return this.prescalerValue;
79+
}
80+
81+
get timeNanos() {
82+
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e9;
83+
}
84+
85+
get timeMicros() {
86+
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e6;
87+
}
88+
89+
get timeMillis() {
90+
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e3;
91+
}
92+
}

0 commit comments

Comments
 (0)