91 lines
2.6 KiB
Rust
91 lines
2.6 KiB
Rust
#[derive(Default, Debug)]
|
|
pub struct Timer {
|
|
tima: u8, // Timer counter, interrupt on overflow
|
|
tma: u8,
|
|
tac: u8, // 00 = 4096 Hz [10240 ticks], 01 = 262144 Hz [160 ticks], 10 = 65536 Hz [640], 11 = 16384 Hz[2560]. Bit 2 0 = stop, 1 = start
|
|
div: u8, // Incremented at speed of 16384Hz
|
|
timer_interrupt: bool,
|
|
tick_counter: u16,
|
|
div_tick_counter: u16,
|
|
|
|
gb_ticks: u16,
|
|
}
|
|
|
|
const TIMER_SPEED: [u16; 4] = [64, 1, 4, 16];
|
|
const TIMER_ENABLE: u8 = (1 << 2);
|
|
|
|
impl Timer {
|
|
pub fn new() -> Timer {
|
|
Timer::default()
|
|
}
|
|
|
|
fn timer_clock_tick(&mut self) {
|
|
// The div reg will always tick
|
|
self.div_tick_counter += 1;
|
|
if self.div_tick_counter >= TIMER_SPEED[3] {
|
|
self.div = self.div.wrapping_add(1);
|
|
self.div_tick_counter -= TIMER_SPEED[3];
|
|
}
|
|
|
|
if (self.tac & TIMER_ENABLE) == TIMER_ENABLE {
|
|
// Is timer enabled?
|
|
self.tick_counter += 1;
|
|
let req_ticks = TIMER_SPEED[(self.tac & 3) as usize];
|
|
|
|
if self.tick_counter >= req_ticks {
|
|
if self.tima == 0xFF {
|
|
self.timer_interrupt = true;
|
|
self.tima = 0;
|
|
} else {
|
|
self.tima += 1;
|
|
}
|
|
self.tick_counter -= req_ticks;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn tick(&mut self, ticks: u16) {
|
|
// One tick 1/4.194304MHz on regular speed => 16 gb ticks
|
|
self.gb_ticks += ticks;
|
|
// If we're in GBC fast mode, we'd require 32 ticks instead of 16
|
|
while self.gb_ticks >= 16 {
|
|
self.timer_clock_tick();
|
|
self.gb_ticks -= 16;
|
|
}
|
|
}
|
|
|
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
match addr {
|
|
0xFF04 => self.div = 0,
|
|
0xFF05 => self.tima = val,
|
|
0xFF06 => self.tma = val,
|
|
0xFF07 => self.tac = val,
|
|
_ => println!("Timer: Write {:02X} to {:04X} unsupported", val, addr),
|
|
}
|
|
}
|
|
|
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
match addr {
|
|
0xFF04 => self.div,
|
|
0xFF05 => self.tima,
|
|
0xFF06 => self.tma,
|
|
0xFF07 => self.tac,
|
|
_ => {
|
|
println!("Timer: Read from {:04X} unsupported", addr);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn timer_interrupt(&mut self) -> bool {
|
|
// Returns whether or not a vblank interrupt should be done
|
|
// Yes, this is polling, and yes, this sucks.\
|
|
if self.timer_interrupt {
|
|
self.timer_interrupt = false;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|