Added usb video example

This commit is contained in:
Gericom
2025-12-14 10:47:56 +01:00
parent 3bb550c12e
commit 8b47512f92
29 changed files with 1883 additions and 2 deletions

View File

@@ -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

View File

@@ -0,0 +1,60 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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

View File

@@ -0,0 +1,134 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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
#---------------------------------------------------------------------------------------

View File

@@ -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 . */
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
#pragma once
/// @brief Enum representing the arm7 state.
enum class Arm7State
{
Idle,
ExitRequested
};

View File

@@ -0,0 +1,49 @@
#include "common.h"
#include <libtwl/i2c/i2c.h>
#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;
}
}
}

View File

@@ -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;
};

View File

@@ -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
};

View File

@@ -0,0 +1,217 @@
#include "common.h"
#include <libtwl/i2c/i2c.h>
#include <libtwl/i2c/i2cMcu.h>
#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;
}

View File

@@ -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);

View File

@@ -0,0 +1,6 @@
#pragma once
#include <nds.h>
#include <libtwl/rtos/rtosMutex.h>
extern rtos_mutex_t gCardMutex;
extern rtos_mutex_t gI2cMutex;

View File

@@ -0,0 +1,262 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/rtos/rtosThread.h>
#include <libtwl/rtos/rtosEvent.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/timer/timer.h>
#include <libtwl/sound/sound.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/ipc/ipcFifoSystem.h>
#include <libtwl/sys/sysPower.h>
#include <libtwl/sio/sioRtc.h>
#include <libtwl/sio/sio.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/mem/memSwap.h>
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/spi/spiPmic.h>
#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;
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,153 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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
#---------------------------------------------------------------------------------------

View File

@@ -0,0 +1,84 @@
#include "common.h"
#include <libtwl/sys/swi.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/rtos/rtosEvent.h>
#include <libtwl/ipc/ipcFifoSystem.h>
#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);
}

View File

@@ -0,0 +1,6 @@
#pragma once
void cam_init(bool front);
void cam_stop();
void cam_switch();
void cam_dmaStart(int dma, void* dst);

View File

@@ -0,0 +1,2 @@
#pragma once
#include <nds/ndstypes.h>

Some files were not shown because too many files have changed in this diff Show More