mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-25 03:31:36 +11:00
164 lines
4.3 KiB
Rust
164 lines
4.3 KiB
Rust
#![no_std]
|
|
#![no_main]
|
|
#![feature(isa_attribute)]
|
|
|
|
use gba::{prelude::*, warn};
|
|
|
|
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
|
const RED: Color = Color::from_rgb(31, 0, 0);
|
|
const GREEN: Color = Color::from_rgb(0, 31, 0);
|
|
const BLUE: Color = Color::from_rgb(0, 0, 31);
|
|
const YELLOW: Color = Color::from_rgb(31, 31, 0);
|
|
const PINK: Color = Color::from_rgb(31, 0, 31);
|
|
|
|
#[panic_handler]
|
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
loop {}
|
|
}
|
|
|
|
fn start_timers() {
|
|
let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16;
|
|
const TIMER_SETTINGS: TimerControl =
|
|
TimerControl::new().with_irq_on_overflow(true).with_enabled(true);
|
|
|
|
TIMER0_RELOAD.write(init_val);
|
|
TIMER0_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(3));
|
|
TIMER1_RELOAD.write(init_val);
|
|
TIMER1_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(1));
|
|
}
|
|
|
|
#[no_mangle]
|
|
fn main() -> ! {
|
|
DISPCNT.write(DisplayControl::new().with_display_mode(3).with_display_bg2(true));
|
|
mode3::dma3_clear_to(BLACK);
|
|
|
|
// Set the IRQ handler to use.
|
|
unsafe { USER_IRQ_HANDLER.write(Some(irq_handler_a32)) };
|
|
|
|
// Enable all interrupts that are set in the IE register.
|
|
unsafe { IME.write(true) };
|
|
|
|
// Request that VBlank, HBlank and VCount will generate IRQs.
|
|
const DISPLAY_SETTINGS: DisplayStatus = DisplayStatus::new()
|
|
.with_vblank_irq_enabled(true)
|
|
.with_hblank_irq_enabled(true)
|
|
.with_vcount_irq_enabled(true);
|
|
DISPSTAT.write(DISPLAY_SETTINGS);
|
|
|
|
// Start two timers with overflow IRQ generation.
|
|
start_timers();
|
|
|
|
loop {
|
|
let this_frame_keys: Keys = KEYINPUT.read().into();
|
|
|
|
// The VBlank IRQ must be enabled at minimum, or else the CPU will halt
|
|
// at the call to vblank_interrupt_wait() as the VBlank IRQ will never
|
|
// be triggered.
|
|
let mut flags = InterruptFlags::new().with_vblank(true);
|
|
|
|
// Enable interrupts based on key input.
|
|
if this_frame_keys.a() {
|
|
flags = flags.with_hblank(true);
|
|
}
|
|
if this_frame_keys.b() {
|
|
flags = flags.with_vcount(true);
|
|
}
|
|
if this_frame_keys.l() {
|
|
flags = flags.with_timer0(true);
|
|
}
|
|
if this_frame_keys.r() {
|
|
flags = flags.with_timer1(true);
|
|
}
|
|
|
|
warn!("IM = {:?}", flags);
|
|
unsafe { IE.write(flags) };
|
|
|
|
// Puts the CPU into low power mode until a VBlank IRQ is received. This
|
|
// will yield considerably better power efficiency as opposed to spin
|
|
// waiting.
|
|
unsafe { VBlankIntrWait() };
|
|
}
|
|
}
|
|
|
|
static mut PIXEL: usize = 0;
|
|
|
|
fn write_pixel(color: Color) {
|
|
unsafe {
|
|
(0x0600_0000 as *mut Color).wrapping_offset(PIXEL as isize).write_volatile(color);
|
|
PIXEL += 1;
|
|
if PIXEL == (mode3::WIDTH * mode3::HEIGHT) {
|
|
PIXEL = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[instruction_set(arm::a32)]
|
|
extern "C" fn irq_handler_a32() {
|
|
// we just use this a32 function to jump over back to t32 code.
|
|
irq_handler_t32()
|
|
}
|
|
|
|
fn irq_handler_t32() {
|
|
// disable Interrupt Master Enable to prevent an interrupt during the handler
|
|
unsafe { IME.write(false) };
|
|
|
|
// read which interrupts are pending, and "filter" the selection by which are
|
|
// supposed to be enabled.
|
|
let which_interrupts_to_handle = IRQ_PENDING.read() & IE.read();
|
|
|
|
// read the current IntrWait value. It sorta works like a running total, so
|
|
// any interrupts we process we'll enable in this value, which we write back
|
|
// at the end.
|
|
let mut intr_wait_flags = INTR_WAIT_ACKNOWLEDGE.read();
|
|
|
|
if which_interrupts_to_handle.vblank() {
|
|
vblank_handler();
|
|
intr_wait_flags.set_vblank(true);
|
|
}
|
|
if which_interrupts_to_handle.hblank() {
|
|
hblank_handler();
|
|
intr_wait_flags.set_hblank(true);
|
|
}
|
|
if which_interrupts_to_handle.vcount() {
|
|
vcount_handler();
|
|
intr_wait_flags.set_vcount(true);
|
|
}
|
|
if which_interrupts_to_handle.timer0() {
|
|
timer0_handler();
|
|
intr_wait_flags.set_timer0(true);
|
|
}
|
|
if which_interrupts_to_handle.timer1() {
|
|
timer1_handler();
|
|
intr_wait_flags.set_timer1(true);
|
|
}
|
|
|
|
// acknowledge that we did stuff.
|
|
IRQ_ACKNOWLEDGE.write(which_interrupts_to_handle);
|
|
|
|
// write out any IntrWait changes.
|
|
unsafe { INTR_WAIT_ACKNOWLEDGE.write(intr_wait_flags) };
|
|
|
|
// re-enable as we go out.
|
|
unsafe { IME.write(true) };
|
|
}
|
|
|
|
fn vblank_handler() {
|
|
write_pixel(BLUE);
|
|
}
|
|
|
|
fn hblank_handler() {
|
|
write_pixel(GREEN);
|
|
}
|
|
|
|
fn vcount_handler() {
|
|
write_pixel(RED);
|
|
}
|
|
|
|
fn timer0_handler() {
|
|
write_pixel(YELLOW);
|
|
}
|
|
|
|
fn timer1_handler() {
|
|
write_pixel(PINK);
|
|
}
|