diff options
Diffstat (limited to '')
| -rw-r--r-- | src/peripherals/watchdog.ts | 134 |
1 files changed, 134 insertions, 0 deletions
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; + } +} |
