aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2020-11-25 13:46:54 +0200
committerUri Shaked2020-11-25 13:49:16 +0200
commit43fe0a5b858060f569087acaee002be064b6c70b (patch)
treeeed366e3e544f92b99cfbc39ececad5e443bb7ce
parent0.12.0 (diff)
downloadavr8js-43fe0a5b858060f569087acaee002be064b6c70b.tar.gz
avr8js-43fe0a5b858060f569087acaee002be064b6c70b.tar.bz2
avr8js-43fe0a5b858060f569087acaee002be064b6c70b.zip
feat(clock): Clock Prescale (CLKPR) support #68
close #68
Diffstat (limited to '')
-rw-r--r--src/index.ts1
-rw-r--r--src/peripherals/clock.spec.ts92
-rw-r--r--src/peripherals/clock.ts92
3 files changed, 185 insertions, 0 deletions
diff --git a/src/index.ts b/src/index.ts
index d02718a..f4ac3aa 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -41,3 +41,4 @@ export {
} from './peripherals/eeprom';
export * from './peripherals/twi';
export { spiConfig, SPIConfig, SPITransferCallback, AVRSPI } from './peripherals/spi';
+export { AVRClock, AVRClockConfig, clockConfig } from './peripherals/clock';
diff --git a/src/peripherals/clock.spec.ts b/src/peripherals/clock.spec.ts
new file mode 100644
index 0000000..69fc832
--- /dev/null
+++ b/src/peripherals/clock.spec.ts
@@ -0,0 +1,92 @@
+import { CPU } from '../cpu/cpu';
+import { AVRClock, clockConfig } from './clock';
+
+// Clock Registers
+const CLKPC = 0x61;
+
+// Register bit names
+const CLKPCE = 128;
+
+describe('Clock', () => {
+ it('should set the prescaler when double-writing CLKPC', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
+ expect(clock.frequency).toEqual(2e6); // 2MHz
+ expect(cpu.readData(CLKPC)).toEqual(3);
+ });
+
+ it('should not update the prescaler if CLKPCE was not set CLKPC', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
+ expect(clock.frequency).toEqual(16e6); // still 16MHz
+ expect(cpu.readData(CLKPC)).toEqual(0);
+ });
+
+ it('should not update the prescaler if more than 4 cycles passed since setting CLKPCE', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.cycles += 6;
+ cpu.writeData(CLKPC, 3); // Divide by 8 (2^3)
+ expect(clock.frequency).toEqual(16e6); // still 16MHz
+ expect(cpu.readData(CLKPC)).toEqual(0);
+ });
+
+ describe('prescaler property', () => {
+ it('should return the current prescaler value', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 5); // Divide by 32 (2^5)
+ cpu.cycles = 16e6;
+ expect(clock.prescaler).toEqual(32);
+ });
+ });
+
+ describe('time properties', () => {
+ it('should return current number of microseconds, derived from base freq + prescaler', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
+ cpu.cycles = 16e6;
+ expect(clock.timeMillis).toEqual(4000); // 4 seconds
+ });
+
+ it('should return current number of milliseconds, derived from base freq + prescaler', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
+ cpu.cycles = 16e6;
+ expect(clock.timeMicros).toEqual(4e6); // 4 seconds
+ });
+
+ it('should return current number of nanoseconds, derived from base freq + prescaler', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
+ cpu.cycles = 16e6;
+ expect(clock.timeNanos).toEqual(4e9); // 4 seconds
+ });
+
+ it('should correctly calculate time when changing the prescale value at runtime', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ cpu.cycles = 16e6; // run 1 second at 16MHz
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 2); // Divide by 4 (2^2)
+ cpu.cycles += 2 * 4e6; // run 2 more seconds at 4MhZ
+ expect(clock.timeMillis).toEqual(3000); // 3 seconds in total
+
+ cpu.writeData(CLKPC, CLKPCE);
+ cpu.writeData(CLKPC, 1); // Divide by 2 (2^1)
+ cpu.cycles += 0.5 * 8e6; // run 0.5 more seconds at 8MhZ
+ expect(clock.timeMillis).toEqual(3500); // 3.5 seconds in total
+ });
+ });
+});
diff --git a/src/peripherals/clock.ts b/src/peripherals/clock.ts
new file mode 100644
index 0000000..1dbf4ba
--- /dev/null
+++ b/src/peripherals/clock.ts
@@ -0,0 +1,92 @@
+/**
+ * AVR8 Clock
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
+ *
+ * Copyright (C) 2020, Uri Shaked
+ */
+
+import { CPU } from '../cpu/cpu';
+import { u32, u8 } from '../types';
+
+const CLKPCE = 128;
+
+export interface AVRClockConfig {
+ CLKPR: u8;
+}
+
+export const clockConfig: AVRClockConfig = {
+ CLKPR: 0x61,
+};
+
+const prescalers = [
+ 1,
+ 2,
+ 4,
+ 8,
+ 16,
+ 32,
+ 64,
+ 128,
+ 256,
+
+ // The following values are "reserved" according to the datasheet, so we measured
+ // with a scope to figure them out (on ATmega328p)
+ 2,
+ 4,
+ 8,
+ 16,
+ 32,
+ 64,
+ 128,
+];
+
+export class AVRClock {
+ private clockEnabledCycles = 0;
+ private prescalerValue = 1;
+ cyclesDelta = 0;
+
+ constructor(
+ private cpu: CPU,
+ private baseFreqHz: u32,
+ private config: AVRClockConfig = clockConfig
+ ) {
+ this.cpu.writeHooks[this.config.CLKPR] = (clkpr) => {
+ if ((!this.clockEnabledCycles || this.clockEnabledCycles < cpu.cycles) && clkpr === CLKPCE) {
+ this.clockEnabledCycles = this.cpu.cycles + 4;
+ } else if (this.clockEnabledCycles && this.clockEnabledCycles >= cpu.cycles) {
+ this.clockEnabledCycles = 0;
+ const index = clkpr & 0xf;
+ const oldPrescaler = this.prescalerValue;
+ this.prescalerValue = prescalers[index];
+ this.cpu.data[this.config.CLKPR] = index;
+ if (oldPrescaler !== this.prescalerValue) {
+ this.cyclesDelta =
+ (cpu.cycles + this.cyclesDelta) * (oldPrescaler / this.prescalerValue) - cpu.cycles;
+ }
+ }
+
+ return true;
+ };
+ }
+
+ get frequency() {
+ return this.baseFreqHz / this.prescalerValue;
+ }
+
+ get prescaler() {
+ return this.prescalerValue;
+ }
+
+ get timeNanos() {
+ return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e9;
+ }
+
+ get timeMicros() {
+ return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e6;
+ }
+
+ get timeMillis() {
+ return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e3;
+ }
+}