From 8b47512f92c66feb6dbff0d2879692c132ec3118 Mon Sep 17 00:00:00 2001 From: Gericom Date: Sun, 14 Dec 2025 10:47:56 +0100 Subject: [PATCH] Added usb video example --- README.md | 7 +- examples/usb-video/Makefile | 60 ++++ examples/usb-video/arm7/Makefile | 134 +++++++ examples/usb-video/arm7/dldi_ds_arm7.ld | 232 ++++++++++++ examples/usb-video/arm7/dldi_ds_arm7.specs | 8 + examples/usb-video/arm7/source/Arm7State.h | 8 + .../arm7/source/CameraIpcService.cpp | 49 +++ .../usb-video/arm7/source/CameraIpcService.h | 16 + examples/usb-video/arm7/source/ExitMode.h | 10 + examples/usb-video/arm7/source/cam_ops.cpp | 217 +++++++++++ examples/usb-video/arm7/source/cam_ops.h | 13 + examples/usb-video/arm7/source/common.h | 6 + examples/usb-video/arm7/source/main.cpp | 262 ++++++++++++++ examples/usb-video/arm7/source/tusb_config.h | 84 +++++ .../usb-video/arm7/source/usb_descriptors.cpp | 337 ++++++++++++++++++ .../usb-video/arm7/source/usb_descriptors.h | 5 + examples/usb-video/arm9/Makefile | 153 ++++++++ examples/usb-video/arm9/source/Camera.cpp | 84 +++++ examples/usb-video/arm9/source/Camera.h | 6 + examples/usb-video/arm9/source/common.h | 2 + examples/usb-video/arm9/source/main.cpp | 86 +++++ examples/usb-video/common/CameraIpcCommand.h | 10 + examples/usb-video/common/IpcChannels.h | 4 + examples/usb-video/common/IpcService.cpp | 11 + examples/usb-video/common/IpcService.h | 19 + .../usb-video/common/ThreadIpcService.cpp | 33 ++ examples/usb-video/common/ThreadIpcService.h | 26 ++ libs/libtwl | 2 +- platform/UsbStringDescriptor.h | 1 + 29 files changed, 1883 insertions(+), 2 deletions(-) create mode 100644 examples/usb-video/Makefile create mode 100644 examples/usb-video/arm7/Makefile create mode 100644 examples/usb-video/arm7/dldi_ds_arm7.ld create mode 100644 examples/usb-video/arm7/dldi_ds_arm7.specs create mode 100644 examples/usb-video/arm7/source/Arm7State.h create mode 100644 examples/usb-video/arm7/source/CameraIpcService.cpp create mode 100644 examples/usb-video/arm7/source/CameraIpcService.h create mode 100644 examples/usb-video/arm7/source/ExitMode.h create mode 100644 examples/usb-video/arm7/source/cam_ops.cpp create mode 100644 examples/usb-video/arm7/source/cam_ops.h create mode 100644 examples/usb-video/arm7/source/common.h create mode 100644 examples/usb-video/arm7/source/main.cpp create mode 100644 examples/usb-video/arm7/source/tusb_config.h create mode 100644 examples/usb-video/arm7/source/usb_descriptors.cpp create mode 100644 examples/usb-video/arm7/source/usb_descriptors.h create mode 100644 examples/usb-video/arm9/Makefile create mode 100644 examples/usb-video/arm9/source/Camera.cpp create mode 100644 examples/usb-video/arm9/source/Camera.h create mode 100644 examples/usb-video/arm9/source/common.h create mode 100644 examples/usb-video/arm9/source/main.cpp create mode 100644 examples/usb-video/common/CameraIpcCommand.h create mode 100644 examples/usb-video/common/IpcChannels.h create mode 100644 examples/usb-video/common/IpcService.cpp create mode 100644 examples/usb-video/common/IpcService.h create mode 100644 examples/usb-video/common/ThreadIpcService.cpp create mode 100644 examples/usb-video/common/ThreadIpcService.h diff --git a/README.md b/README.md index 9d670a4..b155661 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ This repository contains examples of using the DSpico USB port from a DS application. The `examples` folder contains the following examples: -- usb-speaker - Makes the DS/DSi/3DS work as a USB speaker for your PC - mass-storage - Allows access to the DSpico micro SD card over USB +- 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 The `platform` folder contains the tiny usb platform code for the DSpico. @@ -15,3 +16,7 @@ The examples require a pre-calico devkitpro/libnds environment to build. With su ## License The platform code and examples are licensed under the Zlib license. For details, see `LICENSE.txt`. + +## Credits +- [tinyusb](https://github.com/hathach/tinyusb) for the USB library. The examples are also largely based on the examples that come with tinyusb. +- [libcamera](https://github.com/FTC55/libcamera) by [FTC55](https://github.com/FTC55) for camera initialization code diff --git a/examples/usb-video/Makefile b/examples/usb-video/Makefile new file mode 100644 index 0000000..efd8337 --- /dev/null +++ b/examples/usb-video/Makefile @@ -0,0 +1,60 @@ +#--------------------------------------------------------------------------------- +.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 := My Wonderful Homebrew +#GAME_SUBTITLE1 := built with devkitARM +#GAME_SUBTITLE2 := http://devitpro.org + +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-video/arm7/Makefile b/examples/usb-video/arm7/Makefile new file mode 100644 index 0000000..876fe7c --- /dev/null +++ b/examples/usb-video/arm7/Makefile @@ -0,0 +1,134 @@ +#--------------------------------------------------------------------------------- +.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 \ + ../../../platform \ + ../../../libs/tinyusb/src \ + ../../../libs/tinyusb/src/device \ + ../../../libs/tinyusb/src/class/video \ + ../../../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-video/arm7/dldi_ds_arm7.ld b/examples/usb-video/arm7/dldi_ds_arm7.ld new file mode 100644 index 0000000..34f7b22 --- /dev/null +++ b/examples/usb-video/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-video/arm7/dldi_ds_arm7.specs b/examples/usb-video/arm7/dldi_ds_arm7.specs new file mode 100644 index 0000000..8f01248 --- /dev/null +++ b/examples/usb-video/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-video/arm7/source/Arm7State.h b/examples/usb-video/arm7/source/Arm7State.h new file mode 100644 index 0000000..b306ac5 --- /dev/null +++ b/examples/usb-video/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-video/arm7/source/CameraIpcService.cpp b/examples/usb-video/arm7/source/CameraIpcService.cpp new file mode 100644 index 0000000..3041654 --- /dev/null +++ b/examples/usb-video/arm7/source/CameraIpcService.cpp @@ -0,0 +1,49 @@ +#include "common.h" +#include +#include "cam_ops.h" +#include "CameraIpcService.h" + +#define DEFAULT_COARSE_INTEGRATION_TIME 0x10 + +void CameraIpcService::Start() +{ + initParams(DEFAULT_COARSE_INTEGRATION_TIME); + ThreadIpcService::Start(); +} + +void CameraIpcService::HandleMessage(u32 data) +{ + switch (data) + { + case CAMERA_IPC_CMD_INIT_FRONT: + { + aptinaInit(I2C_DEVICE_CAMERA_FRONT); + SendResponseMessage(1); + break; + } + case CAMERA_IPC_CMD_INIT_BACK: + { + aptinaInit(I2C_DEVICE_CAMERA_BACK); + SendResponseMessage(1); + break; + } + case CAMERA_IPC_CMD_ACTIVATE: + { + aptinaActivate(); + SendResponseMessage(1); + break; + } + case CAMERA_IPC_CMD_DEACTIVATE: + { + aptinaDeactivate(); + SendResponseMessage(1); + break; + } + case CAMERA_IPC_CMD_SWITCH: + { + aptinaSwitch(); + SendResponseMessage(1); + break; + } + } +} diff --git a/examples/usb-video/arm7/source/CameraIpcService.h b/examples/usb-video/arm7/source/CameraIpcService.h new file mode 100644 index 0000000..ecb62a7 --- /dev/null +++ b/examples/usb-video/arm7/source/CameraIpcService.h @@ -0,0 +1,16 @@ +#pragma once +#include "ThreadIpcService.h" +#include "CameraIpcCommand.h" +#include "IpcChannels.h" + +class CameraIpcService : public ThreadIpcService +{ + u32 _threadStack[128]; + +public: + CameraIpcService() + : ThreadIpcService(IPC_CHANNEL_CAMERA, 6, _threadStack, sizeof(_threadStack)) { } + + void Start() override; + void HandleMessage(u32 data) override; +}; diff --git a/examples/usb-video/arm7/source/ExitMode.h b/examples/usb-video/arm7/source/ExitMode.h new file mode 100644 index 0000000..1750dd8 --- /dev/null +++ b/examples/usb-video/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-video/arm7/source/cam_ops.cpp b/examples/usb-video/arm7/source/cam_ops.cpp new file mode 100644 index 0000000..caab5ca --- /dev/null +++ b/examples/usb-video/arm7/source/cam_ops.cpp @@ -0,0 +1,217 @@ +#include "common.h" +#include +#include +#include "cam_ops.h" + +static u8 sActiveCamera; +static u8 sCoarseIntegrationTime; + +static void writeI2C(u8 device, u16 index, u16 data) +{ + rtos_lockMutex(&gI2cMutex); + i2c_start(device, I2C_DIRECTION_WRITE); + i2c_write(index >> 8, false); + i2c_write(index & 0xFF, false); + i2c_write(data >> 8, false); + i2c_write(data & 0xFF, true); + rtos_unlockMutex(&gI2cMutex); +} + +static u16 readI2C(u8 device, u16 index) +{ + rtos_lockMutex(&gI2cMutex); + i2c_start(device, I2C_DIRECTION_WRITE); + i2c_write(index >> 8, false); + i2c_write(index & 0xFF, true); + i2c_start(device, I2C_DIRECTION_READ); + u8 msb = i2c_read(false); + u8 lsb = i2c_read(true); + rtos_unlockMutex(&gI2cMutex); + return (msb << 8) | lsb; +} + +static void setApt(u16 index, u16 data) +{ + u16 current = readI2C(sActiveCamera, index); + writeI2C(sActiveCamera, index, current | data); +} + +static void clrApt(u16 index, u16 data) +{ + u16 current = readI2C(sActiveCamera, index); + writeI2C(sActiveCamera, index, current & ~(data)); +} + +static void waitClrApt(u16 reg, u16 mask) +{ + while (readI2C(sActiveCamera, reg) & mask); //evaluates true whenever a bit is set +} + +static void waitSetApt(u16 reg, u16 mask) +{ + while (!(readI2C(sActiveCamera, reg) & mask)); // evaluates true whenever a bit is clear +} + +static void writeMCU(u16 index, u16 data) +{ + writeI2C(sActiveCamera, MCU_ADDRESS, index); + writeI2C(sActiveCamera, MCU_DATA, data); +} + +static u16 readMCU(u16 index) +{ + writeI2C(sActiveCamera, MCU_ADDRESS, index); + u16 read = readI2C(sActiveCamera, MCU_DATA); + return (read); +} + +static void setMCU(u16 index, u16 data) +{ + u16 current = readMCU(index); + writeMCU(index, current | data); +} + +static void waitClrMCU(u16 reg, u16 mask) +{ + while (readMCU(reg) & mask); //evaluates true whenever a bit is set +} + +static void initCamera(u8 device) +{ + sActiveCamera = device; + writeI2C(sActiveCamera, 0x001A, 0x0003); + writeI2C(sActiveCamera, 0x001A, 0x0000); + writeI2C(sActiveCamera, 0x0018, 0x4028); + writeI2C(sActiveCamera, 0x001E, 0x0201); + writeI2C(sActiveCamera, 0x0016, 0x42DF); + waitClrApt(0x0018, 0x4000); + waitSetApt(0x301A, 0x0004); + writeMCU(0x02F0, 0x0000); + writeMCU(0x02F2, 0x0210); + writeMCU(0x02F4, 0x001A); + writeMCU(0x2145, 0x02F4); + writeMCU(0xA134, 0x0001); + setMCU(0xA115, 0x0002); + writeMCU(0x2755, 0x0002); + writeMCU(0x2757, 0x0002); + writeI2C(sActiveCamera, 0x0014, 0x2145); + writeI2C(sActiveCamera, 0x0010, 0x0111); + writeI2C(sActiveCamera, 0x0012, 0x0000); + writeI2C(sActiveCamera, 0x0014, 0x244B); + writeI2C(sActiveCamera, 0x0014, 0x304B); + waitSetApt(0x0014, 0x8000); + clrApt(0x0014, 0x0001); + writeMCU(0x2703, 0x0100); + writeMCU(0x2705, 0x00C0); + writeMCU(0x2707, 0x0280); + writeMCU(0x2709, 0x01E0); + writeMCU(0x2715, 0x0001); + writeMCU(0x2719, 0x001A); + writeMCU(0x271B, 0x006B); + writeMCU(0x271D, 0x006B); + writeMCU(0x271F, 0x02C0); + writeMCU(0x2721, 0x034B); + writeMCU(0xA20B, 0x0000); + writeMCU(0xA20C, 0x0006); + writeMCU(0x272B, 0x0001); + writeMCU(0x272F, 0x001A); + writeMCU(0x2731, 0x006B); + writeMCU(0x2733, 0x006B); + writeMCU(0x2735, 0x02C0); + writeMCU(0x2737, 0x034B); + setApt(0x3210, 0x0008); + writeMCU(0xA208, 0x0000); + writeMCU(0xA24C, 0x0020); + writeMCU(0xA24F, 0x0070); + if (sActiveCamera == I2C_DEVICE_CAMERA_FRONT) + { + writeMCU(0x2717, 0x0024); + writeMCU(0x272D, 0x0024); + } + else + { + writeMCU(0x2717, 0x0025); + writeMCU(0x272D, 0x0025); + } + if (sActiveCamera == I2C_DEVICE_CAMERA_FRONT) + { + writeMCU(0xA202, 0x0022); + writeMCU(0xA203, 0x00BB); + } + else + { + writeMCU(0xA202, 0x0000); + writeMCU(0xA203, 0x00FF); + } + setApt(0x0016, 0x0020); + writeMCU(0xA115, 0x0072); + writeMCU(0xA11F, 0x0001); + if (sActiveCamera == I2C_DEVICE_CAMERA_FRONT) + { + writeI2C(sActiveCamera, 0x326C, 0x0900); + writeMCU(0xAB22, 0x0001); + } + else + { + writeI2C(sActiveCamera, 0x326C, 0x1000); + writeMCU(0xAB22, 0x0002); + } + writeMCU(0xA103, 0x0006); + waitClrMCU(0xA103, 0x000F); + writeMCU(0xA103, 0x0005); + waitClrMCU(0xA103, 0x000F); + + //for reasons that are way beyond me, if I don't do this then the image gets messed up. RIP all of my color translation code. (mostly same code as aptinaDeactivate) + clrApt(0x001A, 0x0200); + setApt(0x0018, 0x0001); + waitSetApt(0x0018, 0x4000); + waitClrApt(0x301A, 0x0004); +} + +void aptinaInit(u8 camera) //might want to init both at the same time instead as NO$ suggests, TODO +{ + initCamera(I2C_DEVICE_CAMERA_BACK); + initCamera(I2C_DEVICE_CAMERA_FRONT); + sActiveCamera = camera; //we set the current camera to the one specified +} + +void aptinaActivate() +{ + clrApt(0x0018, 0x0001); + waitClrApt(0x0018, 0x4000); + waitSetApt(0x301A, 0x0004); + writeI2C(sActiveCamera, 0x3012, sCoarseIntegrationTime); + setApt(0x001A, 0x0200); + + if (sActiveCamera == I2C_DEVICE_CAMERA_BACK) + { + mcu_writeReg(MCU_REG_CAMERA, 0); + } +} + +void aptinaDeactivate() +{ + clrApt(0x001A, 0x0200); + setApt(0x0018, 0x0001); + waitSetApt(0x0018, 0x4000); + waitClrApt(0x301A, 0x0004); + + if (sActiveCamera == I2C_DEVICE_CAMERA_BACK) + { + mcu_writeReg(MCU_REG_CAMERA, 1); + } +} + +void aptinaSwitch() +{ + aptinaDeactivate(); + sActiveCamera = sActiveCamera == I2C_DEVICE_CAMERA_BACK + ? I2C_DEVICE_CAMERA_FRONT + : I2C_DEVICE_CAMERA_BACK; + aptinaActivate(); +} + +void initParams(u8 coarse_int_time) +{ + sCoarseIntegrationTime = coarse_int_time; +} \ No newline at end of file diff --git a/examples/usb-video/arm7/source/cam_ops.h b/examples/usb-video/arm7/source/cam_ops.h new file mode 100644 index 0000000..125cd85 --- /dev/null +++ b/examples/usb-video/arm7/source/cam_ops.h @@ -0,0 +1,13 @@ +#pragma once + +#define MCU_ADDRESS 0x098C +#define MCU_DATA 0x0990 +#define STANDBY_CNT 0x0018 + +#define STANDBY_DONE 0x4000 + +void aptinaInit(u8 camera); +void aptinaActivate(); +void aptinaDeactivate(); +void aptinaSwitch(); +void initParams(u8 coarse_int_time); diff --git a/examples/usb-video/arm7/source/common.h b/examples/usb-video/arm7/source/common.h new file mode 100644 index 0000000..2dc083d --- /dev/null +++ b/examples/usb-video/arm7/source/common.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include + +extern rtos_mutex_t gCardMutex; +extern rtos_mutex_t gI2cMutex; diff --git a/examples/usb-video/arm7/source/main.cpp b/examples/usb-video/arm7/source/main.cpp new file mode 100644 index 0000000..c61d52e --- /dev/null +++ b/examples/usb-video/arm7/source/main.cpp @@ -0,0 +1,262 @@ +#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 "tusb_config.h" +#include "usb_descriptors.h" +#include "CameraIpcService.h" + +#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT * 2) +#define NUMBER_OF_FRAME_BUFFERS 3 + +rtos_mutex_t gCardMutex; +rtos_mutex_t gI2cMutex; + +static CameraIpcService sCameraIpcService; + +static rtos_thread_t sUsbThread; +static u32 sUsbThreadStack[512]; + +static rtos_event_t sVBlankEvent; +static rtos_event_t sCaptureEvent; +static ExitMode sExitMode; +static Arm7State sState; +static volatile u8 sMcuIrqFlag = false; + +static u32 sCurrentFrame = 0; +static vu32 sCapturedFrame = 0xFFFFFFFF; +static u8* sFrameBuffer = nullptr; + +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 + rtos_lockMutex(&gI2cMutex); + u32 irqMask = mcu_getIrqMask(); + rtos_unlockMutex(&gI2cMutex); + 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 captureIpcMessageHandler(u32 channel, u32 data, void* arg) +{ + if (sFrameBuffer == nullptr) + { + sFrameBuffer = (u8*)(data << 5); + } + else + { + sCapturedFrame++; + rtos_signalEvent(&sCaptureEvent); + } +} + +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(127); + snd_setMasterEnable(true); + + initializeVBlankIrq(); + + if (isDSiMode()) + { + sCameraIpcService.Start(); + rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); + rtos_enableIrq2Mask(RTOS_IRQ2_MCU); + } + + rtos_createEvent(&sCaptureEvent); + ipc_setChannelHandler(IPC_CHANNEL_CAPTURE, captureIpcMessageHandler, nullptr); + + ipc_setArm7SyncBits(7); + while (ipc_getArm9SyncBits() != 6) + { + rtos_waitEvent(&sVBlankEvent, true, true); + } + + // request two frames in advance + ipc_sendFifoMessage(IPC_CHANNEL_CAPTURE, 0); + ipc_sendFifoMessage(IPC_CHANNEL_CAPTURE, 0); + + tusb_rhport_init_t dev_init = + { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(0, &dev_init); + + rtos_createThread(&sUsbThread, 8, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack)); + rtos_wakeupThread(&sUsbThread); +} + +static void updateArm7IdleState() +{ + checkMcuIrq(); + + if (sState == Arm7State::ExitRequested) + { + snd_setMasterVolume(0); // mute sound + } +} + +static bool performExit(ExitMode exitMode) +{ + switch (exitMode) + { + case ExitMode::Reset: + { + rtos_lockMutex(&gI2cMutex); + mcu_setWarmBootFlag(true); + mcu_hardReset(); + rtos_unlockMutex(&gI2cMutex); + 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; + } + } +} + +void tud_video_frame_xfer_complete_cb(u8 ctl_idx, u8 stm_idx) +{ + (void) ctl_idx; + (void) stm_idx; +} + +int tud_video_commit_cb(u8 ctl_idx, u8 stm_idx, const video_probe_and_commit_control_t* parameters) +{ + (void) ctl_idx; + (void) stm_idx; + return VIDEO_ERROR_NONE; +} + +int main() +{ + sState = Arm7State::Idle; + initializeArm7(); + + while (true) + { + if (tud_video_n_streaming(0, 0)) + { + rtos_waitEvent(&sCaptureEvent, false, true); + while (sCurrentFrame != (sCapturedFrame + 1)) + { + updateArm7(); + tud_video_n_frame_xfer(0, 0, + sFrameBuffer + (sCurrentFrame % NUMBER_OF_FRAME_BUFFERS) * FRAME_SIZE, FRAME_SIZE); + sCurrentFrame++; + ipc_sendFifoMessage(IPC_CHANNEL_CAPTURE, 0); + } + } + else + { + rtos_waitEvent(&sVBlankEvent, true, true); + updateArm7(); + } + } + + return 0; +} diff --git a/examples/usb-video/arm7/source/tusb_config.h b/examples/usb-video/arm7/source/tusb_config.h new file mode 100644 index 0000000..696c6d9 --- /dev/null +++ b/examples/usb-video/arm7/source/tusb_config.h @@ -0,0 +1,84 @@ +#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_VIDEO 1 + +// The number of video streaming interfaces +#define CFG_TUD_VIDEO_STREAMING 1 + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 512 + +// use bulk endpoint for streaming interface + #define CFG_TUD_VIDEO_STREAMING_BULK 0 + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/examples/usb-video/arm7/source/usb_descriptors.cpp b/examples/usb-video/arm7/source/usb_descriptors.cpp new file mode 100644 index 0000000..add3d0d --- /dev/null +++ b/examples/usb-video/arm7/source/usb_descriptors.cpp @@ -0,0 +1,337 @@ +#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] VIDEO | AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VIDEO, 5) | _PID_MAP(VENDOR, 6) ) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 + +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_UVC_CONTROL, + STRID_UVC_STREAMING +}; + +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 = USB_BCD, + + // Use Interface Association Descriptor (IAD) for Video + // 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 = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = 0x01 + }; + + return (const u8*)&deviceDescriptor; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +/* Time stamp base clock. It is a deprecated parameter. */ +#define UVC_CLOCK_FREQUENCY 27000000 + +/* video capture path */ +#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01 +#define UVC_ENTITY_CAP_PROCESSING 0x02 +#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x03 + +enum +{ + ITF_NUM_VIDEO_CONTROL, + ITF_NUM_VIDEO_STREAMING, + ITF_NUM_TOTAL +}; + +#define EPNUM_VIDEO_IN 0x81 + +#define USE_ISO_STREAMING (!CFG_TUD_VIDEO_STREAMING_BULK) + +typedef struct TU_ATTR_PACKED +{ + tusb_desc_interface_t itf; + tusb_desc_video_control_header_1itf_t header; + tusb_desc_video_control_camera_terminal_t camera_terminal; + u8 processing_unit[13]; + tusb_desc_video_control_output_terminal_t output_terminal; +} uvc_control_desc_t; + +/* Windows support YUY2 and NV12 + * https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview */ + +typedef struct TU_ATTR_PACKED +{ + tusb_desc_interface_t itf; + tusb_desc_video_streaming_input_header_1byte_t header; + tusb_desc_video_format_uncompressed_t format; + tusb_desc_video_frame_uncompressed_2int_t frame; + tusb_desc_video_streaming_color_matching_t color; + +#if USE_ISO_STREAMING + // For ISO streaming, USB spec requires to alternate interface + tusb_desc_interface_t itf_alt; +#endif + + tusb_desc_endpoint_t ep; +} uvc_streaming_desc_t; + +typedef struct TU_ATTR_PACKED +{ + tusb_desc_configuration_t config; + tusb_desc_interface_assoc_t iad; + uvc_control_desc_t video_control; + uvc_streaming_desc_t video_streaming; +} uvc_cfg_desc_t; + +const u8* tud_descriptor_configuration_cb(u8 index) +{ + static const uvc_cfg_desc_t configurationDescriptor = + { + .config = { + .bLength = sizeof(tusb_desc_configuration_t), + .bDescriptorType = TUSB_DESC_CONFIGURATION, + + .wTotalLength = sizeof(uvc_cfg_desc_t), + .bNumInterfaces = ITF_NUM_TOTAL, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = TU_BIT(7), + .bMaxPower = 100 / 2 + }, + .iad = { + .bLength = sizeof(tusb_desc_interface_assoc_t), + .bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION, + + .bFirstInterface = ITF_NUM_VIDEO_CONTROL, + .bInterfaceCount = 2, + .bFunctionClass = TUSB_CLASS_VIDEO, + .bFunctionSubClass = VIDEO_SUBCLASS_INTERFACE_COLLECTION, + .bFunctionProtocol = VIDEO_ITF_PROTOCOL_UNDEFINED, + .iFunction = 0 + }, + + .video_control = { + .itf = { + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = ITF_NUM_VIDEO_CONTROL, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_CONTROL, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = STRID_UVC_CONTROL + }, + .header = { + .bLength = sizeof(tusb_desc_video_control_header_1itf_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_HEADER, + + .bcdUVC = VIDEO_BCD_1_50, + .wTotalLength = sizeof(uvc_control_desc_t) - sizeof(tusb_desc_interface_t)/* - sizeof(tusb_desc_endpoint_t) - 5*/, // CS VC descriptors only + .dwClockFrequency = UVC_CLOCK_FREQUENCY, + .bInCollection = 1, + .baInterfaceNr = { ITF_NUM_VIDEO_STREAMING } + }, + .camera_terminal = { + .bLength = sizeof(tusb_desc_video_control_camera_terminal_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_INPUT_TERMINAL, + + .bTerminalID = UVC_ENTITY_CAP_INPUT_TERMINAL, + .wTerminalType = VIDEO_ITT_CAMERA, + .bAssocTerminal = 0, + .iTerminal = 0, + .wObjectiveFocalLengthMin = 0, + .wObjectiveFocalLengthMax = 0, + .wOcularFocalLength = 0, + .bControlSize = 3, + .bmControls = { 0, 0, 0 } + }, + .processing_unit = { 0x0D, 0x24, 0x05, UVC_ENTITY_CAP_PROCESSING, UVC_ENTITY_CAP_INPUT_TERMINAL, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + .output_terminal = { + .bLength = sizeof(tusb_desc_video_control_output_terminal_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, + + .bTerminalID = UVC_ENTITY_CAP_OUTPUT_TERMINAL, + .wTerminalType = VIDEO_TT_STREAMING, + .bAssocTerminal = 0, + .bSourceID = UVC_ENTITY_CAP_PROCESSING, + .iTerminal = 0 + } + }, + + .video_streaming = { + .itf = { + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = ITF_NUM_VIDEO_STREAMING, + .bAlternateSetting = 0, + .bNumEndpoints = CFG_TUD_VIDEO_STREAMING_BULK, // bulk 1, iso 0 + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = STRID_UVC_STREAMING + }, + .header = { + .bLength = sizeof(tusb_desc_video_streaming_input_header_1byte_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_INPUT_HEADER, + + .bNumFormats = 1, + .wTotalLength = sizeof(uvc_streaming_desc_t) - sizeof(tusb_desc_interface_t) + - sizeof(tusb_desc_endpoint_t) - (USE_ISO_STREAMING ? sizeof(tusb_desc_interface_t) : 0) , // CS VS descriptors only + .bEndpointAddress = EPNUM_VIDEO_IN, + .bmInfo = 0, + .bTerminalLink = UVC_ENTITY_CAP_OUTPUT_TERMINAL, + .bStillCaptureMethod = 0, + .bTriggerSupport = 0, + .bTriggerUsage = 0, + .bControlSize = 1, + .bmaControls = { 0 } + }, + .format = { + .bLength = sizeof(tusb_desc_video_format_uncompressed_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, + .bFormatIndex = 1, // 1-based index + .bNumFrameDescriptors = 1, + .guidFormat = { TUD_VIDEO_GUID_YUY2 }, + .bBitsPerPixel = 16, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterlaceFlags = 0, + .bCopyProtect = 0 + }, + .frame = { + .bLength = sizeof(tusb_desc_video_frame_uncompressed_2int_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, + .bFrameIndex = 1, // 1-based index + .bmCapabilities = 0, + .wWidth = FRAME_WIDTH, + .wHeight = FRAME_HEIGHT, + .dwMinBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * 1, + .dwMaxBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * FRAME_RATE, + .dwMaxVideoFrameBufferSize = FRAME_WIDTH * FRAME_HEIGHT * 16 / 8, + .dwDefaultFrameInterval = 10000000 / FRAME_RATE, + .bFrameIntervalType = 2, // 2 discrete + .dwFrameInterval = { + 10000000 / FRAME_RATE, + 10000000 + } + }, + .color = { + .bLength = sizeof(tusb_desc_video_streaming_color_matching_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_COLORFORMAT, + + .bColorPrimaries = VIDEO_COLOR_PRIMARIES_BT709, + .bTransferCharacteristics = VIDEO_COLOR_XFER_CH_BT709, + .bMatrixCoefficients = VIDEO_COLOR_COEF_BT709 + }, + +#if USE_ISO_STREAMING + .itf_alt = { + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = ITF_NUM_VIDEO_STREAMING, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = STRID_UVC_STREAMING + }, +#endif + + .ep = { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + + .bEndpointAddress = EPNUM_VIDEO_IN, + .bmAttributes = { + .xfer = CFG_TUD_VIDEO_STREAMING_BULK ? TUSB_XFER_BULK : TUSB_XFER_ISOCHRONOUS, + .sync = CFG_TUD_VIDEO_STREAMING_BULK ? 0 : 1 // asynchronous + }, + .wMaxPacketSize = CFG_TUD_VIDEO_STREAMING_BULK ? 64 : CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE, + .bInterval = 1 + } + } + }; + + return (const u8*)&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 Camera Example"); + } + case STRID_SERIAL: + { + return USB_STRING_DESCRIPTOR(u"123456789"); + } + case STRID_UVC_CONTROL: + { + return USB_STRING_DESCRIPTOR(u"UVC Control"); + } + case STRID_UVC_STREAMING: + { + return USB_STRING_DESCRIPTOR(u"UVC Streaming"); + } + default: + { + return nullptr; + } + } +} diff --git a/examples/usb-video/arm7/source/usb_descriptors.h b/examples/usb-video/arm7/source/usb_descriptors.h new file mode 100644 index 0000000..2ef9685 --- /dev/null +++ b/examples/usb-video/arm7/source/usb_descriptors.h @@ -0,0 +1,5 @@ +#pragma once + +#define FRAME_WIDTH 256 +#define FRAME_HEIGHT 192 +#define FRAME_RATE 10 // just an indication, we won't actually reach it diff --git a/examples/usb-video/arm9/Makefile b/examples/usb-video/arm9/Makefile new file mode 100644 index 0000000..0874bd0 --- /dev/null +++ b/examples/usb-video/arm9/Makefile @@ -0,0 +1,153 @@ +#--------------------------------------------------------------------------------- +.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 \ + 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-video/arm9/source/Camera.cpp b/examples/usb-video/arm9/source/Camera.cpp new file mode 100644 index 0000000..126713a --- /dev/null +++ b/examples/usb-video/arm9/source/Camera.cpp @@ -0,0 +1,84 @@ +#include "common.h" +#include +#include +#include +#include +#include "IpcChannels.h" +#include "CameraIpcCommand.h" +#include "Camera.h" + +#define REG_SCFG_CLK (*(vu32*)0x04004004) +#define REG_CAM_MCNT (*(vu16*)0x04004200) +#define REG_CAM_CNT (*(vu16*)0x04004202) +#define REG_CAM_DAT (*(vu32*)0x04004204) + +static rtos_event_t sEvent; + +static void ipcMessageHandler(u32 channel, u32 data, void* arg) +{ + rtos_signalEvent(&sEvent); +} + +void cam_init(bool front) +{ + rtos_createEvent(&sEvent); + ipc_setChannelHandler(IPC_CHANNEL_CAMERA, ipcMessageHandler, nullptr); + + REG_SCFG_CLK |= 0x0004; + REG_CAM_MCNT = 0x0000; + swi_waitByLoop(0x1E); + REG_SCFG_CLK |= 0x0100; + swi_waitByLoop(0x1E); + REG_CAM_MCNT = 0x0022; + swi_waitByLoop(0x2008); + REG_SCFG_CLK &= ~0x0100; + REG_CAM_CNT &= ~0x8000; + REG_CAM_CNT |= 0x0020; + REG_CAM_CNT = (REG_CAM_CNT & ~0x0300) | 0x0200; + REG_CAM_CNT |= 0x0400; + REG_CAM_CNT |= 0x0800; + REG_SCFG_CLK |= 0x0100; + swi_waitByLoop(0x14); + + ipc_sendFifoMessage(IPC_CHANNEL_CAMERA, front ? CAMERA_IPC_CMD_INIT_FRONT : CAMERA_IPC_CMD_INIT_BACK); + rtos_waitEvent(&sEvent, false, true); + + REG_SCFG_CLK &= ~0x0100; + REG_SCFG_CLK |= 0x0100; + + ipc_sendFifoMessage(IPC_CHANNEL_CAMERA, CAMERA_IPC_CMD_ACTIVATE); + rtos_waitEvent(&sEvent, false, true); +} + +void cam_stop() +{ + ipc_sendFifoMessage(IPC_CHANNEL_CAMERA, CAMERA_IPC_CMD_DEACTIVATE); + rtos_waitEvent(&sEvent, false, true); +} + +void cam_switch() +{ + ipc_sendFifoMessage(IPC_CHANNEL_CAMERA, CAMERA_IPC_CMD_SWITCH); + rtos_waitEvent(&sEvent, false, true); +} + +void cam_dmaStart(int dma, void* dst) +{ + // REG_CAM_CNT |= 0x2000; + REG_CAM_CNT &= ~0x2000; + REG_CAM_CNT = (REG_CAM_CNT & ~0x000F) | 0x0003; + REG_CAM_CNT |= 0x0020; + REG_CAM_CNT |= 0x8000; + + dma_twl_config_t dmaConfig = + { + .src = (const void*)0x04004204, + .dst = dst, + .totalWordCount = 0x6000, + .wordCount = 0x200, + .blockInterval = 2, + .fillData = 0, + .control = 0x8B044000 + }; + dma_twlSetParams(dma, &dmaConfig); +} diff --git a/examples/usb-video/arm9/source/Camera.h b/examples/usb-video/arm9/source/Camera.h new file mode 100644 index 0000000..4c1f4cf --- /dev/null +++ b/examples/usb-video/arm9/source/Camera.h @@ -0,0 +1,6 @@ +#pragma once + +void cam_init(bool front); +void cam_stop(); +void cam_switch(); +void cam_dmaStart(int dma, void* dst); diff --git a/examples/usb-video/arm9/source/common.h b/examples/usb-video/arm9/source/common.h new file mode 100644 index 0000000..c717672 --- /dev/null +++ b/examples/usb-video/arm9/source/common.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/examples/usb-video/arm9/source/main.cpp b/examples/usb-video/arm9/source/main.cpp new file mode 100644 index 0000000..97e98e1 --- /dev/null +++ b/examples/usb-video/arm9/source/main.cpp @@ -0,0 +1,86 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Camera.h" +#include "IpcChannels.h" + +#define FRAME_SIZE (256 * 192 * 2) +#define NUMBER_OF_FRAME_BUFFERS 3 + +static rtos_event_t sVblankEvent; +static rtos_event_t sCaptureEvent; + +static u32 sCurrentFrame = 0; +static vu32 sRequestedFrame = 0; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVblankEvent); +} + +static void captureIpcMessageHandler(u32 channel, u32 data, void* arg) +{ + sRequestedFrame++; + rtos_signalEvent(&sCaptureEvent); +} + +int main(int argc, char* argv[]) +{ + *(vu32*)0x04000000 = 0x10000; + *(vu16*)0x05000000 = 31 << 5; + *(vu16*)0x0400006C = 0; + + mem_setDsCartridgeCpu(EXMEMCNT_SLOT1_CPU_ARM7); + mem_setMainMemoryPriority(EXMEMCNT_MAIN_MEM_PRIO_ARM7); + + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + rtos_createEvent(&sVblankEvent); + + while (ipc_getArm7SyncBits() != 7); + + *(vu16*)0x05000000 = 31 << 10; + cam_init(false); + *(vu16*)0x05000000 = 31; + + rtos_createEvent(&sCaptureEvent); + ipc_setChannelHandler(IPC_CHANNEL_CAPTURE, captureIpcMessageHandler, nullptr); + + ipc_setArm9SyncBits(6); + + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); + + mem_setVramAMapping(MEM_VRAM_AB_LCDC); + REG_DISPCNT = 0x20000; + + u8* frameBuffer = new(std::align_val_t(32)) u8[FRAME_SIZE * NUMBER_OF_FRAME_BUFFERS]; + ipc_sendFifoMessage(IPC_CHANNEL_CAPTURE, (u32)frameBuffer >> 5); + + while (true) + { + rtos_waitEvent(&sCaptureEvent, false, true); + while (sCurrentFrame != sRequestedFrame) + { + cam_dmaStart(1, frameBuffer + FRAME_SIZE * (sCurrentFrame % 3)); + dma_twlWait(1); + sCurrentFrame++; + ipc_sendFifoMessage(IPC_CHANNEL_CAPTURE, 0); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/usb-video/common/CameraIpcCommand.h b/examples/usb-video/common/CameraIpcCommand.h new file mode 100644 index 0000000..d598416 --- /dev/null +++ b/examples/usb-video/common/CameraIpcCommand.h @@ -0,0 +1,10 @@ +#pragma once + +typedef enum +{ + CAMERA_IPC_CMD_INIT_FRONT, + CAMERA_IPC_CMD_INIT_BACK, + CAMERA_IPC_CMD_ACTIVATE, + CAMERA_IPC_CMD_DEACTIVATE, + CAMERA_IPC_CMD_SWITCH +} CameraIpcCommand; diff --git a/examples/usb-video/common/IpcChannels.h b/examples/usb-video/common/IpcChannels.h new file mode 100644 index 0000000..31e68e1 --- /dev/null +++ b/examples/usb-video/common/IpcChannels.h @@ -0,0 +1,4 @@ +#pragma once + +#define IPC_CHANNEL_CAMERA 16 +#define IPC_CHANNEL_CAPTURE 17 diff --git a/examples/usb-video/common/IpcService.cpp b/examples/usb-video/common/IpcService.cpp new file mode 100644 index 0000000..815bed7 --- /dev/null +++ b/examples/usb-video/common/IpcService.cpp @@ -0,0 +1,11 @@ +#include "common.h" +#include +#include "IpcService.h" + +void IpcService::Start() +{ + ipc_setChannelHandler(_ipcChannel, [] (u32 channel, u32 data, void* arg) + { + static_cast(arg)->OnMessageReceived(data); + }, this); +} diff --git a/examples/usb-video/common/IpcService.h b/examples/usb-video/common/IpcService.h new file mode 100644 index 0000000..16519ea --- /dev/null +++ b/examples/usb-video/common/IpcService.h @@ -0,0 +1,19 @@ +#pragma once +#include + +class IpcService +{ + const u32 _ipcChannel; +protected: + explicit IpcService(u32 ipcChannel) + : _ipcChannel(ipcChannel) { } + + void SendResponseMessage(u32 data) const + { + ipc_sendFifoMessage(_ipcChannel, data); + } + +public: + virtual void Start(); + virtual void OnMessageReceived(u32 data) = 0; +}; diff --git a/examples/usb-video/common/ThreadIpcService.cpp b/examples/usb-video/common/ThreadIpcService.cpp new file mode 100644 index 0000000..f1d02ef --- /dev/null +++ b/examples/usb-video/common/ThreadIpcService.cpp @@ -0,0 +1,33 @@ +#include "common.h" +#include "ThreadIpcService.h" + +void ThreadIpcService::ThreadMain() +{ + while (true) + { + rtos_waitEvent(&_event, false, true); + if (_messageValid) + { + _messageValid = false; + HandleMessage(_message); + } + } +} + +void ThreadIpcService::Start() +{ + rtos_createEvent(&_event); + rtos_createThread(&_thread, 5, [] (void* arg) + { + static_cast(arg)->ThreadMain(); + }, this, _stack, _stackSize); + rtos_wakeupThread(&_thread); + IpcService::Start(); +} + +void ThreadIpcService::OnMessageReceived(u32 data) +{ + _message = data; + _messageValid = true; + rtos_signalEvent(&_event); +} diff --git a/examples/usb-video/common/ThreadIpcService.h b/examples/usb-video/common/ThreadIpcService.h new file mode 100644 index 0000000..a441d25 --- /dev/null +++ b/examples/usb-video/common/ThreadIpcService.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include "IpcService.h" + +class ThreadIpcService : public IpcService +{ + rtos_thread_t _thread; + rtos_event_t _event; + u32* _stack; + u32 _stackSize; + u8 _priority; + bool _messageValid = false; + u32 _message; + + void ThreadMain(); + +public: + ThreadIpcService(u32 ipcChannel, u8 priority, u32* stack, u32 stackSize) + : IpcService(ipcChannel), _stack(stack), _stackSize(stackSize), _priority(priority) { } + + void Start() override; + void OnMessageReceived(u32 data) override; + + virtual void HandleMessage(u32 data); +}; diff --git a/libs/libtwl b/libs/libtwl index 9f75e53..60a213b 160000 --- a/libs/libtwl +++ b/libs/libtwl @@ -1 +1 @@ -Subproject commit 9f75e53c30889f0227199760a9e3a2aa9e8dca0c +Subproject commit 60a213bd422bb063b545ff3799dbfa795371094b diff --git a/platform/UsbStringDescriptor.h b/platform/UsbStringDescriptor.h index d822901..d7382fb 100644 --- a/platform/UsbStringDescriptor.h +++ b/platform/UsbStringDescriptor.h @@ -1,4 +1,5 @@ #pragma once +#include #include "tusb.h" template