diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d789ed..451d782 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,7 @@ jobs: matrix: example: [ "mass-storage", + "usb-microphone", "usb-speaker", "usb-video" ] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fd3e62..907fc8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ jobs: matrix: example: [ "mass-storage", + "usb-microphone", "usb-speaker", "usb-video" ] diff --git a/README.md b/README.md index 52eba5f..e5d4189 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ This repository contains examples of using the DSpico USB port from a DS applica The `examples` folder contains the following examples: - mass-storage - Allows access to the DSpico micro SD card over USB +- usb-microphone - Makes the DSi/3DS work as a USB microphone for your PC + - Note: does not work with regular DS devices currently - usb-speaker - Makes the DS/DSi/3DS work as a USB speaker for your PC - usb-video - Allows to use the camera of your DSi or 3DS on your PC via USB diff --git a/examples/usb-microphone/Makefile b/examples/usb-microphone/Makefile new file mode 100644 index 0000000..daface1 --- /dev/null +++ b/examples/usb-microphone/Makefile @@ -0,0 +1,61 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +export TARGET := $(shell basename $(CURDIR)) +export TOPDIR := $(CURDIR) + +# specify a directory which contains the nitro filesystem +# this is relative to the Makefile +NITRO_FILES := + +# These set the information text in the nds file +GAME_TITLE := USB Microphone Example +GAME_SUBTITLE1 := DSpico +GAME_SUBTITLE2 := LNH team +GAME_ICON := icon.bmp + +include $(DEVKITARM)/ds_rules + +.PHONY: checklibtwl checkarm7 checkarm9 clean + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all: checklibtwl checkarm7 checkarm9 $(TARGET).nds + +#--------------------------------------------------------------------------------- +checklibtwl: + $(MAKE) -C ../../libs/libtwl/libtwl7 + $(MAKE) -C ../../libs/libtwl/libtwl9 + +#--------------------------------------------------------------------------------- +checkarm7: checklibtwl + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +checkarm9: checklibtwl + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +$(TARGET).nds : $(NITRO_FILES) arm7/$(TARGET).elf arm9/$(TARGET).elf + ndstool -c $(TARGET).nds -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ + -b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1);$(GAME_SUBTITLE2)" \ + $(_ADDFILES) + +#--------------------------------------------------------------------------------- +arm7/$(TARGET).elf: + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +arm9/$(TARGET).elf: + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +clean: + $(MAKE) -C arm9 clean + $(MAKE) -C arm7 clean + rm -f $(TARGET).nds $(TARGET).arm7 $(TARGET).arm9 diff --git a/examples/usb-microphone/arm7/Makefile b/examples/usb-microphone/arm7/Makefile new file mode 100644 index 0000000..60da066 --- /dev/null +++ b/examples/usb-microphone/arm7/Makefile @@ -0,0 +1,137 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# 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 files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + ../common/ipc \ + source/ipcServices \ + ../../../platform \ + ../../../libs/tinyusb/src \ + ../../../libs/tinyusb/src/device \ + ../../../libs/tinyusb/src/class/audio \ + ../../../libs/tinyusb/src/class/cdc \ + ../../../libs/tinyusb/src/class/msc \ + ../../../libs/tinyusb/src/common +INCLUDES := source include build ../common ../../../libs/tinyusb/src ../../../platform +DATA := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM7 -DARM7 + +CFLAGS := -g -Wall -O2\ + -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=../dldi_ds_arm7.specs -g $(ARCH) -Wl,--nmagic -Wl,-Map,$(notdir $*).map,--gc-sections -ffunction-sections -fdata-sections + +LIBS := -ltwl7 -lnds7 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl7 $(CURDIR)/../../../libs/libtwl/common + + +#--------------------------------------------------------------------------------- +# 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 ARM7ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) + +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)/*.*))) + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) *.elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM7ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/usb-microphone/arm7/dldi_ds_arm7.ld b/examples/usb-microphone/arm7/dldi_ds_arm7.ld new file mode 100644 index 0000000..34f7b22 --- /dev/null +++ b/examples/usb-microphone/arm7/dldi_ds_arm7.ld @@ -0,0 +1,232 @@ +/*-------------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public License, + v. 2.0. If a copy of the MPL was not distributed with this file, You can + obtain one at https://mozilla.org/MPL/2.0/. +--------------------------------------------------------------------------------*/ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) + + +PHDRS { + crt0 PT_LOAD FLAGS(7); + arm7 PT_LOAD FLAGS(7); + arm7i PT_LOAD FLAGS(0x100007); +} + + +MEMORY { + ewram : ORIGIN = 0x02380000, LENGTH = 12M - 512K + rom : ORIGIN = 0x08000000, LENGTH = 32M + iwram : ORIGIN = 0x037fc000, LENGTH = (96K - 16K) + + twl_ewram : ORIGIN = 0x02e80000, LENGTH = 512K - 64K + twl_iwram : ORIGIN = 0x03000000, LENGTH = 256K +} + +__iwram_start = ORIGIN(iwram); +__iwram_top = ORIGIN(iwram)+ LENGTH(iwram); + +__sp_irq = __iwram_top - 0x100; +__sp_svc = __sp_irq - 0x100; +__sp_usr = __sp_svc - 0x100; + +__irq_flags = 0x04000000 - 8; +__irq_flagsaux = 0x04000000 - 0x40; +__irq_vector = 0x04000000 - 4; + +SECTIONS +{ + + .twl : + { + __arm7i_lma__ = LOADADDR(.twl); + __arm7i_start__ = .; + *(.twl) + *.twl*(.text .stub .text.* .gnu.linkonce.t.*) + *.twl*(.rodata) + *.twl*(.roda) + *.twl*(.rodata.*) + *.twl*(.data) + *.twl*(.data.*) + *.twl*(.gnu.linkonce.d*) + . = ALIGN(4); + __arm7i_end__ = .; + } >twl_iwram AT>twl_ewram :arm7i + + .twl_bss ALIGN(4) (NOLOAD) : + { + __twl_bss_start__ = .; + *(.twl_bss) + *.twl.*(.dynbss) + *.twl.*(.gnu.linkonce.b*) + *.twl.*(.bss*) + *.twl.*(COMMON) + . = ALIGN(4); + __twl_bss_end__ = .; + } >twl_iwram :NONE + + .crt0 : + { + KEEP (*(.crt0)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram :crt0 + + .text : + { + __arm7_lma__ = LOADADDR(.text); + __arm7_start__ = .; + KEEP (*(SORT_NONE(.init))) + *(.plt) + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.vfp11_veneer) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram :arm7 + + .fini : + { + KEEP (*(.fini)) + } >iwram AT>ewram + + .rodata : + { + *(.rodata) + *all.rodata*(*) + *(.roda) + *(.rodata.*) + *(.gnu.linkonce.r*) + SORT(CONSTRUCTORS) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } >iwram AT>ewram + +/* Ensure the __preinit_array_start label is properly aligned. We + could instead move the label definition inside the section, but + the linker would then create the section even if it turns out to + be empty, which isn't pretty. */ + .preinit_array : { + . = ALIGN(32 / 8); + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >iwram AT>ewram + + .init_array : { + PROVIDE (__init_array_start = .); + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >iwram AT>ewram + + .fini_array : { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + PROVIDE (__fini_array_end = .); + } >iwram AT>ewram + + .ctors : + { + /* gcc uses crtbegin.o to find the start of the constructors, so + we make sure it is first. Because this is a wildcard, it + doesn't matter if the user does not actually link against + crtbegin.o; the linker won't look for a file to match a + wildcard. The wildcard also means that it doesn't matter which + directory crtbegin.o is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .eh_frame : + { + KEEP (*(.eh_frame)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .gcc_except_table : + { + *(.gcc_except_table) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + .jcr : { KEEP (*(.jcr)) } >iwram AT>ewram + .got : { *(.got.plt) *(.got) } >iwram AT>ewram + + .data ALIGN(4) : { + __data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + CONSTRUCTORS + . = ALIGN(4); + __data_end = ABSOLUTE(.) ; + } >iwram AT>ewram + + .bss ALIGN(4) (NOLOAD) : + { + __arm7_end__ = .; + __bss_start = ABSOLUTE(.); + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.gnu.linkonce.b*) + *(.bss*) + *(COMMON) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __bss_end__ = ABSOLUTE(.); + __end__ = ABSOLUTE(.); + } >iwram + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} \ No newline at end of file diff --git a/examples/usb-microphone/arm7/dldi_ds_arm7.specs b/examples/usb-microphone/arm7/dldi_ds_arm7.specs new file mode 100644 index 0000000..8f01248 --- /dev/null +++ b/examples/usb-microphone/arm7/dldi_ds_arm7.specs @@ -0,0 +1,8 @@ +%rename link old_link + +*link: +%(old_link) -T ../dldi_ds_arm7.ld%s --gc-sections --no-warn-rwx-segments + +*startfile: +ds_arm7_crt0%O%s crti%O%s crtbegin%O%s + diff --git a/examples/usb-microphone/arm7/source/Arm7State.h b/examples/usb-microphone/arm7/source/Arm7State.h new file mode 100644 index 0000000..b306ac5 --- /dev/null +++ b/examples/usb-microphone/arm7/source/Arm7State.h @@ -0,0 +1,8 @@ +#pragma once + +/// @brief Enum representing the arm7 state. +enum class Arm7State +{ + Idle, + ExitRequested +}; diff --git a/examples/usb-microphone/arm7/source/ExitMode.h b/examples/usb-microphone/arm7/source/ExitMode.h new file mode 100644 index 0000000..1750dd8 --- /dev/null +++ b/examples/usb-microphone/arm7/source/ExitMode.h @@ -0,0 +1,10 @@ +#pragma once + +/// @brief Enum representing the exit mode of launcher. +enum class ExitMode +{ + /// @brief Reset the system (DSi mode only). + Reset, + /// @brief Power off the system. + PowerOff +}; diff --git a/examples/usb-microphone/arm7/source/common.h b/examples/usb-microphone/arm7/source/common.h new file mode 100644 index 0000000..3cac3e4 --- /dev/null +++ b/examples/usb-microphone/arm7/source/common.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +extern rtos_mutex_t gCardMutex; diff --git a/examples/usb-microphone/arm7/source/main.cpp b/examples/usb-microphone/arm7/source/main.cpp new file mode 100644 index 0000000..1e1ab12 --- /dev/null +++ b/examples/usb-microphone/arm7/source/main.cpp @@ -0,0 +1,197 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ExitMode.h" +#include "Arm7State.h" +#include "tusb.h" +#include "usb_descriptors.h" +#include "microphone.h" + +rtos_mutex_t gCardMutex; + +static rtos_thread_t sUsbThread; +static u32 sUsbThreadStack[512]; + +static rtos_event_t sVBlankEvent; +static ExitMode sExitMode; +static Arm7State sState; +static volatile u8 sMcuIrqFlag = false; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVBlankEvent); +} + +static void mcuIrq(u32 irq2Mask) +{ + sMcuIrqFlag = true; +} + +static void checkMcuIrq(void) +{ + // mcu only exists in DSi mode + if (isDSiMode()) + { + // check and ack the flag atomically + if (mem_swapByte(false, &sMcuIrqFlag)) + { + // check the irq mask + u32 irqMask = mcu_getIrqMask(); + if (irqMask & MCU_IRQ_RESET) + { + // power button was released + sExitMode = ExitMode::Reset; + sState = Arm7State::ExitRequested; + } + else if (irqMask & MCU_IRQ_POWER_OFF) + { + // power button was held long to trigger a power off + sExitMode = ExitMode::PowerOff; + sState = Arm7State::ExitRequested; + } + } + } +} + +static void initializeVBlankIrq() +{ + rtos_createEvent(&sVBlankEvent); + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); +} + +static void usbThreadMain(void* arg) +{ + while (true) + { + tud_task(); + } +} + +static void initializeArm7() +{ + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + // clear sound registers + dmaFillWords(0, (void*)0x04000400, 0x100); + + pmic_setAmplifierEnable(true); + sys_setSoundPower(true); + + readUserSettings(); + pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE); + + sio_setGpioSiIrq(false); + sio_setGpioMode(RCNT0_L_MODE_GPIO); + + rtc_init(); + + snd_setMasterVolume(0); + snd_setMasterEnable(false); + + initializeVBlankIrq(); + + if (isDSiMode()) + { + rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); + rtos_enableIrq2Mask(RTOS_IRQ2_MCU); + mic_initialize(); + } + + pmic_setTopBacklightEnable(false); + pmic_setBottomBacklightEnable(false); + + tusb_rhport_init_t dev_init = + { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(0, &dev_init); + + rtos_createThread(&sUsbThread, 3, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack)); + rtos_wakeupThread(&sUsbThread); + + ipc_setArm7SyncBits(7); +} + +static void updateArm7IdleState() +{ + checkMcuIrq(); + + if (sState == Arm7State::ExitRequested) + { + snd_setMasterVolume(0); // mute sound + } +} + +static bool performExit(ExitMode exitMode) +{ + switch (exitMode) + { + case ExitMode::Reset: + { + mcu_setWarmBootFlag(true); + mcu_hardReset(); + break; + } + case ExitMode::PowerOff: + { + pmic_shutdown(); + break; + } + } + + while (true); // wait infinitely for exit +} + +static void updateArm7ExitRequestedState() +{ + performExit(sExitMode); +} + +static void updateArm7() +{ + switch (sState) + { + case Arm7State::Idle: + { + updateArm7IdleState(); + break; + } + case Arm7State::ExitRequested: + { + updateArm7ExitRequestedState(); + break; + } + } +} + +int main() +{ + sState = Arm7State::Idle; + initializeArm7(); + + while (true) + { + rtos_waitEvent(&sVBlankEvent, true, true); + updateArm7(); + } + + return 0; +} diff --git a/examples/usb-microphone/arm7/source/microphone.cpp b/examples/usb-microphone/arm7/source/microphone.cpp new file mode 100644 index 0000000..3195392 --- /dev/null +++ b/examples/usb-microphone/arm7/source/microphone.cpp @@ -0,0 +1,282 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include "tusb.h" +#include "usb_descriptors.h" +#include "microphone.h" + +#define AUDIO_BLOCK_SIZE_IN_BYTES 32 +#define NUMBER_OF_AUDIO_BUFFERS 128 + +static s16 sAudioBuffer[NUMBER_OF_AUDIO_BUFFERS][16]; +static volatile int sReadBlock; +static volatile int sWriteBlock; + +static bool sCaptureStarted = false; +static int sOffset = 0; + +static bool sChannelMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 +static uint16_t sChannelVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 + +void mic_initialize() +{ + twlmic_stop(); + REG_I2SCNT = I2SCNT_MIX_RATIO_DSP_0_NITRO_8 | I2SCNT_FREQUENCY_32728_HZ; + codec_setPage(CODEC_PAGE_0); + { + codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87); + codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87); + codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 21); + } + REG_I2SCNT |= I2SCNT_ENABLE; + codec_setPage(CODEC_PAGE_1); + { + codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 3); + } + bool adcOn, dacOn; + codec_setPage(CODEC_PAGE_0); + { + adcOn = codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC) & 0x80; + dacOn = codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP) & 0xC0; + codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x80); + if (!adcOn || !dacOn) + { + swi_waitByLoop(0x28E91F); // 20ms + } + codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0); + codec_writeRegister(CODEC_REG_PAGE0_AGC_CONTROL_1, 0); + } + codec_setPage(CODEC_PAGE_1); + { + sChannelVolume[0] = 40; // dB + codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, sChannelVolume[0] * 2); // gain + } +} + +static void micIrq(u32 irq2Mask) +{ + u32 data[8]; + data[0] = REG_MIC_FIFO; + data[1] = REG_MIC_FIFO; + data[2] = REG_MIC_FIFO; + data[3] = REG_MIC_FIFO; + data[4] = REG_MIC_FIFO; + data[5] = REG_MIC_FIFO; + data[6] = REG_MIC_FIFO; + data[7] = REG_MIC_FIFO; + int writeBlock = sWriteBlock; + int nextWriteBlock = (writeBlock + 1) % NUMBER_OF_AUDIO_BUFFERS; + if (nextWriteBlock != sReadBlock) + { + memcpy(&sAudioBuffer[writeBlock][0], data, sizeof(data)); + sWriteBlock = nextWriteBlock; + } +} + +static void startMicrophoneCapture() +{ + sReadBlock = 0; + sWriteBlock = 0; + sOffset = 0; + twlmic_stop(); + twlmic_configure(MICCNT_FORMAT_NORMAL, MICCNT_RATE_DIV_1, MICCNT_IRQ_HALF_OVERFLOW); + twlmic_clearFifo(); + rtos_setIrq2Func(RTOS_IRQ2_MIC, micIrq); + rtos_ackIrq2Mask(RTOS_IRQ2_MIC); + rtos_enableIrq2Mask(RTOS_IRQ2_MIC); + twlmic_start(); + sCaptureStarted = true; +} + +static void stopMicrophoneCapture() +{ + rtos_disableIrq2Mask(RTOS_IRQ2_MIC); + twlmic_stop(); + rtos_ackIrq2Mask(RTOS_IRQ2_MIC); + sCaptureStarted = false; +} + +static bool handleMicInputTerminalGetRequest(u8 rhport, const tusb_control_request_t* p_request) +{ + u8 ctrlSel = TU_U16_HIGH(p_request->wValue); + switch (ctrlSel) + { + case AUDIO_TE_CTRL_CONNECTOR: // Get terminal connector + { + audio_desc_channel_cluster_t ret; + ret.bNrChannels = 1; + ret.bmChannelConfig = (audio_channel_config_t)0; + ret.iChannelNames = 0; + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); + } + } + + return false; +} + +static bool handleFeatureUnitGetRequest(u8 rhport, const tusb_control_request_t* p_request) +{ + u8 channelNum = TU_U16_LOW(p_request->wValue); + u8 ctrlSel = TU_U16_HIGH(p_request->wValue); + switch (ctrlSel) + { + case AUDIO_FU_CTRL_MUTE: // Get Mute of channel + { + return tud_control_xfer(rhport, p_request, &sChannelMute[channelNum], 1); + } + case AUDIO_FU_CTRL_VOLUME: + { + switch (p_request->bRequest) + { + case AUDIO_CS_REQ_CUR: // Get Volume of channel + { + return tud_control_xfer(rhport, p_request, &sChannelVolume[channelNum], sizeof(sChannelVolume[channelNum])); + } + case AUDIO_CS_REQ_RANGE: // Get Volume range of channel + { + audio_control_range_2_n_t(1) ret; + ret.wNumSubRanges = 1; + ret.subrange[0].bMin = 0; // 0 dB + ret.subrange[0].bMax = 59; // +59 dB + ret.subrange[0].bRes = 1; // 1 dB steps + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); + } + } + break; + } + } + + return false; +} + +static bool handleFeatureUnitSetRequest(u8 rhport, const tusb_control_request_t* p_request, u8* pBuff) +{ + u8 channelNum = TU_U16_LOW(p_request->wValue); + u8 ctrlSel = TU_U16_HIGH(p_request->wValue); + switch (ctrlSel) + { + case AUDIO_FU_CTRL_MUTE: // Set Mute of channel + { + sChannelMute[channelNum] = ((audio_control_cur_1_t*)pBuff)->bCur; + return true; + } + case AUDIO_FU_CTRL_VOLUME: // Set Volume of channel + { + // Request uses format layout 2 + sChannelVolume[channelNum] = (uint16_t) ((audio_control_cur_2_t*)pBuff)->bCur; + codec_setPage(CODEC_PAGE_1); + { + codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, sChannelVolume[channelNum] * 2); // gain + } + return true; + } + } + + return false; +} + +static bool handleClockGetRequest(u8 rhport, const tusb_control_request_t* p_request) +{ + u8 ctrlSel = TU_U16_HIGH(p_request->wValue); + switch (ctrlSel) + { + case AUDIO_CS_CTRL_SAM_FREQ: + { + switch (p_request->bRequest) + { + case AUDIO_CS_REQ_CUR: // Get Sample Freq. + { + uint32_t sampFreq = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE; + return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); + } + case AUDIO_CS_REQ_RANGE: // Get Sample Freq. range + { + audio_control_range_4_n_t(1) sampleFreqRng; + sampleFreqRng.wNumSubRanges = 1; + sampleFreqRng.subrange[0].bMin = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE; + sampleFreqRng.subrange[0].bMax = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE; + sampleFreqRng.subrange[0].bRes = 0; + return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); + } + } + break; + } + case AUDIO_CS_CTRL_CLK_VALID: // Get Sample Freq. valid + { + uint8_t clkValid = 1; + return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); + } + } + + return false; +} + +// Invoked when audio class specific get request received for an entity +bool tud_audio_get_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request) +{ + u8 entityId = TU_U16_HIGH(p_request->wIndex); + switch (entityId) + { + case UAC2_ENTITY_MIC_INPUT_TERMINAL: + { + return handleMicInputTerminalGetRequest(rhport, p_request); + } + case UAC2_ENTITY_FEATURE_UNIT: + { + return handleFeatureUnitGetRequest(rhport, p_request); + } + case UAC2_ENTITY_CLOCK: + { + return handleClockGetRequest(rhport, p_request); + } + } + + return false; +} + +// Invoked when audio class specific set request received for an entity +bool tud_audio_set_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request, u8* pBuff) +{ + u8 entityId = TU_U16_HIGH(p_request->wIndex); + switch (entityId) + { + case UAC2_ENTITY_FEATURE_UNIT: + { + return handleFeatureUnitSetRequest(rhport, p_request, pBuff); + } + } + + return false; +} + +bool tud_audio_tx_done_pre_load_cb(u8 rhport, u8 itf, u8 ep_in, u8 cur_alt_setting) +{ + if (!sCaptureStarted) + { + startMicrophoneCapture(); + } + + while (sReadBlock != sWriteBlock) + { + int bytesWritten = tud_audio_write(((u8*)&sAudioBuffer[sReadBlock][0]) + sOffset, AUDIO_BLOCK_SIZE_IN_BYTES - sOffset); + int offset = AUDIO_BLOCK_SIZE_IN_BYTES - bytesWritten; + sOffset = offset % AUDIO_BLOCK_SIZE_IN_BYTES; + if (offset > 0) + { + // Could not write entire block + break; + } + sReadBlock = (sReadBlock + 1) % NUMBER_OF_AUDIO_BUFFERS; + } + + return true; +} + +bool tud_audio_set_itf_close_EP_cb(u8 rhport, const tusb_control_request_t* p_request) +{ + stopMicrophoneCapture(); + return true; +} diff --git a/examples/usb-microphone/arm7/source/microphone.h b/examples/usb-microphone/arm7/source/microphone.h new file mode 100644 index 0000000..d7ddbfd --- /dev/null +++ b/examples/usb-microphone/arm7/source/microphone.h @@ -0,0 +1,3 @@ +#pragma once + +void mic_initialize(); diff --git a/examples/usb-microphone/arm7/source/tusb_config.h b/examples/usb-microphone/arm7/source/tusb_config.h new file mode 100644 index 0000000..6f6289b --- /dev/null +++ b/examples/usb-microphone/arm7/source/tusb_config.h @@ -0,0 +1,97 @@ +#pragma once +#include "usb_descriptors.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_DSPICO +#endif + +#define TUP_DCD_ENDPOINT_MAX 16 + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_CUSTOM +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 1 +#define CFG_TUD_VENDOR 0 + +//-------------------------------------------------------------------- +// AUDIO CLASS DRIVER CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE 32728 +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_ONE_CH_DESC_LEN +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 // Size of control request buffer + +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below - be aware: for different number of channels you need another descriptor! +#define CFG_TUD_AUDIO_EP_SZ_IN TUD_AUDIO_EP_SIZE(CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX) +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 256 //(TUD_OPT_HIGH_SPEED ? 8 : 1) * CFG_TUD_AUDIO_EP_SZ_IN // Example write FIFO every 1ms, so it should be 8 times larger for HS device + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/examples/usb-microphone/arm7/source/usb_descriptors.cpp b/examples/usb-microphone/arm7/source/usb_descriptors.cpp new file mode 100644 index 0000000..0680b2d --- /dev/null +++ b/examples/usb-microphone/arm7/source/usb_descriptors.cpp @@ -0,0 +1,94 @@ +#include "common.h" +#include "UsbStringDescriptor.h" +#include "tusb.h" +#include "usb_descriptors.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x5000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +const u8* tud_descriptor_device_cb(void) +{ + static const tusb_desc_device_t deviceDescriptor = + { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // Use Interface Association Descriptor (IAD) for Audio + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = 0x01 + }; + + return (const u8*)&deviceDescriptor; +} + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_MIC_ONE_CH_DESC_LEN) + +const u8* tud_descriptor_configuration_cb(u8 index) +{ + (void)index; + + static const u8 configurationDescriptor[] = + { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(0, STRID_AUDIO_INTERFACE, + CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * 8, + EPNUM_AUDIO_IN, CFG_TUD_AUDIO_EP_SZ_IN) + }; + + return configurationDescriptor; +} + +const u16* tud_descriptor_string_cb(u8 index, u16 langid) +{ + switch (index) + { + case STRID_LANGID: + { + static const UsbStringDescriptor<2> descriptor(0x0409); + return (const u16*)&descriptor; + } + case STRID_MANUFACTURER: + { + return USB_STRING_DESCRIPTOR(u"LNH"); + } + case STRID_PRODUCT: + { + return USB_STRING_DESCRIPTOR(u"DSpico Microphone Example"); + } + case STRID_SERIAL: + { + return USB_STRING_DESCRIPTOR(u"123456789"); + } + case STRID_AUDIO_INTERFACE: + { + return USB_STRING_DESCRIPTOR(u"DSpico Microphone"); + } + default: + { + return nullptr; + } + } +} diff --git a/examples/usb-microphone/arm7/source/usb_descriptors.h b/examples/usb-microphone/arm7/source/usb_descriptors.h new file mode 100644 index 0000000..0467935 --- /dev/null +++ b/examples/usb-microphone/arm7/source/usb_descriptors.h @@ -0,0 +1,24 @@ +#pragma once + +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_AUDIO_INTERFACE +}; + +enum +{ + ITF_NUM_AUDIO_CONTROL = 0, + ITF_NUM_AUDIO_STREAMING, + ITF_NUM_TOTAL +}; + +#define UAC2_ENTITY_MIC_INPUT_TERMINAL 0x01 +#define UAC2_ENTITY_FEATURE_UNIT 0x02 +#define UAC2_ENTITY_MIC_OUTPUT_TERMINAL 0x03 +#define UAC2_ENTITY_CLOCK 0x04 + +#define EPNUM_AUDIO_IN 0x81 diff --git a/examples/usb-microphone/arm9/Makefile b/examples/usb-microphone/arm9/Makefile new file mode 100644 index 0000000..58a169c --- /dev/null +++ b/examples/usb-microphone/arm9/Makefile @@ -0,0 +1,152 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# 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 files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + source/core +INCLUDES := include source ../common +DATA := data +GRAPHICS := gfx + + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM9 -DARM9 + +CFLAGS := -g -Wall -O2\ + -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + -fno-devirtualize-speculatively \ + -Werror=return-type \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + +CFLAGS += -Werror=implicit-function-declaration + +ASFLAGS := -g $(ARCH) $(INCLUDE) -march=armv5te -mtune=arm946e-s + +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--gc-sections,--use-blx -ffunction-sections -fdata-sections + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -ltwl9 -lnds9 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl9 $(CURDIR)/../../../libs/libtwl/common + +#--------------------------------------------------------------------------------- +# 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 ARM9ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) + +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)/*.*))) +PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(PNGFILES:.png=.o)\ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(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) *.elf *.nds* *.bin + + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM9ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +%.nft2.o : %.nft2 + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# This rule creates assembly source files using grit +# grit takes an image file and a .grit describing how the file is to be processed +# add additional rules like this for each image extension +# you use in the graphics folders +#--------------------------------------------------------------------------------- +%.s %.h: %.png %.grit +#--------------------------------------------------------------------------------- + grit $< -fts -o$* + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/usb-microphone/arm9/source/main.cpp b/examples/usb-microphone/arm9/source/main.cpp new file mode 100644 index 0000000..fc6d81d --- /dev/null +++ b/examples/usb-microphone/arm9/source/main.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static rtos_event_t sVblankEvent; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVblankEvent); +} + +int main(int argc, char* argv[]) +{ + *(vu32*)0x04000000 = 0x10000; + *(vu16*)0x05000000 = 31 << 5; + *(vu16*)0x0400006C = 0; + + mem_setDsCartridgeCpu(EXMEMCNT_SLOT1_CPU_ARM7); + + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + rtos_createEvent(&sVblankEvent); + + while (ipc_getArm7SyncBits() != 7); + + ipc_setArm9SyncBits(6); + + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); + + *(vu16*)0x05000000 = 0; + + *(vu32*)0x04001000 = 0x10000; + *(vu16*)0x05000400 = 0; + *(vu16*)0x0400106C = 0; + + while (true) + { + rtos_waitEvent(&sVblankEvent, true, true); + } + + return 0; +} \ No newline at end of file diff --git a/examples/usb-microphone/icon.bmp b/examples/usb-microphone/icon.bmp new file mode 100644 index 0000000..6d31446 Binary files /dev/null and b/examples/usb-microphone/icon.bmp differ diff --git a/libs/libtwl b/libs/libtwl index 9085848..5988d35 160000 --- a/libs/libtwl +++ b/libs/libtwl @@ -1 +1 @@ -Subproject commit 9085848c68f1d4efcb46ae4e22fd0e39690271d8 +Subproject commit 5988d357acc53a85ec064658d2acfb07bd4c0ee9