mirror of
https://github.com/LNH-team/dspico-usb-examples.git
synced 2026-06-09 04:33:30 +02:00
Added usb video example
This commit is contained in:
8
examples/usb-video/arm7/source/Arm7State.h
Normal file
8
examples/usb-video/arm7/source/Arm7State.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Enum representing the arm7 state.
|
||||
enum class Arm7State
|
||||
{
|
||||
Idle,
|
||||
ExitRequested
|
||||
};
|
||||
49
examples/usb-video/arm7/source/CameraIpcService.cpp
Normal file
49
examples/usb-video/arm7/source/CameraIpcService.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
examples/usb-video/arm7/source/CameraIpcService.h
Normal file
16
examples/usb-video/arm7/source/CameraIpcService.h
Normal 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;
|
||||
};
|
||||
10
examples/usb-video/arm7/source/ExitMode.h
Normal file
10
examples/usb-video/arm7/source/ExitMode.h
Normal 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
|
||||
};
|
||||
217
examples/usb-video/arm7/source/cam_ops.cpp
Normal file
217
examples/usb-video/arm7/source/cam_ops.cpp
Normal 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;
|
||||
}
|
||||
13
examples/usb-video/arm7/source/cam_ops.h
Normal file
13
examples/usb-video/arm7/source/cam_ops.h
Normal 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);
|
||||
6
examples/usb-video/arm7/source/common.h
Normal file
6
examples/usb-video/arm7/source/common.h
Normal 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;
|
||||
262
examples/usb-video/arm7/source/main.cpp
Normal file
262
examples/usb-video/arm7/source/main.cpp
Normal 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;
|
||||
}
|
||||
84
examples/usb-video/arm7/source/tusb_config.h
Normal file
84
examples/usb-video/arm7/source/tusb_config.h
Normal 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
|
||||
337
examples/usb-video/arm7/source/usb_descriptors.cpp
Normal file
337
examples/usb-video/arm7/source/usb_descriptors.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
examples/usb-video/arm7/source/usb_descriptors.h
Normal file
5
examples/usb-video/arm7/source/usb_descriptors.h
Normal 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
|
||||
Reference in New Issue
Block a user