mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-11 17:41:33 +11:00
Merge pull request #83 from agbrs/interrupt-handler
Fancy Interrupt handler with Closures
This commit is contained in:
commit
b1516a07ab
|
@ -12,6 +12,7 @@ debug = true
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=crt0.s");
|
println!("cargo:rerun-if-changed=crt0.s");
|
||||||
println!("cargo:rerun-if-changed=gba_mb.ld");
|
println!("cargo:rerun-if-changed=gba_mb.ld");
|
||||||
println!("cargo:rerun-if-changed=interrupt_simple.s");
|
|
||||||
println!("cargo:rerun-if-changed=src/sound/mixer/mixer.s");
|
println!("cargo:rerun-if-changed=src/sound/mixer/mixer.s");
|
||||||
|
println!("cargo:rerun-if-changed=interrupt_handler.s");
|
||||||
println!("cargo:rerun-if-changed=gfx/test_logo.png");
|
println!("cargo:rerun-if-changed=gfx/test_logo.png");
|
||||||
|
|
||||||
let out_file_name = "crt0.o";
|
let out_file_name = "crt0.o";
|
||||||
|
|
|
@ -18,7 +18,7 @@ b .Initialise_mb
|
||||||
swi 0x00250000
|
swi 0x00250000
|
||||||
|
|
||||||
@ Set interrupt handler
|
@ Set interrupt handler
|
||||||
ldr r0, =InterruptHandlerSimple
|
ldr r0, =InterruptHandler
|
||||||
ldr r1, =0x03007FFC
|
ldr r1, =0x03007FFC
|
||||||
str r0, [r1]
|
str r0, [r1]
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ b .Initialise_mb
|
||||||
|
|
||||||
.Initialise:
|
.Initialise:
|
||||||
@ Set interrupt handler
|
@ Set interrupt handler
|
||||||
ldr r0, =InterruptHandlerSimple
|
ldr r0, =InterruptHandler
|
||||||
ldr r1, =0x03007FFC
|
ldr r1, =0x03007FFC
|
||||||
str r0, [r1]
|
str r0, [r1]
|
||||||
|
|
||||||
|
@ -62,5 +62,5 @@ b .Initialise_mb
|
||||||
b 1b
|
b 1b
|
||||||
.pool
|
.pool
|
||||||
|
|
||||||
.include "interrupt_simple.s"
|
|
||||||
.include "src/sound/mixer/mixer.s"
|
.include "src/sound/mixer/mixer.s"
|
||||||
|
.include "interrupt_handler.s"
|
||||||
|
|
|
@ -14,7 +14,7 @@ struct Vector2D {
|
||||||
pub fn main() -> ! {
|
pub fn main() -> ! {
|
||||||
let mut gba = agb::Gba::new();
|
let mut gba = agb::Gba::new();
|
||||||
let mut bitmap = gba.display.video.bitmap3();
|
let mut bitmap = gba.display.video.bitmap3();
|
||||||
let vblank = gba.display.vblank.get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
let mut input = agb::input::ButtonController::new();
|
let mut input = agb::input::ButtonController::new();
|
||||||
let mut pos = Vector2D {
|
let mut pos = Vector2D {
|
||||||
|
@ -23,7 +23,7 @@ pub fn main() -> ! {
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
|
|
||||||
input.update();
|
input.update();
|
||||||
pos.x += input.x_tri() as i32;
|
pos.x += input.x_tri() as i32;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use agb::display;
|
||||||
pub fn main() -> ! {
|
pub fn main() -> ! {
|
||||||
let mut gba = agb::Gba::new();
|
let mut gba = agb::Gba::new();
|
||||||
let mut bitmap = gba.display.video.bitmap4();
|
let mut bitmap = gba.display.video.bitmap4();
|
||||||
let vblank = gba.display.vblank.get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
bitmap.set_palette_entry(1, 0x001F);
|
bitmap.set_palette_entry(1, 0x001F);
|
||||||
bitmap.set_palette_entry(2, 0x03E0);
|
bitmap.set_palette_entry(2, 0x03E0);
|
||||||
|
@ -30,7 +30,7 @@ pub fn main() -> ! {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
count += 1;
|
count += 1;
|
||||||
if count % 6 == 0 {
|
if count % 6 == 0 {
|
||||||
bitmap.flip_page();
|
bitmap.flip_page();
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub fn main() -> ! {
|
||||||
|
|
||||||
let mut gba = agb::Gba::new();
|
let mut gba = agb::Gba::new();
|
||||||
let mut gfx = gba.display.video.tiled0();
|
let mut gfx = gba.display.video.tiled0();
|
||||||
let vblank = gba.display.vblank.get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
let mut input = agb::input::ButtonController::new();
|
let mut input = agb::input::ButtonController::new();
|
||||||
|
|
||||||
gfx.set_sprite_palette_raw(&CHICKEN_PALETTE);
|
gfx.set_sprite_palette_raw(&CHICKEN_PALETTE);
|
||||||
|
@ -90,7 +90,7 @@ pub fn main() -> ! {
|
||||||
let terminal_velocity = (1 << 8) / 2;
|
let terminal_velocity = (1 << 8) / 2;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
frame_count += 1;
|
frame_count += 1;
|
||||||
|
|
||||||
input.update();
|
input.update();
|
||||||
|
|
|
@ -6,6 +6,9 @@ fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn __RUST_INTERRUPT_HANDLER(_: u16) {}
|
||||||
|
|
||||||
// implementation of tonc's "My first GBA demo"
|
// implementation of tonc's "My first GBA demo"
|
||||||
// https://coranac.com/tonc/text/first.htm
|
// https://coranac.com/tonc/text/first.htm
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ const I_WILL_NOT_LET_YOU_LET_ME_DOWN: &[u8] = include_bytes!("i-will-not-let-you
|
||||||
pub fn main() -> ! {
|
pub fn main() -> ! {
|
||||||
let mut gba = Gba::new();
|
let mut gba = Gba::new();
|
||||||
let mut input = ButtonController::new();
|
let mut input = ButtonController::new();
|
||||||
let vblank_provider = gba.display.vblank.get();
|
let vblank_provider = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer();
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
@ -50,7 +50,7 @@ pub fn main() -> ! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vblank_provider.wait_for_VBlank();
|
vblank_provider.wait_for_vblank();
|
||||||
mixer.vblank();
|
mixer.vblank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,18 @@ struct Vector2D {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn main() -> ! {
|
pub fn main() -> ! {
|
||||||
let mut gba = agb::Gba::new();
|
let mut gba = agb::Gba::new();
|
||||||
let mut vblank = gba.display.vblank.get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
let mut input = agb::input::ButtonController::new();
|
let mut input = agb::input::ButtonController::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
bitmap3_mode(&mut gba.display.video.bitmap3(), &mut vblank, &mut input);
|
bitmap3_mode(&mut gba.display.video.bitmap3(), &vblank, &mut input);
|
||||||
bitmap4_mode(&mut gba.display.video.bitmap4(), &mut vblank, &mut input);
|
bitmap4_mode(&mut gba.display.video.bitmap4(), &vblank, &mut input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bitmap3_mode(
|
fn bitmap3_mode(
|
||||||
bitmap: &mut display::bitmap3::Bitmap3,
|
bitmap: &mut display::bitmap3::Bitmap3,
|
||||||
vblank: &mut display::vblank::VBlank,
|
vblank: &agb::interrupt::VBlank,
|
||||||
input: &mut agb::input::ButtonController,
|
input: &mut agb::input::ButtonController,
|
||||||
) {
|
) {
|
||||||
let mut pos = Vector2D {
|
let mut pos = Vector2D {
|
||||||
|
@ -33,7 +33,7 @@ fn bitmap3_mode(
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
|
|
||||||
input.update();
|
input.update();
|
||||||
if input.is_just_pressed(agb::input::Button::B) {
|
if input.is_just_pressed(agb::input::Button::B) {
|
||||||
|
@ -51,7 +51,7 @@ fn bitmap3_mode(
|
||||||
|
|
||||||
fn bitmap4_mode(
|
fn bitmap4_mode(
|
||||||
bitmap: &mut display::bitmap4::Bitmap4,
|
bitmap: &mut display::bitmap4::Bitmap4,
|
||||||
vblank: &mut display::vblank::VBlank,
|
vblank: &agb::interrupt::VBlank,
|
||||||
input: &mut agb::input::ButtonController,
|
input: &mut agb::input::ButtonController,
|
||||||
) {
|
) {
|
||||||
bitmap.set_palette_entry(1, 0x001F);
|
bitmap.set_palette_entry(1, 0x001F);
|
||||||
|
@ -72,7 +72,7 @@ fn bitmap4_mode(
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
|
|
||||||
input.update();
|
input.update();
|
||||||
if input.is_just_pressed(agb::input::Button::B) {
|
if input.is_just_pressed(agb::input::Button::B) {
|
||||||
|
|
|
@ -4,16 +4,11 @@
|
||||||
extern crate agb;
|
extern crate agb;
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn main() -> ! {
|
pub fn main() -> ! {
|
||||||
let mut gba = agb::Gba::new();
|
let count = agb::interrupt::Mutex::new(0);
|
||||||
|
agb::add_interrupt_handler!(agb::interrupt::Interrupt::VBlank, |key| {
|
||||||
let vblank = gba.display.vblank.get();
|
let mut count = count.lock_with_key(&key);
|
||||||
|
agb::println!("Hello, world, frame = {}", *count);
|
||||||
let mut count = 0;
|
*count += 1;
|
||||||
loop {
|
});
|
||||||
vblank.wait_for_VBlank();
|
loop {}
|
||||||
|
|
||||||
agb::println!("Hello, world, frame = {}", count);
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
50
agb/examples/wave.rs
Normal file
50
agb/examples/wave.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate agb;
|
||||||
|
|
||||||
|
use agb::{
|
||||||
|
display::example_logo,
|
||||||
|
interrupt::{Interrupt, Mutex},
|
||||||
|
number::FixedNum,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BackCosines {
|
||||||
|
cosines: [u16; 32],
|
||||||
|
row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn main() -> ! {
|
||||||
|
let mut gba = agb::Gba::new();
|
||||||
|
let mut gfx = gba.display.video.tiled0();
|
||||||
|
|
||||||
|
example_logo::display_logo(&mut gfx);
|
||||||
|
|
||||||
|
let mut time = 0;
|
||||||
|
let cosines = [0_u16; 32];
|
||||||
|
|
||||||
|
let back = Mutex::new(BackCosines { cosines, row: 0 });
|
||||||
|
|
||||||
|
agb::add_interrupt_handler!(Interrupt::HBlank, |_| {
|
||||||
|
let mut backc = back.lock();
|
||||||
|
let deflection = backc.cosines[backc.row % 32];
|
||||||
|
unsafe { ((0x0400_0010) as *mut u16).write_volatile(deflection) }
|
||||||
|
backc.row += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
let mut backc = back.lock();
|
||||||
|
backc.row = 0;
|
||||||
|
time += 1;
|
||||||
|
for (r, a) in backc.cosines.iter_mut().enumerate() {
|
||||||
|
let n: FixedNum<8> = (FixedNum::new(r as i32) / 32 + FixedNum::new(time) / 128).cos()
|
||||||
|
* (256 * 4 - 1)
|
||||||
|
/ 256;
|
||||||
|
*a = (n.trunc() % (32 * 8)) as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||||
OUTPUT_ARCH(arm)
|
OUTPUT_ARCH(arm)
|
||||||
|
|
||||||
ENTRY(__start)
|
ENTRY(__start)
|
||||||
|
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||||
|
|
||||||
MEMORY {
|
MEMORY {
|
||||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||||
|
|
|
@ -2,6 +2,7 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||||
OUTPUT_ARCH(arm)
|
OUTPUT_ARCH(arm)
|
||||||
|
|
||||||
ENTRY(__start)
|
ENTRY(__start)
|
||||||
|
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||||
|
|
||||||
MEMORY {
|
MEMORY {
|
||||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||||
|
|
45
agb/interrupt_handler.s
Normal file
45
agb/interrupt_handler.s
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
@ An interrupt handler that simply acknowledges all interrupts
|
||||||
|
.arm
|
||||||
|
.global InterruptHandler
|
||||||
|
.section .iwram, "ax"
|
||||||
|
.align
|
||||||
|
InterruptHandler:
|
||||||
|
mov r2, #0x04000000 @ interrupt enable register location
|
||||||
|
add r2, #0x200
|
||||||
|
|
||||||
|
mov r1, #0
|
||||||
|
strh r1, [r2, #8]
|
||||||
|
|
||||||
|
ldrh r1, [r2] @ load 16 bit interrupt enable to r1
|
||||||
|
ldrh r3, [r2, #2] @ load 16 bit interrupt request to r3
|
||||||
|
and r0, r1, r3 @ interrupts both enabled and requested
|
||||||
|
|
||||||
|
@ change to system mode
|
||||||
|
mrs r1, cpsr
|
||||||
|
orr r1, r1, #0xD
|
||||||
|
msr cpsr_c, r1
|
||||||
|
|
||||||
|
@ call the rust interrupt handler with r0 set to the triggered interrupts
|
||||||
|
ldr r1, =__RUST_INTERRUPT_HANDLER
|
||||||
|
push {lr, r2}
|
||||||
|
mov lr, pc
|
||||||
|
bx r1
|
||||||
|
pop {lr, r2}
|
||||||
|
|
||||||
|
@ change back to interrupt mode
|
||||||
|
mrs r1, cpsr
|
||||||
|
bic r1, r1, #0xD
|
||||||
|
msr cpsr_c, r1
|
||||||
|
|
||||||
|
mov r1, #1
|
||||||
|
strh r1, [r2, #8]
|
||||||
|
|
||||||
|
strh r0, [r2, #2] @ store to interrupt request
|
||||||
|
|
||||||
|
ldr r2, =0x03007FF8 @ load bios interrupt request location
|
||||||
|
ldrh r1, [r2] @ load bios interrupt requests
|
||||||
|
orr r1, r1, r0 @ or with enabled and requested interrupts
|
||||||
|
strh r1, [r2] @ acknowlege bios requests
|
||||||
|
|
||||||
|
bx lr @ return to bios
|
||||||
|
.pool
|
|
@ -1,18 +0,0 @@
|
||||||
@ An interrupt handler that simply acknowledges all interrupts
|
|
||||||
.arm
|
|
||||||
.global InterruptHandlerSimple
|
|
||||||
.align
|
|
||||||
InterruptHandlerSimple:
|
|
||||||
ldr r2, =0x04000200 @ interrupt enable register location
|
|
||||||
ldrh r1, [r2] @ load 16 bit interrupt enable to r1
|
|
||||||
ldrh r3, [r2, #2] @ load 16 bit interrupt request to r3
|
|
||||||
and r0, r1, r3 @ interrupts both enabled and requested
|
|
||||||
strh r0, [r2, #2] @ store to interrupt request
|
|
||||||
|
|
||||||
ldr r2, =0x03007FF8 @ load bios interrupt request location
|
|
||||||
ldrh r1, [r2] @ load bios interrupt requests
|
|
||||||
orr r1, r1, r0 @ or with enabled and requested interrupts
|
|
||||||
strh r1, [r2] @ acknowlege bios requests
|
|
||||||
|
|
||||||
bx lr @ return to bios
|
|
||||||
.pool
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use crate::memory_mapped::MemoryMapped;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use vblank::VBlankGiver;
|
|
||||||
use video::Video;
|
use video::Video;
|
||||||
|
|
||||||
use self::object::ObjectControl;
|
use self::object::ObjectControl;
|
||||||
|
@ -20,13 +19,11 @@ pub mod palette16;
|
||||||
pub mod tile_data;
|
pub mod tile_data;
|
||||||
/// Graphics mode 0. Four regular backgrounds.
|
/// Graphics mode 0. Four regular backgrounds.
|
||||||
pub mod tiled0;
|
pub mod tiled0;
|
||||||
/// Syscall for waiting for vblank.
|
|
||||||
pub mod vblank;
|
|
||||||
/// Giving out graphics mode.
|
/// Giving out graphics mode.
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
|
||||||
const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
|
const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
|
||||||
const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
|
pub(crate) const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
|
||||||
const VCOUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0006) };
|
const VCOUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0006) };
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
@ -65,7 +62,6 @@ enum DisplayMode {
|
||||||
/// Manages distribution of display modes, obtained from the gba struct
|
/// Manages distribution of display modes, obtained from the gba struct
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
pub video: Video,
|
pub video: Video,
|
||||||
pub vblank: VBlankGiver,
|
|
||||||
pub object: ObjectDistribution,
|
pub object: ObjectDistribution,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +78,6 @@ impl Display {
|
||||||
pub(crate) const unsafe fn new() -> Self {
|
pub(crate) const unsafe fn new() -> Self {
|
||||||
Display {
|
Display {
|
||||||
video: Video {},
|
video: Video {},
|
||||||
vblank: VBlankGiver {},
|
|
||||||
object: ObjectDistribution {},
|
object: ObjectDistribution {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +104,7 @@ unsafe fn set_graphics_settings(settings: GraphicsSettings) {
|
||||||
/// Waits until vblank using a busy wait loop, this should almost never be used.
|
/// Waits until vblank using a busy wait loop, this should almost never be used.
|
||||||
/// I only say almost because whilst I don't believe there to be a reason to use
|
/// I only say almost because whilst I don't believe there to be a reason to use
|
||||||
/// this I can't rule it out.
|
/// this I can't rule it out.
|
||||||
pub fn busy_wait_for_VBlank() {
|
pub fn busy_wait_for_vblank() {
|
||||||
while VCOUNT.get() >= 160 {}
|
while VCOUNT.get() >= 160 {}
|
||||||
while VCOUNT.get() < 160 {}
|
while VCOUNT.get() < 160 {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,10 +118,10 @@ impl Background {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_x(&self, x: u16) {
|
fn set_x(&self, x: u16) {
|
||||||
unsafe { *((0x0400_0010 + 4 * self.background as usize) as *mut u16) = x }
|
unsafe { ((0x0400_0010 + 4 * self.background as usize) as *mut u16).write_volatile(x) }
|
||||||
}
|
}
|
||||||
fn set_y(&self, y: u16) {
|
fn set_y(&self, y: u16) {
|
||||||
unsafe { *((0x0400_0012 + 4 * self.background as usize) as *mut u16) = y }
|
unsafe { ((0x0400_0012 + 4 * self.background as usize) as *mut u16).write_volatile(y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Forces the portion of the map in current view to be copied to the map
|
/// Forces the portion of the map in current view to be copied to the map
|
||||||
|
@ -189,6 +189,11 @@ impl Background {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_offset(&mut self, position: Vector2D<u32>) {
|
||||||
|
self.set_x(position.x as u16);
|
||||||
|
self.set_y(position.y as u16);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the position of the map to be shown on screen. This automatically
|
/// Sets the position of the map to be shown on screen. This automatically
|
||||||
/// manages copying the correct portion to the map block and moving the map
|
/// manages copying the correct portion to the map block and moving the map
|
||||||
/// registers.
|
/// registers.
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
use super::DISPLAY_STATUS;
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct VBlankGiver {}
|
|
||||||
|
|
||||||
impl VBlankGiver {
|
|
||||||
/// Gets a vblank handle where only one can be obtained at a time
|
|
||||||
pub fn get(&mut self) -> VBlank {
|
|
||||||
unsafe { VBlank::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Once obtained, this guarentees that interrupts are enabled and set up to
|
|
||||||
/// allow for waiting for vblank
|
|
||||||
pub struct VBlank {}
|
|
||||||
|
|
||||||
impl VBlank {
|
|
||||||
unsafe fn new() -> Self {
|
|
||||||
crate::interrupt::enable_interrupts();
|
|
||||||
crate::interrupt::enable(crate::interrupt::Interrupt::VBlank);
|
|
||||||
enable_VBlank_interrupt();
|
|
||||||
VBlank {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
/// Waits for VBlank using interrupts. This is the preferred method for
|
|
||||||
/// waiting until the next frame.
|
|
||||||
pub fn wait_for_VBlank(&self) {
|
|
||||||
crate::syscall::wait_for_VBlank();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VBlank {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
disable_VBlank_interrupt();
|
|
||||||
crate::interrupt::disable(crate::interrupt::Interrupt::VBlank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
unsafe fn enable_VBlank_interrupt() {
|
|
||||||
let status = DISPLAY_STATUS.get() | (1 << 3);
|
|
||||||
DISPLAY_STATUS.set(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
unsafe fn disable_VBlank_interrupt() {
|
|
||||||
let status = DISPLAY_STATUS.get() & !(1 << 3);
|
|
||||||
DISPLAY_STATUS.set(status);
|
|
||||||
}
|
|
|
@ -1,6 +1,13 @@
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use core::{
|
||||||
|
cell::{Cell, UnsafeCell},
|
||||||
|
marker::{PhantomData, PhantomPinned},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
pin::Pin,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
use crate::{display::DISPLAY_STATUS, memory_mapped::MemoryMapped};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
VBlank = 0,
|
VBlank = 0,
|
||||||
HBlank = 1,
|
HBlank = 1,
|
||||||
|
@ -18,24 +25,52 @@ pub enum Interrupt {
|
||||||
Gamepak = 13,
|
Gamepak = 13,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Interrupt {
|
||||||
|
fn enable(self) {
|
||||||
|
let _interrupt_token = temporary_interrupt_disable();
|
||||||
|
self.other_things_to_enable_interrupt();
|
||||||
|
let interrupt = self as usize;
|
||||||
|
let enabled = ENABLED_INTERRUPTS.get() | (1 << (interrupt as u16));
|
||||||
|
ENABLED_INTERRUPTS.set(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable(self) {
|
||||||
|
let _interrupt_token = temporary_interrupt_disable();
|
||||||
|
self.other_things_to_disable_interrupt();
|
||||||
|
let interrupt = self as usize;
|
||||||
|
let enabled = ENABLED_INTERRUPTS.get() & !(1 << (interrupt as u16));
|
||||||
|
ENABLED_INTERRUPTS.set(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn other_things_to_enable_interrupt(self) {
|
||||||
|
match self {
|
||||||
|
Interrupt::VBlank => {
|
||||||
|
DISPLAY_STATUS.set_bits(1, 1, 3);
|
||||||
|
}
|
||||||
|
Interrupt::HBlank => {
|
||||||
|
DISPLAY_STATUS.set_bits(1, 1, 4);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn other_things_to_disable_interrupt(self) {
|
||||||
|
match self {
|
||||||
|
Interrupt::VBlank => {
|
||||||
|
DISPLAY_STATUS.set_bits(0, 1, 3);
|
||||||
|
}
|
||||||
|
Interrupt::HBlank => {
|
||||||
|
DISPLAY_STATUS.set_bits(0, 1, 4);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ENABLED_INTERRUPTS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x04000200) };
|
const ENABLED_INTERRUPTS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x04000200) };
|
||||||
const INTERRUPTS_ENABLED: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x04000208) };
|
const INTERRUPTS_ENABLED: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x04000208) };
|
||||||
|
|
||||||
pub fn enable(interrupt: Interrupt) {
|
struct Disable {}
|
||||||
let _interrupt_token = temporary_interrupt_disable();
|
|
||||||
let interrupt = interrupt as usize;
|
|
||||||
let enabled = ENABLED_INTERRUPTS.get() | (1 << (interrupt as u16));
|
|
||||||
ENABLED_INTERRUPTS.set(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable(interrupt: Interrupt) {
|
|
||||||
let _interrupt_token = temporary_interrupt_disable();
|
|
||||||
let interrupt = interrupt as usize;
|
|
||||||
let enabled = ENABLED_INTERRUPTS.get() & !(1 << (interrupt as u16));
|
|
||||||
ENABLED_INTERRUPTS.set(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Disable {}
|
|
||||||
|
|
||||||
impl Drop for Disable {
|
impl Drop for Disable {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
@ -43,15 +78,334 @@ impl Drop for Disable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn temporary_interrupt_disable() -> Disable {
|
fn temporary_interrupt_disable() -> Disable {
|
||||||
disable_interrupts();
|
disable_interrupts();
|
||||||
Disable {}
|
Disable {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_interrupts() {
|
fn enable_interrupts() {
|
||||||
INTERRUPTS_ENABLED.set(1);
|
INTERRUPTS_ENABLED.set(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn disable_interrupts() {
|
fn disable_interrupts() {
|
||||||
INTERRUPTS_ENABLED.set(0);
|
INTERRUPTS_ENABLED.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InterruptRoot {
|
||||||
|
next: Cell<*const InterruptClosure>,
|
||||||
|
count: Cell<i32>,
|
||||||
|
interrupt: Interrupt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptRoot {
|
||||||
|
const fn new(interrupt: Interrupt) -> Self {
|
||||||
|
InterruptRoot {
|
||||||
|
next: Cell::new(core::ptr::null()),
|
||||||
|
count: Cell::new(0),
|
||||||
|
interrupt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce(&self) {
|
||||||
|
let new_count = self.count.get() - 1;
|
||||||
|
if new_count == 0 {
|
||||||
|
self.interrupt.disable();
|
||||||
|
}
|
||||||
|
self.count.set(new_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&self) {
|
||||||
|
let count = self.count.get();
|
||||||
|
if count == 0 {
|
||||||
|
self.interrupt.enable();
|
||||||
|
}
|
||||||
|
self.count.set(count + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut INTERRUPT_TABLE: [InterruptRoot; 14] = [
|
||||||
|
InterruptRoot::new(Interrupt::VBlank),
|
||||||
|
InterruptRoot::new(Interrupt::HBlank),
|
||||||
|
InterruptRoot::new(Interrupt::VCounter),
|
||||||
|
InterruptRoot::new(Interrupt::Timer0),
|
||||||
|
InterruptRoot::new(Interrupt::Timer1),
|
||||||
|
InterruptRoot::new(Interrupt::Timer2),
|
||||||
|
InterruptRoot::new(Interrupt::Timer3),
|
||||||
|
InterruptRoot::new(Interrupt::Serial),
|
||||||
|
InterruptRoot::new(Interrupt::Dma0),
|
||||||
|
InterruptRoot::new(Interrupt::Dma1),
|
||||||
|
InterruptRoot::new(Interrupt::Dma2),
|
||||||
|
InterruptRoot::new(Interrupt::Dma3),
|
||||||
|
InterruptRoot::new(Interrupt::Keypad),
|
||||||
|
InterruptRoot::new(Interrupt::Gamepak),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn __RUST_INTERRUPT_HANDLER(interrupt: u16) -> u16 {
|
||||||
|
for (i, root) in unsafe { INTERRUPT_TABLE.iter().enumerate() } {
|
||||||
|
if (1 << i) & interrupt != 0 {
|
||||||
|
root.trigger_interrupts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterruptClosureBounded<'a> {
|
||||||
|
c: InterruptClosure,
|
||||||
|
_phantom: PhantomData<&'a ()>,
|
||||||
|
_unpin: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterruptClosure {
|
||||||
|
closure: *const (dyn Fn(Key)),
|
||||||
|
next: Cell<*const InterruptClosure>,
|
||||||
|
root: *const InterruptRoot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptRoot {
|
||||||
|
fn trigger_interrupts(&self) {
|
||||||
|
let mut c = self.next.get();
|
||||||
|
while !c.is_null() {
|
||||||
|
let closure_ptr = unsafe { &*c }.closure;
|
||||||
|
let closure_ref = unsafe { &*closure_ptr };
|
||||||
|
closure_ref(Key());
|
||||||
|
c = unsafe { &*c }.next.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InterruptClosure {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let root = unsafe { &*self.root };
|
||||||
|
root.reduce();
|
||||||
|
let mut c = root.next.get();
|
||||||
|
let own_pointer = self as *const _;
|
||||||
|
if c == own_pointer {
|
||||||
|
unsafe { &*self.root }.next.set(self.next.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let p = unsafe { &*c }.next.get();
|
||||||
|
if p == own_pointer {
|
||||||
|
unsafe { &*c }.next.set(self.next.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot {
|
||||||
|
unsafe { &INTERRUPT_TABLE[interrupt as usize] }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_interrupt_handle_root<'a>(
|
||||||
|
f: &'a dyn Fn(Key),
|
||||||
|
root: &InterruptRoot,
|
||||||
|
) -> InterruptClosureBounded<'a> {
|
||||||
|
InterruptClosureBounded {
|
||||||
|
c: InterruptClosure {
|
||||||
|
closure: unsafe { core::mem::transmute(f as *const _) },
|
||||||
|
next: Cell::new(core::ptr::null()),
|
||||||
|
root: root as *const _,
|
||||||
|
},
|
||||||
|
_phantom: PhantomData,
|
||||||
|
_unpin: PhantomPinned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [add_interrupt_handler!] macro should be used instead of this function.
|
||||||
|
/// Creates an interrupt handler from a closure.
|
||||||
|
pub fn get_interrupt_handle(
|
||||||
|
f: &(dyn Fn(Key) + Send + Sync),
|
||||||
|
interrupt: Interrupt,
|
||||||
|
) -> InterruptClosureBounded {
|
||||||
|
let root = interrupt_to_root(interrupt);
|
||||||
|
|
||||||
|
get_interrupt_handle_root(f, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [add_interrupt_handler!] macro should be used instead of this.
|
||||||
|
/// Adds an interrupt handler to the interrupt table such that when that
|
||||||
|
/// interrupt is triggered the closure is called.
|
||||||
|
pub fn add_interrupt<'a>(interrupt: Pin<&'a InterruptClosureBounded<'a>>) {
|
||||||
|
let root = unsafe { &*interrupt.c.root };
|
||||||
|
root.add();
|
||||||
|
let mut c = root.next.get();
|
||||||
|
if c.is_null() {
|
||||||
|
root.next.set((&interrupt.c) as *const _);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let p = unsafe { &*c }.next.get();
|
||||||
|
if p.is_null() {
|
||||||
|
unsafe { &*c }.next.set((&interrupt.c) as *const _);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Creates a new interrupt handler in the current scope, when this scope drops
|
||||||
|
/// the interrupt handler is removed. Note that this returns nothing, but some
|
||||||
|
/// stack space is used. The interrupt handler is of the form `Fn(Key) + Send +
|
||||||
|
/// Sync` where Key can be used to unlock a mutex without checking whether
|
||||||
|
/// interrupts need to be disabled, as during an interrupt interrupts are
|
||||||
|
/// disabled.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
/// ```
|
||||||
|
/// add_interrupt_handler!(Interrupt::VBlank, |key| agb::println!("hello world!"));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
macro_rules! add_interrupt_handler {
|
||||||
|
($interrupt: expr, $handler: expr) => {
|
||||||
|
let a = $handler;
|
||||||
|
let a = $crate::interrupt::get_interrupt_handle(&a, $interrupt);
|
||||||
|
let a = unsafe { core::pin::Pin::new_unchecked(&a) };
|
||||||
|
$crate::interrupt::add_interrupt(a);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_vblank_interrupt_handler(_gba: &mut crate::Gba) {
|
||||||
|
{
|
||||||
|
let counter = Mutex::new(0);
|
||||||
|
let counter_2 = Mutex::new(0);
|
||||||
|
add_interrupt_handler!(Interrupt::VBlank, |key| *counter.lock_with_key(&key) += 1);
|
||||||
|
add_interrupt_handler!(Interrupt::VBlank, |_| *counter_2.lock() += 1);
|
||||||
|
|
||||||
|
let vblank = VBlank::get();
|
||||||
|
|
||||||
|
while *counter.lock() < 100 || *counter_2.lock() < 100 {
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
interrupt_to_root(Interrupt::VBlank).next.get(),
|
||||||
|
core::ptr::null(),
|
||||||
|
"expected the interrupt table for vblank to be empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_interrupt_table_length(_gba: &mut crate::Gba) {
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { INTERRUPT_TABLE.len() },
|
||||||
|
Interrupt::Gamepak as usize + 1,
|
||||||
|
"interrupt table should be able to store gamepak interrupt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
enum MutexState {
|
||||||
|
Unlocked,
|
||||||
|
Locked(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mutex<T> {
|
||||||
|
internal: UnsafeCell<T>,
|
||||||
|
state: UnsafeCell<MutexState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Key();
|
||||||
|
|
||||||
|
unsafe impl<T> Send for Mutex<T> {}
|
||||||
|
unsafe impl<T> Sync for Mutex<T> {}
|
||||||
|
|
||||||
|
impl<T> Mutex<T> {
|
||||||
|
pub fn lock(&self) -> MutexRef<T> {
|
||||||
|
let state = INTERRUPTS_ENABLED.get();
|
||||||
|
INTERRUPTS_ENABLED.set(0);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { *self.state.get() },
|
||||||
|
MutexState::Unlocked,
|
||||||
|
"mutex must be unlocked to be able to lock it"
|
||||||
|
);
|
||||||
|
unsafe { *self.state.get() = MutexState::Locked(state != 0) };
|
||||||
|
MutexRef {
|
||||||
|
internal: &self.internal,
|
||||||
|
state: &self.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_with_key(&self, _key: &Key) -> MutexRef<T> {
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { *self.state.get() },
|
||||||
|
MutexState::Unlocked,
|
||||||
|
"mutex must be unlocked to be able to lock it"
|
||||||
|
);
|
||||||
|
unsafe { *self.state.get() = MutexState::Locked(false) };
|
||||||
|
MutexRef {
|
||||||
|
internal: &self.internal,
|
||||||
|
state: &self.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(val: T) -> Self {
|
||||||
|
Mutex {
|
||||||
|
internal: UnsafeCell::new(val),
|
||||||
|
state: UnsafeCell::new(MutexState::Unlocked),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MutexRef<'a, T> {
|
||||||
|
internal: &'a UnsafeCell<T>,
|
||||||
|
state: &'a UnsafeCell<MutexState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Drop for MutexRef<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let state = &mut *self.state.get();
|
||||||
|
let prev_state = *state;
|
||||||
|
*state = MutexState::Unlocked;
|
||||||
|
match prev_state {
|
||||||
|
MutexState::Locked(b) => INTERRUPTS_ENABLED.set(b as u16),
|
||||||
|
MutexState::Unlocked => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Deref for MutexRef<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.internal.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> DerefMut for MutexRef<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { &mut *self.internal.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct VBlank {}
|
||||||
|
|
||||||
|
impl VBlank {
|
||||||
|
/// Handles setting up everything reqired to be able to use the wait for
|
||||||
|
/// interrupt syscall.
|
||||||
|
pub fn get() -> Self {
|
||||||
|
interrupt_to_root(Interrupt::VBlank).add();
|
||||||
|
VBlank {}
|
||||||
|
}
|
||||||
|
/// Pauses CPU until vblank interrupt is triggered where code execution is
|
||||||
|
/// resumed.
|
||||||
|
pub fn wait_for_vblank(&self) {
|
||||||
|
crate::syscall::wait_for_vblank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VBlank {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
interrupt_to_root(Interrupt::VBlank).reduce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub mod sound;
|
||||||
pub use agb_image_converter::include_gfx;
|
pub use agb_image_converter::include_gfx;
|
||||||
|
|
||||||
mod bitarray;
|
mod bitarray;
|
||||||
mod interrupt;
|
pub mod interrupt;
|
||||||
mod memory_mapped;
|
mod memory_mapped;
|
||||||
/// Implements logging to the mgba emulator.
|
/// Implements logging to the mgba emulator.
|
||||||
pub mod mgba;
|
pub mod mgba;
|
||||||
|
@ -142,15 +142,15 @@ pub extern "C" fn main() -> ! {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn assert_image_output(image: &str) {
|
fn assert_image_output(image: &str) {
|
||||||
display::busy_wait_for_VBlank();
|
display::busy_wait_for_vblank();
|
||||||
display::busy_wait_for_VBlank();
|
display::busy_wait_for_vblank();
|
||||||
let mut mgba = crate::mgba::Mgba::new().unwrap();
|
let mut mgba = crate::mgba::Mgba::new().unwrap();
|
||||||
mgba.print(
|
mgba.print(
|
||||||
format_args!("image:{}", image),
|
format_args!("image:{}", image),
|
||||||
crate::mgba::DebugLevel::Info,
|
crate::mgba::DebugLevel::Info,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
display::busy_wait_for_VBlank();
|
display::busy_wait_for_vblank();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -163,14 +163,14 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case]
|
#[test_case]
|
||||||
fn wait_30_frames(gba: &mut Gba) {
|
fn wait_30_frames(_gba: &mut Gba) {
|
||||||
let vblank = gba.display.vblank.get();
|
let vblank = crate::interrupt::VBlank::get();
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
loop {
|
loop {
|
||||||
if counter > 30 {
|
if counter > 30 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
vblank.wait_for_VBlank();
|
vblank.wait_for_vblank();
|
||||||
counter += 1
|
counter += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,9 @@ pub fn wait_for_interrupt() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
/// The vblank interrupt handler [VBlank][crate::interrupt::VBlank] should be
|
||||||
pub fn wait_for_VBlank() {
|
/// used instead of calling this function directly.
|
||||||
|
pub fn wait_for_vblank() {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(
|
asm!(
|
||||||
"swi 0x05",
|
"swi 0x05",
|
||||||
|
|
|
@ -2,6 +2,7 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||||
OUTPUT_ARCH(arm)
|
OUTPUT_ARCH(arm)
|
||||||
|
|
||||||
ENTRY(__start)
|
ENTRY(__start)
|
||||||
|
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||||
|
|
||||||
MEMORY {
|
MEMORY {
|
||||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||||
|
|
|
@ -2,6 +2,7 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||||
OUTPUT_ARCH(arm)
|
OUTPUT_ARCH(arm)
|
||||||
|
|
||||||
ENTRY(__start)
|
ENTRY(__start)
|
||||||
|
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||||
|
|
||||||
MEMORY {
|
MEMORY {
|
||||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||||
|
|
Loading…
Reference in a new issue