Implementing IR adapter detection

This commit is contained in:
Rodrigo Alfonso 2025-02-07 23:32:41 -03:00
parent 5ad59ea2e1
commit a83ed0a6e3
3 changed files with 486 additions and 0 deletions

View File

@ -0,0 +1,289 @@
#
# Template tonc makefile
#
# Yoinked mostly from DKP's template
#
# === SETUP ===========================================================
# --- No implicit rules ---
.SUFFIXES:
# --- Paths ---
export TONCLIB := ${DEVKITPRO}/libtonc
# === TONC RULES ======================================================
#
# Yes, this is almost, but not quite, completely like to
# DKP's base_rules and gba_rules
#
export PATH := $(DEVKITARM)/bin:$(PATH)
# --- Executable names ---
PREFIX ?= arm-none-eabi-
export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export NM := $(PREFIX)nm
export OBJCOPY := $(PREFIX)objcopy
# LD defined in Makefile
# === LINK / TRANSLATE ================================================
%.gba : %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
@gbafix $@ -t$(TITLE)
#----------------------------------------------------------------------
%.mb.elf :
@echo Linking multiboot
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.elf :
@echo Linking cartridge
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.a :
@echo $(notdir $@)
@rm -f $@
$(AR) -crs $@ $^
# === OBJECTIFY =======================================================
%.iwram.o : %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.iwram.o : %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# canned command sequence for binary data
#----------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
# =====================================================================
# --- Main path ---
export PATH := $(DEVKITARM)/bin:$(PATH)
# === PROJECT DETAILS =================================================
# PROJ : Base project name
# TITLE : Title for ROM header (12 characters)
# LIBS : Libraries to use, formatted as list for linker flags
# BUILD : Directory for build process temporaries. Should NOT be empty!
# SRCDIRS : List of source file directories
# DATADIRS : List of data file directories
# INCDIRS : List of header file directories
# LIBDIRS : List of library directories
# General note: use `.' for the current dir, don't leave the lists empty.
export PROJ ?= $(notdir $(CURDIR))
TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
# --- switches ---
bMB := 0 # Multiboot build
bTEMPS := 0 # Save gcc temporaries (.i and .s files)
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
# === BUILD FLAGS =====================================================
# This is probably where you can stop editing
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
# up things (gcse seems fond of building masks inside a loop instead of
# outside them for example). Removing them sometimes helps
# --- Architecture ---
ARCH := -mthumb-interwork -mthumb
RARCH := -mthumb-interwork -mthumb
IARCH := -mthumb-interwork -marm -mlong-calls
# --- Main flags ---
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
USERFLAGS ?=
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---
ifeq ($(strip $(bTEMPS)), 1)
CFLAGS += -save-temps
CXXFLAGS += -save-temps
endif
# --- Debug info ? ---
ifeq ($(strip $(bDEBUG)), 1)
CFLAGS += -DDEBUG -g
CXXFLAGS += -DDEBUG -g
ASFLAGS += -DDEBUG -g
LDFLAGS += -g
else
CFLAGS += -DNDEBUG
CXXFLAGS += -DNDEBUG
ASFLAGS += -DNDEBUG
endif
# === BUILD PROC ======================================================
ifneq ($(BUILD),$(notdir $(CURDIR)))
# Still in main dir:
# * Define/export some extra variables
# * Invoke this file again from the build dir
# PONDER: what happens if BUILD == "" ?
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := \
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
# --- List source and data files ---
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
# --- Set linker depending on C++ file existence ---
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
# --- Define object file list ---
export OFILES := $(addsuffix .o, $(BINFILES)) \
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
$(SFILES:.s=.o)
# --- Create include and library search paths ---
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
# --- Create BUILD if necessary, and run this makefile from there ---
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
all : $(BUILD)
clean:
@echo clean ...
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
else # If we're here, we should be in the BUILD dir
DEPENDS := $(OFILES:.o=.d)
# --- Main targets ----
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
-include $(DEPENDS)
endif # End BUILD switch
# --- More targets ----------------------------------------------------
.PHONY: clean rebuild start
rebuild: clean $(BUILD)
start:
start "$(TARGET).gba"
restart: rebuild start
# EOF

View File

@ -0,0 +1,38 @@
// (0) Include the header
#include "../../../lib/LinkIR.hpp"
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
// (1) Create a LinkIR instance
LinkIR* linkIR = new LinkIR();
void init() {
Common::initTTE();
// (2) Add the required interrupt service routines
interrupt_init();
interrupt_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, LINK_IR_ISR_SERIAL);
interrupt_add(INTR_TIMER3, []() {});
// (3) Initialize the library
// linkIR->activate();
}
int main() {
init();
bool isConnected = linkIR->activate();
if (isConnected) {
Common::log("Adapter connected!");
} else {
Common::log("NOT connected");
}
while (true) {
VBlankIntrWait();
}
return 0;
}

159
lib/LinkIR.hpp Normal file
View File

@ -0,0 +1,159 @@
#ifndef LINK_IR_H
#define LINK_IR_H
// --------------------------------------------------------------------------
// A driver for the Infrared Adapter (AGB-006).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkIR* linkIR = new LinkIR();
// - 2) Add the interrupt service routines:
// interrupt_init();
// interrupt_add(INTR_SERIAL, LINK_IR_ISR_SERIAL);
// interrupt_add(INTR_TIMER3, []() {});
// - 3) Initialize the library with:
// linkIR->activate();
// - 4) Send IR signals:
// linkIR->send({21, 2, 40, 4, 32, 0});
// // u16 array
// // even indices: marks (IR on)
// // odd indices: spaces (IR off)
// // numbers represent microseconds
// // 0 = EOS
// - 5) Receive IR signals;
// u16 pulses[2000];
// linkIR->receive(pulses, 2000, 30000);
// // out array, max entries, timeout (in microseconds)
// --------------------------------------------------------------------------
#ifndef LINK_DEVELOPMENT
#pragma GCC system_header
#endif
#include "LinkGPIO.hpp"
#include "_link_common.hpp"
LINK_VERSION_TAG LINK_IR_VERSION = "vLinkIR/v8.0.0";
#define LINK_IR_SIGNAL_END 0
#define LINK_IR_DEFAULT_TIMER_ID 3
/**
* @brief A driver for the Infrared Adapter (AGB-006).
*/
class LinkIR {
private:
using u32 = Link::u32;
using u16 = Link::u16;
using u8 = Link::u8;
using Pin = LinkGPIO::Pin;
using Direction = LinkGPIO::Direction;
static constexpr int TIMEOUT_MICROSECONDS = 1000;
static constexpr int TO_TICKS = 17;
public:
/**
* @brief Constructs a new LinkIR object.
* @param timerId `(0~3)` GBA Timer to use for waiting.
*/
explicit LinkIR(u8 timerId = LINK_IR_DEFAULT_TIMER_ID) {
config.timerId = timerId;
}
/**
* @brief Returns whether the library is active or not.
*/
[[nodiscard]] bool isActive() { return isEnabled; }
/**
* @brief Activates the library. Returns if the adapter is connected or not.
*/
bool activate() {
LINK_READ_TAG(LINK_IR_VERSION);
LINK_BARRIER;
isEnabled = false;
LINK_BARRIER;
resetState();
linkGPIO.reset();
LINK_BARRIER;
isEnabled = true;
LINK_BARRIER;
linkGPIO.setMode(Pin::SC, Direction::OUTPUT);
linkGPIO.writePin(Pin::SC, false);
linkGPIO.setMode(Pin::SO, Direction::OUTPUT);
linkGPIO.writePin(Pin::SO, false);
linkGPIO.setSIInterrupts(true);
linkGPIO.writePin(Pin::SO, true);
setTimer(TIMEOUT_MICROSECONDS);
Link::_IntrWait(0,
Link::_IRQ_SERIAL | Link::_TIMER_IRQ_IDS[config.timerId]);
bool success = irq;
irq = false;
return success;
}
/**
* @brief Deactivates the library.
*/
void deactivate() {
isEnabled = false;
linkGPIO.reset();
}
/**
* @brief This method is called by the SERIAL interrupt handler.
* \warning This is internal API!
*/
void _onSerial() {
if (!isEnabled)
return;
irq = true;
}
struct Config {
u8 timerId;
};
/**
* @brief LinkIR configuration.
* \warning `deactivate()` first, change the config, and `activate()` again!
*/
Config config;
private:
LinkGPIO linkGPIO;
volatile bool isEnabled = false;
volatile bool irq = false;
void resetState() { irq = false; }
void setTimer(u32 microseconds) {
u32 cycles = microseconds * TO_TICKS;
Link::_REG_TM[config.timerId].start = -cycles;
Link::_REG_TM[config.timerId].cnt =
Link::_TM_ENABLE | Link::_TM_IRQ | Link::_TM_FREQ_1;
}
};
extern LinkIR* linkIR;
/**
* @brief SERIAL interrupt handler.
*/
inline void LINK_IR_ISR_SERIAL() {
linkIR->_onSerial();
}
// TODO: IMPLEMENT
// TODO: C BINDINGS
#endif // LINK_IR_H