diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f2b41f641926786a69f6c0b73accda78945171d..d65e876308247c70c589c920c4258457b9c370d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
-* Support for sprite drawing, works wit Tetris
+* Support for sprite drawing, works with Tetris
+* Support for timers
 
 ### Changed
 
diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs
index 2c916122b4e30b75f89b4b75335f7aae23769f08..2e236739c81b0488159c8132e66c0d5408420ba3 100644
--- a/examples/sdl/src/main.rs
+++ b/examples/sdl/src/main.rs
@@ -34,7 +34,7 @@ fn start_sdl() -> Graphics {
 
     // creates the system window that is going to be used to
     // show the emulator and sets it to the central are o screen
-    let mut window = video_subsystem
+    let window = video_subsystem
         .window(
             TITLE,
             2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale
@@ -84,7 +84,8 @@ fn main() {
     let mut game_boy = GameBoy::new();
     game_boy.load_boot_default();
 
-    game_boy.load_rom_file("../../res/roms.prop/tetris.gb");
+    //game_boy.load_rom_file("../../res/roms.prop/tetris.gb");
+    game_boy.load_rom_file("../../res/roms.prop/dr_mario.gb");
     //game_boy.load_rom_file("../../res/roms.prop/alleyway.gb");
 
     //game_boy.load_rom_file("../../res/roms/firstwhite.gb");
diff --git a/src/cpu.rs b/src/cpu.rs
index 1e29d760c946baf923b91ef179fcb6b9b657f110..9d538684346b700d201f9e220673d215ea31d877 100644
--- a/src/cpu.rs
+++ b/src/cpu.rs
@@ -5,6 +5,7 @@ use crate::{
     mmu::Mmu,
     pad::Pad,
     ppu::Ppu,
+    timer::Timer,
 };
 
 pub const PREFIX: u8 = 0xcb;
@@ -129,6 +130,27 @@ impl Cpu {
 
                 return 16;
             }
+
+            // @todo aggregate the handling of these interrupts
+            if (self.mmu.ie & 0x04 == 0x04) && self.mmu.timer().int_tima() {
+                println!("Going to run Timer interrupt handler (0x50)");
+
+                self.disable_int();
+                self.push_word(pc);
+                self.pc = 0x50;
+
+                // acknowledges that the timer interrupt has been
+                // properly handled
+                self.mmu.timer().ack_tima();
+
+                // in case the CPU is currently halted waiting
+                // for an interrupt, releases it
+                if self.halted {
+                    self.halted = false;
+                }
+
+                return 16;
+            }
         }
 
         // in case the CPU is currently in the halted state
@@ -192,6 +214,11 @@ impl Cpu {
         self.mmu().pad()
     }
 
+    #[inline(always)]
+    pub fn timer(&mut self) -> &mut Timer {
+        self.mmu().timer()
+    }
+
     #[inline(always)]
     pub fn halted(&self) -> bool {
         self.halted
diff --git a/src/data.rs b/src/data.rs
index c9330da6e7c417932c73e6ade7f54d794db89d5f..c77a3e8e5cc6a85a32c0a09832a9d8e89e14cd2e 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -9,8 +9,6 @@ pub enum BootRom {
     MgbBootix,
 }
 
-/// Static data corresponding to the DMG boot ROM
-/// allows freely using the emulator without external dependency.
 pub const DMG_BOOT: [u8; 256] = [
     49, 254, 255, 175, 33, 255, 159, 50, 203, 124, 32, 251, 33, 38, 255, 14, 17, 62, 128, 50, 226,
     12, 62, 243, 226, 50, 62, 119, 119, 62, 252, 224, 71, 17, 4, 1, 33, 16, 128, 26, 205, 149, 0,
diff --git a/src/gb.rs b/src/gb.rs
index b58146b668921115368ef86be340f7302646fa51..acfd5d9825dea000a104bef7fed1ce42213fd263 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -4,6 +4,7 @@ use crate::{
     mmu::Mmu,
     pad::{Pad, PadKey},
     ppu::{Ppu, Tile, FRAME_BUFFER_SIZE},
+    timer::Timer,
     util::read_file,
 };
 
@@ -22,9 +23,10 @@ pub struct GameBoy {
 impl GameBoy {
     #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
     pub fn new() -> Self {
-        let pad = Pad::new();
         let ppu = Ppu::new();
-        let mmu = Mmu::new(ppu, pad);
+        let pad = Pad::new();
+        let timer = Timer::new();
+        let mmu = Mmu::new(ppu, pad, timer);
         let cpu = Cpu::new(mmu);
         Self { cpu: cpu }
     }
@@ -50,6 +52,7 @@ impl GameBoy {
     pub fn clock(&mut self) -> u8 {
         let cycles = self.cpu_clock();
         self.ppu_clock(cycles);
+        self.timer_clock(cycles);
         cycles
     }
 
@@ -61,6 +64,10 @@ impl GameBoy {
         self.ppu().clock(cycles)
     }
 
+    pub fn timer_clock(&mut self, cycles: u8) {
+        self.timer().clock(cycles)
+    }
+
     pub fn load_rom(&mut self, data: &[u8]) {
         self.cpu.mmu().write_rom(0x0000, data);
     }
@@ -144,6 +151,10 @@ impl GameBoy {
         self.cpu.pad()
     }
 
+    pub fn timer(&mut self) -> &mut Timer {
+        self.cpu.timer()
+    }
+
     pub fn frame_buffer(&mut self) -> &Box<[u8; FRAME_BUFFER_SIZE]> {
         &(self.ppu().frame_buffer)
     }
diff --git a/src/lib.rs b/src/lib.rs
index cd9b622a2584ae2037e005e24466eedd594e1cfd..11af82a5047fa0b409b5bbf4eccfad99daa9bec5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,4 +5,5 @@ pub mod inst;
 pub mod mmu;
 pub mod pad;
 pub mod ppu;
+pub mod timer;
 pub mod util;
diff --git a/src/mmu.rs b/src/mmu.rs
index a64f6a6aca548f381c13b93907e3d366ab6ed516..e3dae1ddcfd9fe68afa9179e48b6ca229dcc045e 100644
--- a/src/mmu.rs
+++ b/src/mmu.rs
@@ -1,4 +1,4 @@
-use crate::{pad::Pad, ppu::Ppu};
+use crate::{pad::Pad, ppu::Ppu, timer::Timer};
 
 pub const BIOS_SIZE: usize = 256;
 pub const ROM_SIZE: usize = 32768;
@@ -6,24 +6,29 @@ pub const RAM_SIZE: usize = 8192;
 pub const ERAM_SIZE: usize = 8192;
 
 pub struct Mmu {
+    /// Register that controls the interrupts that are considered
+    /// to be enabled and should be triggered.
+    pub ie: u8,
+
+    /// Reference to the PPU (Pixel Processing Unit) that is going
+    /// to be used both for VRAM reading/writing and to forward
+    /// some of the access operations.
     ppu: Ppu,
     pad: Pad,
+    timer: Timer,
     boot_active: bool,
     boot: [u8; BIOS_SIZE],
     rom: [u8; ROM_SIZE],
     ram: [u8; RAM_SIZE],
     eram: [u8; RAM_SIZE],
-
-    /// Registers that controls the interrupts that are considered
-    /// to be enabled and should be triggered.
-    pub ie: u8,
 }
 
 impl Mmu {
-    pub fn new(ppu: Ppu, pad: Pad) -> Self {
+    pub fn new(ppu: Ppu, pad: Pad, timer: Timer) -> Self {
         Self {
             ppu: ppu,
             pad: pad,
+            timer: timer,
             boot_active: true,
             boot: [0u8; BIOS_SIZE],
             rom: [0u8; ROM_SIZE],
@@ -49,6 +54,10 @@ impl Mmu {
         &mut self.pad
     }
 
+    pub fn timer(&mut self) -> &mut Timer {
+        &mut self.timer
+    }
+
     pub fn boot_active(&self) -> bool {
         self.boot_active
     }
@@ -87,28 +96,30 @@ impl Mmu {
                 0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900
                 | 0xa00 | 0xb00 | 0xc00 | 0xd00 => self.ram[(addr & 0x1fff) as usize],
                 0xe00 => self.ppu.oam[(addr & 0x009f) as usize],
-                0xf00 => {
-                    if addr == 0xffff {
-                        self.ie
-                    } else if addr >= 0xff80 {
-                        self.ppu.hram[(addr & 0x007f) as usize]
-                    } else {
-                        match addr & 0x00f0 {
-                            0x00 => match addr & 0x00ff {
-                                0x00 => self.pad.read(addr),
-                                _ => {
-                                    println!("Reading from unknown IO control 0x{:04x}", addr);
-                                    0x00
-                                }
-                            },
-                            0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr),
+                0xf00 => match addr & 0x00ff {
+                    0x0f => {
+                        let value = if self.ppu.int_vblank() { 0x01 } else { 0x00 }
+                            | if self.timer.int_tima() { 0x04 } else { 0x00 };
+                        value
+                    }
+                    0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize],
+                    0xff => self.ie,
+                    _ => match addr & 0x00f0 {
+                        0x00 => match addr & 0x00ff {
+                            0x00 => self.pad.read(addr),
+                            0x04..=0x07 => self.timer.read(addr),
                             _ => {
                                 println!("Reading from unknown IO control 0x{:04x}", addr);
                                 0x00
                             }
+                        },
+                        0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr),
+                        _ => {
+                            println!("Reading from unknown IO control 0x{:04x}", addr);
+                            0x00
                         }
-                    }
-                }
+                    },
+                },
                 addr => panic!("Reading from unknown location 0x{:04x}", addr),
             },
             addr => panic!("Reading from unknown location 0x{:04x}", addr),
@@ -159,15 +170,18 @@ impl Mmu {
                     self.ppu.oam[(addr & 0x009f) as usize] = value;
                     self.ppu.update_object(addr, value);
                 }
-                0xf00 => {
-                    if addr == 0xffff {
-                        self.ie = value;
-                    } else if addr >= 0xff80 {
-                        self.ppu.hram[(addr & 0x007f) as usize] = value;
-                    } else {
+                0xf00 => match addr & 0x00ff {
+                    0x0f => {
+                        self.ppu.set_int_vblank(value & 0x01 == 0x01);
+                        self.timer.set_int_tima(value & 0x04 == 0x04);
+                    }
+                    0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize] = value,
+                    0xff => self.ie = value,
+                    _ => {
                         match addr & 0x00f0 {
                             0x00 => match addr & 0x00ff {
                                 0x00 => self.pad.write(addr, value),
+                                0x04..=0x07 => self.timer.write(addr, value),
                                 _ => println!("Writing to unknown IO control 0x{:04x}", addr),
                             },
                             0x40 | 0x60 | 0x70 => {
@@ -189,7 +203,7 @@ impl Mmu {
                             _ => println!("Writing to unknown IO control 0x{:04x}", addr),
                         }
                     }
-                }
+                },
                 addr => panic!("Writing in unknown location 0x{:04x}", addr),
             },
             addr => panic!("Writing in unknown location 0x{:04x}", addr),
diff --git a/src/ppu.rs b/src/ppu.rs
index 6cb2b802f9487f591eea7ee7747c0d94afd69bfa..c2073b2fde7864f4103b46f4840f55cf6669964f 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -327,22 +327,22 @@ impl Ppu {
         match self.mode {
             PpuMode::OamRead => {
                 if self.mode_clock >= 80 {
-                    self.mode_clock = 0;
                     self.mode = PpuMode::VramRead;
+                    self.mode_clock = self.mode_clock - 80;
                 }
             }
             PpuMode::VramRead => {
                 if self.mode_clock >= 172 {
                     self.render_line();
 
-                    self.mode_clock = 0;
                     self.mode = PpuMode::HBlank;
+                    self.mode_clock = self.mode_clock - 172;
                 }
             }
             PpuMode::HBlank => {
                 if self.mode_clock >= 204 {
                     // increments the register that holds the
-                    // information about the current line in drawign
+                    // information about the current line in drawing
                     self.ly += 1;
 
                     // in case we've reached the end of the
@@ -354,7 +354,7 @@ impl Ppu {
                         self.mode = PpuMode::OamRead;
                     }
 
-                    self.mode_clock = 0;
+                    self.mode_clock = self.mode_clock - 204;
                 }
             }
             PpuMode::VBlank => {
@@ -369,7 +369,7 @@ impl Ppu {
                         self.ly = 0;
                     }
 
-                    self.mode_clock = 0;
+                    self.mode_clock = self.mode_clock - 456;
                 }
             }
         }
@@ -498,6 +498,10 @@ impl Ppu {
         self.int_vblank
     }
 
+    pub fn set_int_vblank(&mut self, value: bool) {
+        self.int_vblank = value;
+    }
+
     pub fn ack_vblank(&mut self) {
         self.int_vblank = false;
     }
@@ -705,13 +709,13 @@ impl Ppu {
                     // the object is only considered visible if it's a priority
                     // or if the underlying pixel is transparent (zero value)
                     let is_visible = obj.priority || self.color_buffer[color_offset] == 0;
-                    if is_visible {
+                    let pixel = tile_row[if obj.xflip { 7 - x } else { x }];
+                    if is_visible && pixel != 0 {
                         // obtains the current pixel data from the tile row and
                         // re-maps it according to the object palette
-                        let pixel = tile_row[if obj.xflip { 7 - x } else { x }];
                         let color = palette[pixel as usize];
 
-                        // set the color pixel in the frame buffer
+                        // sets the color pixel in the frame buffer
                         self.frame_buffer[frame_offset] = color[0];
                         self.frame_buffer[frame_offset + 1] = color[1];
                         self.frame_buffer[frame_offset + 2] = color[2];
diff --git a/src/timer.rs b/src/timer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0169e9e8ceec2700463b66134338174179ef1c15
--- /dev/null
+++ b/src/timer.rs
@@ -0,0 +1,89 @@
+pub struct Timer {
+    div: u8,
+    tima: u8,
+    tma: u8,
+    tac: u8,
+    div_clock: u16,
+    tima_clock: u16,
+    tima_enabled: bool,
+    tima_ratio: u16,
+    int_tima: bool,
+}
+
+impl Timer {
+    pub fn new() -> Self {
+        Self {
+            div: 0,
+            tima: 0,
+            tma: 0,
+            tac: 0x0,
+            div_clock: 0,
+            tima_clock: 0,
+            tima_enabled: false,
+            tima_ratio: 1024,
+            int_tima: false,
+        }
+    }
+
+    pub fn clock(&mut self, cycles: u8) {
+        self.div_clock += cycles as u16;
+        self.tima_clock += cycles as u16;
+
+        if self.div_clock >= 256 {
+            self.div = self.div.wrapping_add(1);
+            self.div_clock = self.div_clock - 256;
+        }
+
+        if self.tima_enabled && self.tima_clock >= self.tima_ratio {
+            if self.tima == 0xff {
+                self.int_tima = true;
+                self.tima = self.tma;
+            }
+
+            self.tima = self.tima.wrapping_add(1);
+            self.tima_clock = self.tima_clock - self.tima_ratio;
+        }
+    }
+
+    pub fn read(&mut self, addr: u16) -> u8 {
+        match addr & 0x00ff {
+            0x04 => self.div,
+            0x05 => self.tima,
+            0x06 => self.tma,
+            0x07 => self.tac,
+            addr => panic!("Reding from unknown Timer location 0x{:04x}", addr),
+        }
+    }
+
+    pub fn write(&mut self, addr: u16, value: u8) {
+        match addr & 0x00ff {
+            0x04 => self.div = 0,
+            0x05 => self.tima = value,
+            0x06 => self.tma = value,
+            0x07 => {
+                self.tac = value;
+                match value & 0x03 {
+                    0x00 => self.tima_ratio = 1024,
+                    0x01 => self.tima_ratio = 16,
+                    0x02 => self.tima_ratio = 64,
+                    0x03 => self.tima_ratio = 256,
+                    value => panic!("Invalid TAC value 0x{:02x}", value),
+                }
+                self.tima_enabled = value & 0x04 == 0x04;
+            }
+            addr => panic!("Writing to unknown Timer location 0x{:04x}", addr),
+        }
+    }
+
+    pub fn int_tima(&self) -> bool {
+        self.int_tima
+    }
+
+    pub fn set_int_tima(&mut self, value: bool) {
+        self.int_tima = value;
+    }
+
+    pub fn ack_tima(&mut self) {
+        self.int_tima = false;
+    }
+}