summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Hader <[email protected]>2026-08-23 20:25:36 -0500
committerDaniel Hader <[email protected]>2026-08-23 20:25:36 -0500
commit0532e04a9e273f2a9033a28cdbce1030a91c5bb4 (patch)
tree6e07d6bffeb834e01ff4cc18ec620ee265da8505
initial commit working tile demo
-rw-r--r--.gitignore5
-rw-r--r--Makefile160
-rw-r--r--source/input.hpp45
-rw-r--r--source/main.cpp57
-rw-r--r--source/types.hpp18
-rw-r--r--source/util.hpp191
6 files changed, 476 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3de8727
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.elf
+*.gba
+*.sav
+build/
+#*# \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f7abf52
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,160 @@
+#---------------------------------------------------------------------------------
+.SUFFIXES:
+#---------------------------------------------------------------------------------
+
+ifeq ($(strip $(DEVKITARM)),)
+$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
+endif
+
+include $(DEVKITARM)/gba_rules
+
+#---------------------------------------------------------------------------------
+# TARGET is the name of the output
+# BUILD is the directory where object files & intermediate files will be placed
+# SOURCES is a list of directories containing source code
+# INCLUDES is a list of directories containing extra header files
+# DATA is a list of directories containing binary data
+# GRAPHICS is a list of directories containing files to be processed by grit
+#
+# All directories are specified relative to the project directory where
+# the makefile is found
+#
+#---------------------------------------------------------------------------------
+TARGET := $(notdir $(CURDIR))
+BUILD := build
+SOURCES := source
+INCLUDES := include
+DATA :=
+MUSIC :=
+
+#---------------------------------------------------------------------------------
+# options for code generation
+#---------------------------------------------------------------------------------
+ARCH := -mthumb -mthumb-interwork
+CFLAGS := -g -Wall -O2\
+ -mcpu=arm7tdmi -mtune=arm7tdmi\
+ $(ARCH)
+
+CFLAGS += $(INCLUDE)
+CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++20
+
+ASFLAGS := -g $(ARCH)
+LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map)
+
+#---------------------------------------------------------------------------------
+# any extra libraries we wish to link with the project
+#---------------------------------------------------------------------------------
+LIBS := #-lmm -lgba
+
+#---------------------------------------------------------------------------------
+# list of directories containing libraries, this must be the top level containing
+# include and lib
+#---------------------------------------------------------------------------------
+LIBDIRS := $(LIBGBA)
+
+#---------------------------------------------------------------------------------
+# no real need to edit anything past this point unless you need to add additional
+# rules for different file extensions
+#---------------------------------------------------------------------------------
+
+
+ifneq ($(BUILD),$(notdir $(CURDIR)))
+#---------------------------------------------------------------------------------
+
+export OUTPUT := $(CURDIR)/$(TARGET)
+
+export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
+ $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
+ $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))
+
+export DEPSDIR := $(CURDIR)/$(BUILD)
+
+CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
+CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
+SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
+BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
+
+ifneq ($(strip $(MUSIC)),)
+ export AUDIOFILES := $(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir))
+ BINFILES += soundbank.bin
+endif
+
+#---------------------------------------------------------------------------------
+# use CXX for linking C++ projects, CC for standard C
+#---------------------------------------------------------------------------------
+ifeq ($(strip $(CPPFILES)),)
+#---------------------------------------------------------------------------------
+ export LD := $(CC)
+#---------------------------------------------------------------------------------
+else
+#---------------------------------------------------------------------------------
+ export LD := $(CXX)
+#---------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------
+
+export OFILES_BIN := $(addsuffix .o,$(BINFILES))
+
+export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
+
+export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)
+
+export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))
+
+export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
+ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
+ -I$(CURDIR)/$(BUILD)
+
+export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
+
+.PHONY: $(BUILD) clean
+
+#---------------------------------------------------------------------------------
+$(BUILD):
+ @[ -d $@ ] || mkdir -p $@
+ @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
+
+#---------------------------------------------------------------------------------
+clean:
+ @echo clean ...
+ @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba
+
+
+#---------------------------------------------------------------------------------
+else
+
+#---------------------------------------------------------------------------------
+# main targets
+#---------------------------------------------------------------------------------
+
+$(OUTPUT).gba : $(OUTPUT).elf
+
+$(OUTPUT).elf : $(OFILES)
+
+$(OFILES_SOURCES) : $(HFILES)
+
+#---------------------------------------------------------------------------------
+# The bin2o rule should be copied and modified
+# for each extension used in the data directories
+#---------------------------------------------------------------------------------
+
+#---------------------------------------------------------------------------------
+# rule to build soundbank from music files
+#---------------------------------------------------------------------------------
+soundbank.bin soundbank.h : $(AUDIOFILES)
+#---------------------------------------------------------------------------------
+ @mmutil $^ -osoundbank.bin -hsoundbank.h
+
+#---------------------------------------------------------------------------------
+# This rule links in binary data with the .bin extension
+#---------------------------------------------------------------------------------
+%.bin.o %_bin.h : %.bin
+#---------------------------------------------------------------------------------
+ @echo $(notdir $<)
+ @$(bin2o)
+
+
+-include $(DEPSDIR)/*.d
+#---------------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------------
diff --git a/source/input.hpp b/source/input.hpp
new file mode 100644
index 0000000..2a2a3a2
--- /dev/null
+++ b/source/input.hpp
@@ -0,0 +1,45 @@
+#include "types.hpp"
+
+
+
+typedef u16 KeyFlags;
+
+enum Key : KeyFlags {
+ A = 0x0001,
+ B = 0x0002,
+ Select = 0x0004,
+ Start = 0x0008,
+ Right = 0x0010,
+ Left = 0x0020,
+ Up = 0x0040,
+ Down = 0x0080,
+ R = 0x0100,
+ L = 0x0200,
+};
+
+constexpr KeyFlags KEY_MASK = 0x03FF;
+
+volatile KeyFlags* const REG_KEYINPUT = reinterpret_cast<volatile KeyFlags*>(0x04000130);
+
+static inline KeyFlags &keyflags_curr() {
+ static KeyFlags keybits_curr = 0;
+ return keybits_curr;
+}
+
+static inline KeyFlags &keyflags_prev() {
+ static KeyFlags keybits_prev = 0;
+ return keybits_prev;
+}
+
+static inline void keyflags_poll() {
+ KeyFlags &prev = keyflags_prev();
+ KeyFlags &curr = keyflags_curr();
+
+ prev = curr;
+ curr = ~*REG_KEYINPUT & KEY_MASK;
+}
+
+static inline bool key_is_down(Key key) { return keyflags_curr() & key; }
+static inline bool key_is_up(Key key) { return ~keyflags_curr() & key; }
+static inline bool key_was_down(Key key) { return keyflags_prev() & key; }
+static inline bool key_was_up(Key key) { return ~keyflags_prev() & key; }
diff --git a/source/main.cpp b/source/main.cpp
new file mode 100644
index 0000000..ec6be22
--- /dev/null
+++ b/source/main.cpp
@@ -0,0 +1,57 @@
+#include "types.hpp"
+#include "util.hpp"
+#include "input.hpp"
+
+ScreenEntry *bg0_map = screenblock_mem[28];
+
+void bad_vsync() {
+ while (*REG_VCOUNT >= 160);
+ while (*REG_VCOUNT < 160);
+}
+
+int main() {
+ auto bg_flags = BGControlFlags()
+ .set_charblock_base(0)
+ .set_screenblock_base(0)
+ .set_size(BGControlSize::Regular_64x64);
+
+ set_bg_control(0, bg_flags);
+ set_bg_offsets(0, 0, 0);
+
+ const Tile4 tiles[2] = {
+ {{0x11111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x00000001}},
+ {{0x00000000, 0x00100100, 0x01100110, 0x00011000, 0x00011000, 0x01100110, 0x00100100, 0x00000000}},
+ };
+
+ set_tile(0, 0, tiles[0]);
+ set_tile(0, 1, tiles[1]);
+
+ set_bg_palette(0, 1, Color::from_rgb(31, 0, 0));
+ set_bg_palette(1, 1, Color::from_rgb(31, 0, 0));
+ set_bg_palette(2, 1, Color::from_rgb(31, 0, 0));
+ set_bg_palette(3, 1, Color::from_rgb(31, 0, 0));
+
+ ScreenEntry *screen_entry_ptr = bg0_map;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 32*32; j++) {
+ *screen_entry_ptr++ = ScreenEntry().set_palette_bank(i);
+ }
+ }
+
+ set_display_control(Mode0, BG0);
+
+ s16 x = 0, y = 0;
+ while (1) {
+ bad_vsync();
+ keyflags_poll();
+
+ if (key_is_down(Key::Left)) x -= 1;
+ if (key_is_down(Key::Right)) x += 1;
+ if (key_is_down(Key::Up)) y -= 1;
+ if (key_is_down(Key::Down)) y += 1;
+
+ set_bg_offsets(0, x, y);
+ }
+
+ return 0;
+}
diff --git a/source/types.hpp b/source/types.hpp
new file mode 100644
index 0000000..222b11c
--- /dev/null
+++ b/source/types.hpp
@@ -0,0 +1,18 @@
+#ifndef TYPES_HPP
+#define TYPES_HPP
+
+typedef signed char s8;
+typedef signed short s16;
+typedef signed int s32;
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+
+typedef volatile unsigned char vu8;
+typedef volatile unsigned short vu16;
+typedef volatile unsigned int vu32;
+
+typedef u16 color_t;
+
+#endif
diff --git a/source/util.hpp b/source/util.hpp
new file mode 100644
index 0000000..7b39376
--- /dev/null
+++ b/source/util.hpp
@@ -0,0 +1,191 @@
+#ifndef UTIL_HPP
+#define UTIL_HPP
+
+#include "types.hpp"
+#include <assert.h>
+#include <type_traits>
+
+constexpr u32 SCREEN_WIDTH = 240;
+constexpr u32 SCREEN_HEIGHT = 160;
+
+vu16* const REG_VCOUNT = reinterpret_cast<vu16*>(0x04000006);
+
+enum DisplayControlMode : u16 {
+ Mode0 = 0x0000,
+ Mode1 = 0x0001,
+ Mode2 = 0x0002,
+ Mode3 = 0x0003,
+ Mode4 = 0x0004,
+ Mode5 = 0x0005,
+};
+
+enum DisplayControlLayer : u16 {
+ BG0 = 0x0100,
+ BG1 = 0x0200,
+ BG2 = 0x0400,
+ BG3 = 0x0800,
+ BG4 = 0x1000,
+};
+
+typedef u16 DisplayControlFlags;
+
+constexpr DisplayControlFlags operator|(DisplayControlMode mode, DisplayControlLayer layer) {
+ return static_cast<DisplayControlFlags>(static_cast<u16>(mode) | static_cast<u16>(layer));
+}
+
+static inline void set_display_control(DisplayControlMode mode, DisplayControlLayer layer) {
+ static volatile DisplayControlFlags* const display_control = reinterpret_cast<DisplayControlFlags*>(0x04000000);
+ *display_control = mode | layer;
+}
+
+enum BGControlColorMode : u16 {
+ Mode_4bpp = 0x0000,
+ Mode_8bpp = 0x0080,
+};
+
+enum BGControlSize : u16 {
+ Regular_32x32 = 0x0000,
+ Regular_64x32 = 0x4000,
+ Regular_32x64 = 0x8000,
+ Regular_64x64 = 0xc000,
+ Affine_16x16 = 0x0000,
+ Affine_32x32 = 0x4000,
+ Affine_64x64 = 0x8000,
+ Affine_128x128 = 0xc000,
+};
+
+struct BGControlFlags {
+ u16 flags;
+
+ constexpr BGControlFlags() : flags(0) {}
+
+ constexpr BGControlFlags &set_priority(u16 priority) {
+ assert(priority < 4);
+ this->flags = (this->flags & 0xfffc) | priority;
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_charblock_base(u16 block) {
+ assert(block < 4);
+ this->flags = (this->flags & 0xfff3) | (block << 2);
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_mosaic(u16 enabled) {
+ this->flags = (this->flags & 0xffbf) | (!!enabled << 6);
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_color_mode(BGControlColorMode mode) {
+ this->flags = (this->flags & 0xff7f) | mode;
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_screenblock_base(u16 block) {
+ assert(block < 32);
+ this->flags = (this->flags & 0xe0ff) | (block << 8);
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_affine_wrapping(u16 enabled) {
+ this->flags = (this->flags & 0xdfff) | (!!enabled << 13);
+ return *this;
+ }
+
+ constexpr BGControlFlags &set_size(BGControlSize size) {
+ this->flags = (this->flags & 0x3fff) | size;
+ return *this;
+ }
+
+ constexpr operator u16() const { return this->flags; }
+};
+
+static inline void set_bg_control(u16 bg, BGControlFlags flags) {
+ assert(bg < 4);
+
+ vu16* const reg = reinterpret_cast<vu16*>(0x04000008 + 2*bg);
+ *reg = flags;
+}
+
+static inline void set_bg_offsets(u16 bg, u16 h_offset, u16 v_offset) {
+ vu16* const h_reg = reinterpret_cast<vu16*>(0x04000010 + 4*bg);
+ vu16* const v_reg = reinterpret_cast<vu16*>(0x04000012 + 4*bg);
+
+ *h_reg = h_offset;
+ *v_reg = v_offset;
+}
+
+struct Color {
+ u16 data;
+
+ static constexpr Color from_rgb(u16 r, u16 g, u16 b) {
+ assert(r < 32 && g < 32 && b < 32);
+ return Color { .data = u16(r | (g << 5) | (b << 10)) };
+ }
+};
+
+typedef Color PaletteBank[16];
+
+struct Tile4 { u32 data[8]; };
+typedef Tile4 CharBlock4[512];
+
+template <typename T>
+static inline void volatile_copy(volatile T* dst, const T* src) {
+ using U = std::conditional_t<(sizeof(T) % sizeof(u32) == 0) && (alignof(T) % alignof(u32) == 0), u32, u16>;
+ static_assert(sizeof(T) % sizeof(U) == 0);
+ static_assert(alignof(T) % alignof(U) == 0);
+ auto *d = reinterpret_cast<volatile U*>(dst);
+ auto *s = reinterpret_cast<const U*>(src);
+ for (unsigned i = 0; i < sizeof(T) / sizeof(U); i++) {
+ d[i] = s[i];
+ }
+}
+
+static inline void set_tile(u16 block, u16 index, const Tile4 &tile) {
+ // TODO bounds check
+ volatile CharBlock4 *const blocks = reinterpret_cast<volatile CharBlock4*>(0x06000000);
+ volatile_copy(&blocks[block][index], &tile);
+}
+
+static inline void set_bg_palette(u32 bank, u32 index, const Color &color) {
+ volatile PaletteBank *const banks = reinterpret_cast<volatile PaletteBank*>(0x05000000);
+ volatile_copy(&banks[bank][index], &color);
+}
+
+// pair of screen entry bytes
+struct ScreenEntry {
+ u16 data;
+
+ constexpr ScreenEntry() : data(0) {}
+ constexpr ScreenEntry &set_tile_index(u16 tid) {
+ assert(tid < 1024);
+ this->data = (this->data & ~0x03ff) | tid;
+ return *this;
+ }
+
+ constexpr ScreenEntry &set_hflip(u16 flip) {
+ this->data = (this->data & ~0x0400) | (!!flip << 10);
+ return *this;
+ }
+
+ constexpr ScreenEntry &set_vflip(u16 flip) {
+ this->data = (this->data & ~0x0800) | (!!flip << 11);
+ return *this;
+ }
+
+ constexpr ScreenEntry &set_palette_bank(u16 bank) {
+ assert(bank < 16);
+ this->data = (this->data & ~0xf000) | (bank << 12);
+ return *this;
+ }
+
+ constexpr operator u16() const { return this->data; }
+};
+
+typedef ScreenEntry ScreenBlock[1024];
+
+CharBlock4 *const tile4_mem = reinterpret_cast<CharBlock4*>(0x06000000);
+PaletteBank *const palette_bg_bank = reinterpret_cast<PaletteBank*>(0x05000000);
+ScreenBlock *const screenblock_mem = reinterpret_cast<ScreenBlock*>(0x06000000);
+
+#endif