aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
authorUri Shaked2020-11-25 13:46:54 +0200
committerUri Shaked2020-11-25 13:49:16 +0200
commit43fe0a5b858060f569087acaee002be064b6c70b (patch)
treeeed366e3e544f92b99cfbc39ececad5e443bb7ce /src/peripherals
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 'src/peripherals')
-rw-r--r--src/peripherals/clock.spec.ts92
-rw-r--r--src/peripherals/clock.ts92
2 files changed, 184 insertions, 0 deletions
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;
+ }
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.