Adding LinkUART

This commit is contained in:
Rodrigo Alfonso
2024-04-20 09:45:25 -03:00
parent 75a70f6f40
commit 7bafeedd30
3 changed files with 609 additions and 0 deletions

View File

@@ -0,0 +1,284 @@
#
# 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 -O2
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
else
TARGET := $(PROJ)
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,85 @@
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
// (0) Include the header
#include "../../../lib/LinkUART.hpp"
void log(std::string text);
inline void VBLANK() {}
std::string buffer = "";
// (1) Create a LinkUART instance
LinkUART* linkUART = new LinkUART();
void init() {
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the interrupt service routines
interrupt_init();
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, LINK_UART_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
}
int main() {
init();
bool firstTransfer = false;
while (true) {
std::string output = "LinkUART_demo (v6.2.3)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkUART->isActive()) {
firstTransfer = true;
output += "START: Start listening...\n";
output += "\n(stop: press L+R)\n";
if ((keys & KEY_START) | (keys & KEY_SELECT)) {
// (3) Initialize the library
linkUART->activate();
buffer = "";
}
} else {
// Title
output += "[uart]\n";
if (firstTransfer) {
log(output + "Waiting...");
firstTransfer = false;
}
// (4) Send/read bytes
if (linkUART->canRead()) {
u8 newByte = linkUART->read();
while (!linkUART->canSend())
;
linkUART->send('z');
buffer += (char)newByte;
if (buffer.size() > 250)
buffer = "";
}
output += buffer;
// Cancel
if ((keys & KEY_L) && (keys & KEY_R)) {
linkUART->deactivate();
}
}
// Print
VBlankIntrWait();
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

240
lib/LinkUART.hpp Normal file
View File

@@ -0,0 +1,240 @@
#ifndef LINK_UART_H
#define LINK_UART_H
// --------------------------------------------------------------------------
// An UART handler for the Link Port (8N1, 7N1, 8E1, 7E1, 8O1, 7E1).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkUART* linkUART = new LinkUART();
// - 2) Add the required interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_SERIAL, LINK_UART_ISR_SERIAL);
// - 3) Initialize the library with:
// linkUART->activate();
// - 4) Send/read bytes by using:
// if (linkUART->canSend())
// linkUART->send(0xFA);
// if (linkUART->canRead())
// u8 newByte = linkUART->read();
// --------------------------------------------------------------------------
// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug.
// That causes packet loss. You REALLY want to use libugba's instead.
// (see examples)
// --------------------------------------------------------------------------
#include <tonc_bios.h>
#include <tonc_core.h>
// Buffer size
#define LINK_UART_QUEUE_SIZE 256
#define LINK_UART_BIT_CTS 2
#define LINK_UART_BIT_PARITY_CONTROL 3
#define LINK_UART_BIT_SEND_DATA_FLAG 4
#define LINK_UART_BIT_RECEIVE_DATA_FLAG 5
#define LINK_UART_BIT_ERROR_FLAG 6
#define LINK_UART_BIT_DATA_LENGTH 7
#define LINK_UART_BIT_FIFO_ENABLE 8
#define LINK_UART_BIT_PARITY_ENABLE 9
#define LINK_UART_BIT_SEND_ENABLE 10
#define LINK_UART_BIT_RECEIVE_ENABLE 11
#define LINK_UART_BIT_UART_1 12
#define LINK_UART_BIT_UART_2 13
#define LINK_UART_BIT_IRQ 14
#define LINK_UART_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_UART_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_UART_BARRIER asm volatile("" ::: "memory")
static volatile char LINK_UART_VERSION[] = "LinkUART/v6.2.3";
void LINK_UART_ISR_SERIAL();
class LinkUART {
public:
enum BaudRate {
BAUD_RATE_0, // 9600 bps
BAUD_RATE_1, // 38400 bps
BAUD_RATE_2, // 57600 bps
BAUD_RATE_3 // 115200 bps
};
enum DataSize { SIZE_7_BITS, SIZE_8_BITS };
enum Parity { NO, EVEN, ODD };
explicit LinkUART() {
this->config.baudRate = BAUD_RATE_0;
this->config.dataSize = SIZE_8_BITS;
this->config.parity = NO;
this->config.useCTS = false;
}
bool isActive() { return isEnabled; }
void activate(BaudRate baudRate = BAUD_RATE_0,
DataSize dataSize = SIZE_8_BITS,
Parity parity = NO,
bool useCTS = false) {
this->config.baudRate = baudRate;
this->config.dataSize = dataSize;
this->config.parity = parity;
this->config.useCTS = false;
LINK_UART_BARRIER;
isEnabled = false;
LINK_UART_BARRIER;
reset();
LINK_UART_BARRIER;
isEnabled = true;
LINK_UART_BARRIER;
}
void deactivate() {
LINK_UART_BARRIER;
isEnabled = false;
LINK_UART_BARRIER;
resetState();
stop();
}
bool canRead() { return !queue.isEmpty(); }
u8 read() { return queue.pop(); }
bool canSend() { return !isBitHigh(LINK_UART_BIT_SEND_DATA_FLAG); }
void send(u8 data) { REG_SIODATA8 = data; }
void _onSerial() {
if (!isEnabled)
return;
if (hasError()) {
reset();
return;
}
if (canReceive())
queue.push((u8)REG_SIODATA8);
}
private:
class U8Queue {
public:
void push(u8 item) {
if (isFull())
pop();
rear = (rear + 1) % LINK_UART_QUEUE_SIZE;
arr[rear] = item;
count++;
}
u16 pop() {
if (isEmpty())
return 0;
auto x = arr[front];
front = (front + 1) % LINK_UART_QUEUE_SIZE;
count--;
return x;
}
u16 peek() {
if (isEmpty())
return 0;
return arr[front];
}
void clear() {
front = count = 0;
rear = -1;
}
u32 size() { return count; }
bool isEmpty() { return size() == 0; }
bool isFull() { return size() == LINK_UART_QUEUE_SIZE; }
private:
u8 arr[LINK_UART_QUEUE_SIZE];
vs32 front = 0;
vs32 rear = -1;
vu32 count = 0;
};
struct Config {
BaudRate baudRate;
DataSize dataSize;
Parity parity;
bool useCTS;
};
Config config;
U8Queue queue;
volatile bool isEnabled = false;
bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); }
bool canReceive() { return !isBitHigh(LINK_UART_BIT_RECEIVE_DATA_FLAG); }
void reset() {
resetState();
stop();
start();
}
void resetState() { queue.clear(); }
void stop() { setGeneralPurposeMode(); }
void start() {
setUARTMode();
if (config.dataSize == SIZE_8_BITS)
set8BitData();
if (config.parity > NO) {
if (config.parity == ODD)
setOddParity();
setParityOn();
}
if (config.useCTS)
setCTSOn();
setFIFOOn();
setInterruptsOn();
setSendOn();
setReceiveOn();
}
void set8BitData() { setBitHigh(LINK_UART_BIT_DATA_LENGTH); }
void setParityOn() { setBitHigh(LINK_UART_BIT_PARITY_ENABLE); }
void setOddParity() { setBitHigh(LINK_UART_BIT_PARITY_CONTROL); }
void setCTSOn() { setBitHigh(LINK_UART_BIT_CTS); }
void setFIFOOn() { setBitHigh(LINK_UART_BIT_FIFO_ENABLE); }
void setInterruptsOn() { setBitHigh(LINK_UART_BIT_IRQ); }
void setSendOn() { setBitHigh(LINK_UART_BIT_SEND_ENABLE); }
void setReceiveOn() { setBitHigh(LINK_UART_BIT_RECEIVE_ENABLE); }
void setUARTMode() {
REG_RCNT = REG_RCNT & ~(1 << LINK_UART_BIT_GENERAL_PURPOSE_HIGH);
REG_SIOCNT = (1 << LINK_UART_BIT_UART_1) | (1 << LINK_UART_BIT_UART_2);
REG_SIOCNT |= config.baudRate;
REG_SIOMLT_SEND = 0;
}
void setGeneralPurposeMode() {
REG_RCNT = (REG_RCNT & ~(1 << LINK_UART_BIT_GENERAL_PURPOSE_LOW)) |
(1 << LINK_UART_BIT_GENERAL_PURPOSE_HIGH);
}
bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { REG_SIOCNT &= ~(1 << bit); }
};
extern LinkUART* linkUART;
inline void LINK_UART_ISR_SERIAL() {
linkUART->_onSerial();
}
#endif // LINK_UART_H