diff --git a/examples/LinkUART_demo/Makefile b/examples/LinkUART_demo/Makefile new file mode 100644 index 0000000..7cff424 --- /dev/null +++ b/examples/LinkUART_demo/Makefile @@ -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 $( `(echo $(> `(echo $(> `(echo $( $(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 diff --git a/examples/LinkUART_demo/src/main.cpp b/examples/LinkUART_demo/src/main.cpp new file mode 100644 index 0000000..652dc2f --- /dev/null +++ b/examples/LinkUART_demo/src/main.cpp @@ -0,0 +1,85 @@ +#include +#include +#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()); +} diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp new file mode 100644 index 0000000..822caee --- /dev/null +++ b/lib/LinkUART.hpp @@ -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 +#include + +// 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