diff --git a/dump.bat b/dump.bat index 1996735..8b90c30 100644 --- a/dump.bat +++ b/dump.bat @@ -1,15 +1,17 @@ cargo build --examples -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/basic_keyinput >target/ex-basic_keyinput.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/basic_keyinput >target/ex-basic_keyinput.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/do_nothing >target/ex-do_nothing.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/do_nothing >target/ex-do_nothing.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/mode0 >target/ex-mode0.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/mode0 >target/ex-mode0.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/mode3 >target/ex-mode3.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/mode3 >target/ex-mode3.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/mode4 >target/ex-mode4.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/mode4 >target/ex-mode4.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/mode5 >target/ex-mode5.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/mode5 >target/ex-mode5.txt -arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/paddle_ball >target/ex-paddle_ball.txt +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/paddle_ball >target/ex-paddle_ball.txt + +arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std --visualize-jumps target/thumbv4t-none-eabi/debug/examples/timer >target/ex-timer.txt diff --git a/examples/timer.rs b/examples/timer.rs new file mode 100644 index 0000000..a2886d3 --- /dev/null +++ b/examples/timer.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] + +use gba::{ + asm_runtime::USER_IRQ_HANDLER, + bios::VBlankIntrWait, + gba_cell::GbaCell, + mmio::{BACKDROP_COLOR, DISPCNT, DISPSTAT, IE, IME, TIMER0_CONTROL}, + timers::{CpusPerTick, TimerControl}, + video::{Color, DisplayControl, DisplayStatus}, + IrqBits, +}; + +static SECONDS: GbaCell = GbaCell::new(0); + +gba::panic_handler!(mgba_log_err); + +#[no_mangle] +extern "C" fn main() -> ! { + USER_IRQ_HANDLER.write(Some(irq_handler)); + IE.write(IrqBits::new().with_vblank(true).with_timer0(true)); + IME.write(true); + DISPSTAT.write(DisplayStatus::new().with_vblank_irq(true)); + + TIMER0_CONTROL.write( + TimerControl::new() + .with_enabled(true) + .with_send_irq(true) + // .with_cpus_per_tick(CpusPerTick::_64), + ); + + DISPCNT.write(DisplayControl::new().with_bg_mode(3)); + loop { + VBlankIntrWait(); + BACKDROP_COLOR.write(Color(SECONDS.read() as u16)); + } +} + +extern "C" fn irq_handler(bits: IrqBits) { + if bits.timer0() { + SECONDS.write(SECONDS.read().wrapping_add(1)); + } +} diff --git a/src/lib.rs b/src/lib.rs index 047c870..a04d5b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ pub mod per_project_setup; pub mod per_system_setup; pub mod random; pub mod sample_art; +pub mod timers; pub mod video; #[cfg(feature = "critical-section")] diff --git a/src/mmio/peripheral_controls.rs b/src/mmio/peripheral_controls.rs index de0bbc9..4168172 100644 --- a/src/mmio/peripheral_controls.rs +++ b/src/mmio/peripheral_controls.rs @@ -1,3 +1,5 @@ +use crate::timers::TimerControl; + use super::*; /// Display Control setting. @@ -125,6 +127,44 @@ pub const DMA3_TRANSFER_COUNT: VolAddress = pub const DMA3_CONTROL: VolAddress = unsafe { VolAddress::new(0x0400_00DE) }; +/// Timer0's current counter value. +pub const TIMER0_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0100) }; +/// Timer1's current counter value. +pub const TIMER1_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0104) }; +/// Timer2's current counter value. +pub const TIMER2_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0108) }; +/// Timer3's current counter value. +pub const TIMER3_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_010C) }; + +/// The value for Timer0 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER0_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0100) }; +/// The value for Timer1 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER1_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0104) }; +/// The value for Timer2 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER2_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0108) }; +/// The value for Timer3 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER3_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_010C) }; + +/// Control bits for Timer 0. +pub const TIMER0_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_0102) }; + +/// Control bits for Timer 1. +pub const TIMER1_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_0106) }; + +/// Control bits for Timer 2. +pub const TIMER2_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_010A) }; + +/// Control bits for Timer 3. +pub const TIMER3_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_010E) }; + /// Key Input (read-only). /// /// Gives the low-active button state of all system buttons. diff --git a/src/timers.rs b/src/timers.rs new file mode 100644 index 0000000..9662641 --- /dev/null +++ b/src/timers.rs @@ -0,0 +1,49 @@ +//! Timer related data types. + +use bitfrob::{u8_with_bit, u8_with_region}; + +/// Control bits for one of the GBA's four timers. +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct TimerControl(u8); +impl TimerControl { + /// A new, zeroed value. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// The number of CPU cycles per timer tick. + #[inline] + pub const fn with_cpus_per_tick(self, cpus: CpusPerTick) -> Self { + Self(u8_with_region(0, 1, self.0, cpus as u8)) + } + /// If the timer should *only* tick when the lower-number timer overflows. + /// + /// * When set, this **overrides** the `cpus_per_tick` value and Timer N will + /// instead tick once per overflow of Timer (N-1). + /// * This has no effect for Timer 0, since it has no lower numbered timer. + #[inline] + pub const fn cascade_ticks(self, cascade: bool) -> Self { + Self(u8_with_bit(2, self.0, cascade)) + } + /// If an overflow of this timer should send an interrupt. + #[inline] + pub const fn with_send_irq(self, irq: bool) -> Self { + Self(u8_with_bit(6, self.0, irq)) + } + /// If this timer is enabled. + #[inline] + pub const fn with_enabled(self, enabled: bool) -> Self { + Self(u8_with_bit(7, self.0, enabled)) + } +} + +/// How many CPU cycles per timer tick. +#[repr(u8)] +#[allow(missing_docs)] +pub enum CpusPerTick { + _1 = 0, + _64 = 1, + _256 = 2, + _1024 = 3, +}