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

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