This commit is contained in:
lokathor 2022-10-15 17:57:55 -06:00
parent 796e8351f3
commit 8d7df89f8d

View file

@ -1,22 +1,98 @@
//! Module to interface with the GBA's four timer units.
//!
//! Similar to the background layers and DMA units, there are four timer units
//! and they're numbered 0 through 3.
//!
//! There's two hardware addresses that control each timer.
//! * The timer's high address is the [`TimerControl`] bits.
//! * The timer's low address is a `u16` which *reads* the timer's "count"
//! value, but *writes* the timer's "reload" value. In this crate we actually
//! represent that as two separate MMIO controls for improved code clarity.
//! Just be aware that in mGBA's debugger and in other documentation you'll
//! see it as a single address.
//!
//! When a timer is disabled, it will continue to read the count value that it
//! stopped at.
//!
//! ## Reloading
//!
//! When the timer goes from disabled to enabled, or when the timer overflows,
//! the last set reload value is copied to the counter value.
//!
//! ## Ticking
//!
//! When a timer is enabled, the timer will tick every so often. Each tick
//! increases the counter value by 1. The rate at which the timer ticks depends
//! on the timer's configuration:
//!
//! * If the `cascade` bit is set the timer will tick once per overflow of the
//! next lower timer. For example, if timer 3 is set to cascade, it will tick
//! once per overflow of timer 2. Note that timer 0 ignores the cascade bit,
//! since it doesn't have a "next lower" timer.
//! * Otherwise, the timer ticks every one or more CPU cycles, according to the
//! [`TimerScale`] set in the `scale` field.
//!
//! ## Overflows
//!
//! When a timer would tick *above* `u16::MAX` then an overflow occurs. This can
//! trigger an interrupt, and will also cause the timer to copy its reload value
//! into its counter.
//!
//! If you want a timer to overflow every `x` ticks (where `x` is non-zero),
//! then use the [`wrapping_neg`](u16::wrapping_neg) method to easily get the
//! right reload value to set:
//!
//! ```
//! # use gba::prelude::*;
//! let x = 7_u16;
//! TIMER0_RELOAD.write(x.wrapping_neg());
//! ```
//!
//! ## Timer Tips
//!
//! When a timer goes from disabled to enabled it will reset the counter value
//! to the reload value. If you want to temporarily pause a timer *without*
//! having the counter value get reset when you resume the timer you can instead
//! set the `cascade` bit of the timer while the next lower timer is disabled.
//! This keeps the timer active but prevents it from ticking. Note that this
//! doesn't work for timer 0 because that timer ignores the cascade bit.
use crate::macros::{pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field};
/// A number of CPU cycles per timer tick.
///
/// * The GBA's CPU runs at 16,777,216 cycles per second (16.78 Mhz).
/// * The GBA's PPU outputs one pixel per 4 CPU cycles.
/// * It takes 280,896 cycles for one full frame (when you add up all the draw
/// and blank periods).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum TimerPrescale {
pub enum TimerScale {
/// Approximately 59.6 nanoseconds
#[default]
_1 = 0,
/// Approximately 3.815 microseconds
_64 = 1,
/// Approximately 15.26 microseconds
_256 = 2,
/// Approximately 61.04 microseconds
_1024 = 3,
}
/// Timer configuration bits.
///
/// * `prescale` is how many CPU cycles per tick
/// * `cascade` will override the prescale value and instead tick the timer once
/// per overflow of the next lower timer. Timer 0 ignores the cascade bit.
/// * `overflow_irq` will cause an IRQ to be sent each overflow.
/// * `enabled` makes the timer tick.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TimerControl(u16);
impl TimerControl {
pub_const_fn_new_zeroed!();
u16_enum_field!(0 - 1: TimerPrescale, prescale, with_prescale);
u16_bool_field!(2, chained, with_chained);
u16_enum_field!(0 - 1: TimerScale, scale, with_scale);
u16_bool_field!(2, cascade, with_cascade);
u16_bool_field!(6, overflow_irq, with_overflow_irq);
u16_bool_field!(7, enabled, with_enabled);
}