aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
authorUri Shaked2021-09-10 01:35:06 +0300
committerUri Shaked2021-09-10 01:35:06 +0300
commit39fe0472ce7b7f54438e69f47705086bc60d9716 (patch)
treeb84cd4c3829c87dde6f8ea56fe6342a891fd9525 /src/peripherals
parent0.17.1 (diff)
downloadavr8js-39fe0472ce7b7f54438e69f47705086bc60d9716.tar.gz
avr8js-39fe0472ce7b7f54438e69f47705086bc60d9716.tar.bz2
avr8js-39fe0472ce7b7f54438e69f47705086bc60d9716.zip
feat(watchdog): implement watchdog timer #106
Diffstat (limited to '')
-rw-r--r--src/peripherals/watchdog.spec.ts210
-rw-r--r--src/peripherals/watchdog.ts134
2 files changed, 344 insertions, 0 deletions
diff --git a/src/peripherals/watchdog.spec.ts b/src/peripherals/watchdog.spec.ts
new file mode 100644
index 0000000..6fea3b3
--- /dev/null
+++ b/src/peripherals/watchdog.spec.ts
@@ -0,0 +1,210 @@
+/**
+ * AVR8 Watchdog Timer Test Suite
+ * Part of AVR8js
+ *
+ * Copyright (C) 2021 Uri Shaked
+ */
+
+import { AVRClock, clockConfig } from '..';
+import { CPU } from '../cpu/cpu';
+import { asmProgram, TestProgramRunner } from '../utils/test-utils';
+import { AVRWatchdog, watchdogConfig } from './watchdog';
+
+const R20 = 20;
+
+const MCUSR = 0x54;
+const WDRF = 1 << 3;
+
+const WDTCSR = 0x60;
+const WDP0 = 1 << 0;
+const WDP1 = 1 << 1;
+const WDP2 = 1 << 2;
+const WDE = 1 << 3;
+const WDCE = 1 << 4;
+const WDP3 = 1 << 5;
+const WDIE = 1 << 6;
+
+const INT_WDT = 0xc;
+
+describe('Watchdog', () => {
+ it('should correctly calculate the prescaler from WDTCSR', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ cpu.writeData(WDTCSR, WDCE | WDE);
+ cpu.writeData(WDTCSR, 0);
+ expect(watchdog.prescaler).toEqual(2048);
+ cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0);
+ expect(watchdog.prescaler).toEqual(256 * 1024);
+ cpu.writeData(WDTCSR, WDP3 | WDP0);
+ expect(watchdog.prescaler).toEqual(1024 * 1024);
+ });
+
+ it('should not change the prescaler unless WDCE is set', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ cpu.writeData(WDTCSR, 0);
+ expect(watchdog.prescaler).toEqual(2048);
+ cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0);
+ expect(watchdog.prescaler).toEqual(2048);
+
+ cpu.writeData(WDTCSR, WDCE | WDE);
+ cpu.cycles += 5; // WDCE should expire after 4 cycles
+ cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0);
+ expect(watchdog.prescaler).toEqual(2048);
+ });
+
+ it('should reset the CPU when the timer expires', () => {
+ const { program } = asmProgram(`
+ ; register addresses
+ _REPLACE WDTCSR, ${WDTCSR}
+
+ ; Setup watchdog
+ ldi r16, ${WDE | WDCE}
+ sts WDTCSR, r16
+ ldi r16, ${WDE}
+ sts WDTCSR, r16
+
+ nop
+
+ break
+ `);
+ const cpu = new CPU(program);
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ const runner = new TestProgramRunner(cpu);
+
+ // Setup: enable watchdog timer
+ runner.runInstructions(4);
+ expect(watchdog.enabled).toBe(true);
+
+ // Now we skip 8ms. Watchdog shouldn't fire, yet
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(1);
+
+ // Now we skip an extra 8ms. Watchdog should fire and reset!
+ cpu.cycles += 16000 * 8;
+ cpu.tick();
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.readData(MCUSR)).toEqual(WDRF);
+ });
+
+ it('should extend the watchdog timeout when executing a WDR instruction', () => {
+ const { program } = asmProgram(`
+ ; register addresses
+ _REPLACE WDTCSR, ${WDTCSR}
+
+ ; Setup watchdog
+ ldi r16, ${WDE | WDCE}
+ sts WDTCSR, r16
+ ldi r16, ${WDE}
+ sts WDTCSR, r16
+
+ wdr
+ nop
+
+ break
+ `);
+ const cpu = new CPU(program);
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ const runner = new TestProgramRunner(cpu);
+
+ // Setup: enable watchdog timer
+ runner.runInstructions(4);
+ expect(watchdog.enabled).toBe(true);
+
+ // Now we skip 8ms. Watchdog shouldn't fire, yet
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(1);
+
+ // Now we skip an extra 8ms. We extended the timeout with WDR, so watchdog won't fire yet
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(1);
+
+ // Finally, another 8ms bring us to 16ms since last WDR, and watchdog should fire
+ cpu.cycles += 16000 * 8;
+ cpu.tick();
+ expect(cpu.pc).toEqual(0);
+ });
+
+ it('should fire an interrupt when the watchdog expires and WDIE is set', () => {
+ const { program } = asmProgram(`
+ ; register addresses
+ _REPLACE WDTCSR, ${WDTCSR}
+
+ ; Setup watchdog
+ ldi r16, ${WDE | WDCE}
+ sts WDTCSR, r16
+ ldi r16, ${WDE | WDIE}
+ sts WDTCSR, r16
+
+ nop
+ sei
+
+ break
+ `);
+ const cpu = new CPU(program);
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ const runner = new TestProgramRunner(cpu);
+
+ // Setup: enable watchdog timer
+ runner.runInstructions(4);
+ expect(watchdog.enabled).toBe(true);
+
+ // Now we skip 8ms. Watchdog shouldn't fire, yet
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(1);
+
+ // Now we skip an extra 8ms. Watchdog should fire and jump to the interrupt handler
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(1);
+
+ expect(cpu.pc).toEqual(INT_WDT);
+ // The watchdog timer should also clean the WDIE bit, so next timeout will reset the MCU.
+ expect(cpu.readData(WDTCSR) & WDIE).toEqual(0);
+ });
+
+ it('should not reset the CPU if the watchdog has been disabled', () => {
+ const { program } = asmProgram(`
+ ; register addresses
+ _REPLACE WDTCSR, ${WDTCSR}
+
+ ; Setup watchdog
+ ldi r16, ${WDE | WDCE}
+ sts WDTCSR, r16
+ ldi r16, ${WDE}
+ sts WDTCSR, r16
+
+ ; disable watchdog
+ ldi r16, ${WDE | WDCE}
+ sts WDTCSR, r16
+ ldi r16, 0
+ sts WDTCSR, r16
+
+ ldi r20, 55
+
+ break
+ `);
+ const cpu = new CPU(program);
+ const clock = new AVRClock(cpu, 16e6, clockConfig);
+ const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock);
+ const runner = new TestProgramRunner(cpu);
+
+ // Setup: enable watchdog timer
+ runner.runInstructions(4);
+ expect(watchdog.enabled).toBe(true);
+
+ // Now we skip 8ms. Watchdog shouldn't fire, yet. We disable it.
+ cpu.cycles += 16000 * 8;
+ runner.runInstructions(4);
+
+ // Now we skip an extra 20ms. Watchdog shouldn't reset!
+ cpu.cycles += 16000 * 20;
+ runner.runInstructions(1);
+ expect(cpu.pc).not.toEqual(0);
+ expect(cpu.data[R20]).toEqual(55); // assert that `ldi r20, 55` ran
+ });
+});
diff --git a/src/peripherals/watchdog.ts b/src/peripherals/watchdog.ts
new file mode 100644
index 0000000..dc66220
--- /dev/null
+++ b/src/peripherals/watchdog.ts
@@ -0,0 +1,134 @@
+/**
+ * AVR8 Watchdog Timer
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
+ *
+ * Copyright (C) 2021 Uri Shaked
+ */
+
+import { AVRClock } from '..';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
+import { u8 } from '../types';
+
+export interface WatchdogConfig {
+ watchdogInterrupt: u8;
+ MCUSR: u8;
+ WDTCSR: u8;
+}
+
+// Register bits:
+const MCUSR_WDRF = 0x8; // Watchdog System Reset Flag
+
+const WDTCSR_WDIF = 0x80;
+const WDTCSR_WDIE = 0x40;
+const WDTCSR_WDP3 = 0x20;
+const WDTCSR_WDCE = 0x10; // Watchdog Change Enable
+const WDTCSR_WDE = 0x8;
+const WDTCSR_WDP2 = 0x4;
+const WDTCSR_WDP1 = 0x2;
+const WDTCSR_WDP0 = 0x1;
+const WDTCSR_WDP210 = WDTCSR_WDP2 | WDTCSR_WDP1 | WDTCSR_WDP0;
+
+const WDTCSR_PROTECT_MASK = WDTCSR_WDE | WDTCSR_WDP3 | WDTCSR_WDP210;
+
+export const watchdogConfig: WatchdogConfig = {
+ watchdogInterrupt: 0x0c,
+ MCUSR: 0x54,
+ WDTCSR: 0x60,
+};
+
+export class AVRWatchdog {
+ readonly clockFrequency = 128000;
+
+ /**
+ * Used to keep track on the last write to WDCE. Once written, the WDE/WDP* bits can be changed.
+ */
+ private changeEnabledCycles = 0;
+ private watchdogTimeout = 0;
+ private enabledValue = false;
+ private scheduled = false;
+
+ // Interrupts
+ private Watchdog: AVRInterruptConfig = {
+ address: this.config.watchdogInterrupt,
+ flagRegister: this.config.WDTCSR,
+ flagMask: WDTCSR_WDIF,
+ enableRegister: this.config.WDTCSR,
+ enableMask: WDTCSR_WDIE,
+ };
+
+ constructor(private cpu: CPU, private config: WatchdogConfig, private clock: AVRClock) {
+ const { WDTCSR } = config;
+ this.cpu.onWatchdogReset = () => {
+ this.resetWatchdog();
+ };
+ cpu.writeHooks[WDTCSR] = (value: u8, oldValue: u8) => {
+ if (value & WDTCSR_WDCE && value & WDTCSR_WDE) {
+ this.changeEnabledCycles = this.cpu.cycles + 4;
+ value = value & ~WDTCSR_PROTECT_MASK;
+ } else {
+ if (this.cpu.cycles >= this.changeEnabledCycles) {
+ value = (value & ~WDTCSR_PROTECT_MASK) | (oldValue & WDTCSR_PROTECT_MASK);
+ }
+ this.enabledValue = !!(value & WDTCSR_WDE || value & WDTCSR_WDIE);
+ this.cpu.data[WDTCSR] = value;
+ }
+
+ if (this.enabled) {
+ this.resetWatchdog();
+ }
+
+ if (this.enabled && !this.scheduled) {
+ this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
+ }
+
+ this.cpu.clearInterruptByFlag(this.Watchdog, value);
+ return true;
+ };
+ }
+
+ resetWatchdog() {
+ const cycles = Math.floor((this.clock.frequency / this.clockFrequency) * this.prescaler);
+ this.watchdogTimeout = this.cpu.cycles + cycles;
+ }
+
+ checkWatchdog = () => {
+ if (this.enabled && this.cpu.cycles >= this.watchdogTimeout) {
+ // Watchdog timed out!
+ const wdtcsr = this.cpu.data[this.config.WDTCSR];
+ if (wdtcsr & WDTCSR_WDIE) {
+ this.cpu.setInterruptFlag(this.Watchdog);
+ }
+ if (wdtcsr & WDTCSR_WDE) {
+ if (wdtcsr & WDTCSR_WDIE) {
+ this.cpu.data[this.config.WDTCSR] &= ~WDTCSR_WDIE;
+ } else {
+ this.cpu.reset();
+ this.scheduled = false;
+ this.cpu.data[this.config.MCUSR] |= MCUSR_WDRF;
+ return;
+ }
+ }
+ this.resetWatchdog();
+ }
+ if (this.enabled) {
+ this.scheduled = true;
+ this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
+ } else {
+ this.scheduled = false;
+ }
+ };
+
+ get enabled() {
+ return this.enabledValue;
+ }
+
+ /**
+ * The base clock frequency is 128KHz. Thus, a prescaler of 2048 gives 16ms timeout.
+ */
+ get prescaler() {
+ const wdtcsr = this.cpu.data[this.config.WDTCSR];
+ const value = ((wdtcsr & WDTCSR_WDP3) >> 2) | (wdtcsr & WDTCSR_WDP210);
+ return 2048 << value;
+ }
+}
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.