Initial Commit

This commit is contained in:
zongor 2023-02-04 10:58:14 -05:00
parent 176cff5f2a
commit fdafcdb95b
12 changed files with 1406 additions and 2 deletions

26
CMakeLists.txt Normal file
View File

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.12)
add_executable(arducam_firmware_uvc)
target_sources(arducam_firmware_uvc PUBLIC
${CMAKE_CURRENT_LIST_DIR}/arducam/arducam.c
${CMAKE_CURRENT_LIST_DIR}/main.c
${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c
)
# Make sure TinyUSB can find tusb_config.h
target_include_directories(arducam_firmware_uvc PUBLIC
${CMAKE_CURRENT_LIST_DIR})
pico_generate_pio_header(arducam_firmware_uvc ${CMAKE_CURRENT_LIST_DIR}/image.pio)
target_link_libraries(arducam_firmware_uvc
pico_stdlib
tinyusb_device
tinyusb_board
hardware_dma
hardware_i2c
hardware_pio
hardware_pwm
)
pico_add_extra_outputs(arducam_firmware_uvc)

View File

@ -1,3 +1,13 @@
# HM01B0_Firmware
# Raspberry Pi Pico + Arducam hm01b0 UVC Firmware
Raspberry Pi Pico + Arducam hm01b0 UVC Firmware
A very simple firmware for the arducam hm01b0 & raspberry pi pico that uses usb uvc protocol.
It honestly barely works, but the tinyusb implementation of uvc is still a work in progress so theoretically this will get better as tinyusb's uvc develops.
One consistant way to get this to work is using OBS Studio's virtual camera, a lot of other webcam programs do not play nice with it.
This has only been tested on linux, although it should work anywhere that the tinyusb uvc library works.
Also, during testing I had this weird issue where pipewire would randomly eat up all of my ram, so I decided to keep the shell script in this repo for reference in case you run into the same issue.
Might have to mess with the CMakeLists a bit to get it to work in a standalone way but pulling the [pico-examples](https://github.com/raspberrypi/pico-examples) repo and sticking it in `pico-examples/usb/device/` will for sure make it work.

337
arducam/arducam.c Normal file
View File

@ -0,0 +1,337 @@
#include <stdio.h>
#include "pico/stdlib.h"
#include "arducam.h"
#include "ov2640_init.h"
#include "hm01b0_init.h"
#include "hardware/dma.h"
#include "hardware/i2c.h"
#include "hardware/pwm.h"
#include "image.pio.h"
int PIN_LED = 25;
int PIN_CAM_SIOC = 5; // I2C0 SCL
int PIN_CAM_SIOD = 4; // I2C0 SDA
int PIN_CAM_RESETB = 2;
int PIN_CAM_XCLK = 3;
int PIN_CAM_VSYNC = 16; //GP15 hsync GP14 pixel clock
int PIN_CAM_Y2_PIO_BASE = 6; // data GPIO6
#if defined (SOFTWARE_I2C)
#define SCCB_SIC_H() gpio_put(PIN_CAM_SIOC,1) //SCL H
#define SCCB_SIC_L() gpio_put(PIN_CAM_SIOC,0) //SCL H
#define SCCB_SID_H() gpio_put(PIN_CAM_SIOD,1) //SDA H
#define SCCB_SID_L() gpio_put(PIN_CAM_SIOD,0) //SDA H
#define SCCB_DATA_IN gpio_set_dir(PIN_CAM_SIOD, GPIO_IN);
#define SCCB_DATA_OUT gpio_set_dir(PIN_CAM_SIOD, GPIO_OUT);
#define SCCB_SID_STATE gpio_get(PIN_CAM_SIOD)
void sccb_bus_init(void);
void sccb_bus_start(void);
void sccb_bus_stop(void);
void sccb_bus_send_noack(void);
void sccb_bus_send_ack(void);
unsigned char sccb_bus_write_byte(unsigned char data);
unsigned char sccb_bus_read_byte(void);
unsigned char I2C_TIM;
void sccb_bus_start(void)
{
SCCB_SID_H();
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
SCCB_SID_L();
sleep_us(I2C_TIM);
SCCB_SIC_L();
sleep_us(I2C_TIM);
}
void sccb_bus_stop(void)
{
SCCB_SID_L();
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
SCCB_SID_H();
sleep_us(I2C_TIM);
}
void sccb_bus_send_noack(void)
{
SCCB_SID_H();
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
SCCB_SIC_L();
sleep_us(I2C_TIM);
SCCB_SID_L();
sleep_us(I2C_TIM);
}
void sccb_bus_send_ack(void)
{
SCCB_SID_L();
sleep_us(I2C_TIM);
SCCB_SIC_L();
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
SCCB_SIC_L();
sleep_us(I2C_TIM);
SCCB_SID_L();
sleep_us(I2C_TIM);
}
unsigned char sccb_bus_write_byte(unsigned char data)
{
unsigned char i;
unsigned char tem;
for(i = 0; i < 8; i++)
{
if((data<<i) & 0x80)
{
SCCB_SID_H();
}
else
{
SCCB_SID_L();
}
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
SCCB_SIC_L();
}
SCCB_DATA_IN;
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
if(SCCB_SID_STATE)
{
tem = 0;
}
else
{
tem = 1;
}
SCCB_SIC_L();
sleep_us(I2C_TIM);
SCCB_DATA_OUT;
return tem;
}
unsigned char sccb_bus_read_byte(void)
{
unsigned char i;
unsigned char read = 0;
SCCB_DATA_IN;
for(i = 8; i > 0; i--)
{
sleep_us(I2C_TIM);
SCCB_SIC_H();
sleep_us(I2C_TIM);
read = read << 1;
if(SCCB_SID_STATE)
{
read += 1;
}
SCCB_SIC_L();
sleep_us(I2C_TIM);
}
SCCB_DATA_OUT;
return read;
}
unsigned char wrSensorReg16_8( uint8_t slave_address, int regID, int regDat)
{
sccb_bus_start();
if(0==sccb_bus_write_byte(slave_address<<1))
{
sccb_bus_stop();
return(0);
}
sleep_us(10);
if(0==sccb_bus_write_byte(regID>>8))
{
sccb_bus_stop();
return(0);
}
sleep_us(10);
if(0==sccb_bus_write_byte(regID))
{
sccb_bus_stop();
return(0);
}
sleep_us(10);
if(0==sccb_bus_write_byte(regDat))
{
sccb_bus_stop();
return(0);
}
sccb_bus_stop();
return(1);
}
unsigned char rdSensorReg16_8(uint8_t slave_address, unsigned int regID, unsigned char* regDat)
{
sccb_bus_start();
if(0==sccb_bus_write_byte(slave_address<<1))
{
sccb_bus_stop();
return(0);
}
sleep_us(20);
sleep_us(20);
if(0==sccb_bus_write_byte(regID>>8))
{
sccb_bus_stop();
return(0);
}
sleep_us(20);
if(0==sccb_bus_write_byte(regID))
{
sccb_bus_stop();
return(0);
}
sleep_us(20);
sccb_bus_stop();
sleep_us(20);
sccb_bus_start();
if(0==sccb_bus_write_byte((slave_address<<1)|0x01))
{
sccb_bus_stop();
return(0);
}
sleep_us(20);
*regDat=sccb_bus_read_byte();
sccb_bus_send_noack();
sccb_bus_stop();
return(1);
}
#endif
void arducam_init(struct arducam_config *config){
gpio_set_function(config->pin_xclk, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(config->pin_xclk);
// 6 cycles (0 to 5), 125 MHz / 6 = ~20.83 MHz wrap rate
pwm_set_wrap(slice_num, 9);
pwm_set_gpio_level(config->pin_xclk, 3);
pwm_set_enabled(slice_num, true);
#ifndef SOFTWARE_I2C
// SCCB I2C @ 100 kHz
gpio_set_function(config->pin_sioc, GPIO_FUNC_I2C);
gpio_set_function(config->pin_siod, GPIO_FUNC_I2C);
i2c_init(config->sccb, 100 * 1000);
#else
gpio_init(config->pin_sioc);
gpio_init(config->pin_siod);
gpio_set_dir(config->pin_sioc, GPIO_OUT);
gpio_set_dir(config->pin_siod, GPIO_OUT);
#endif
// Initialise reset pin
gpio_init(config->pin_resetb);
gpio_set_dir(config->pin_resetb, GPIO_OUT);
// Reset camera, and give it some time to wake back up
gpio_put(config->pin_resetb, 0);
sleep_ms(100);
gpio_put(config->pin_resetb, 1);
sleep_ms(100);
// Initialise the camera itself over SCCB
arducam_regs_write(config, hm01b0_324x244);
// Enable image RX PIO
uint offset = pio_add_program(config->pio, &image_program);
image_program_init(config->pio, config->pio_sm, offset, config->pin_y2_pio_base);
}
void arducam_capture_frame(struct arducam_config *config) {
dma_channel_config c = dma_channel_get_default_config(config->dma_channel);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
channel_config_set_dreq(&c, pio_get_dreq(config->pio, config->pio_sm, false));
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
dma_channel_configure(
config->dma_channel, &c,
config->image_buf,
&config->pio->rxf[config->pio_sm],
config->image_buf_size,
false
);
// Wait for vsync rising edge to start frame
while (gpio_get(config->pin_vsync) == true);
while (gpio_get(config->pin_vsync) == false);
dma_channel_start(config->dma_channel);
pio_sm_set_enabled(config->pio, config->pio_sm, true);
dma_channel_wait_for_finish_blocking(config->dma_channel);
pio_sm_set_enabled(config->pio, config->pio_sm, false);
}
void arducam_reg_write(struct arducam_config *config, uint16_t reg, uint8_t value) {
uint8_t data[3];
uint8_t length =0;
switch (config->sccb_mode){
case I2C_MODE_16_8:
data[0] = (uint8_t)(reg>>8)&0xFF;
data[1] = (uint8_t)(reg)&0xFF;
data[2] = value;
length = 3;
break;
case I2C_MODE_8_8:
data[0] = (uint8_t)(reg)&0xFF;
data[1] = value;
length = 2;
break;
}
//printf("length: %x data[0]: = %x data[1] = %x data[2] = %x\r\n", length, data[0],data[1],data[2]);
#ifndef SOFTWARE_I2C
int ret = i2c_write_blocking(config->sccb, config->sensor_address, data, length, false);
#else
int ret = wrSensorReg16_8(config->sensor_address, reg, value);
#endif
//printf("ret: %x\r\n", ret);
}
uint8_t arducam_reg_read(struct arducam_config *config, uint16_t reg) {
uint8_t data[2];
uint8_t length;
switch (config->sccb_mode){
case I2C_MODE_16_8:
data[0] = (uint8_t)(reg>>8)&0xFF;
data[1] = (uint8_t)(reg)&0xFF;
length = 2;
case I2C_MODE_8_8:
data[0] = (uint8_t)reg&0xFF;
length = 1;
}
i2c_write_blocking(config->sccb, config->sensor_address, data, length, false);
uint8_t value;
i2c_read_blocking(config->sccb, config->sensor_address, &value, 1, false);
return value;
}
void arducam_regs_write(struct arducam_config *config, struct senosr_reg* regs_list) {
while (1) {
uint16_t reg = regs_list->reg;
uint8_t value = regs_list->val;
if (reg == 0xFFFF && value == 0xFF) {
break;
}
//printf("reg: 0x%04x , val: 0x%02x\r\n",reg, value);
arducam_reg_write(config, reg, value);
regs_list++;
}
}

51
arducam/arducam.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef _ARDUCAM__H
#define _ARDUCAM__H
#include "stdint.h"
#include "pico/stdio.h"
#include "hardware/pio.h"
#include "hardware/i2c.h"
#define SOFTWARE_I2C 1
enum i2c_mode{
I2C_MODE_16_8 = 0,
I2C_MODE_8_8 = 1,
};
struct senosr_reg{
uint16_t reg;
uint8_t val;
};
struct arducam_config {
uint8_t sensor_address;
i2c_inst_t *sccb;
enum i2c_mode sccb_mode;
uint pin_sioc;
uint pin_siod;
uint pin_resetb;
uint pin_xclk;
uint pin_vsync;
// Y2, Y3, Y4, Y5, Y6, Y7, Y8, PCLK, HREF
uint pin_y2_pio_base;
PIO pio;
uint pio_sm;
uint dma_channel;
uint8_t *image_buf;
size_t image_buf_size;
};
extern int PIN_LED;
extern int PIN_CAM_SIOC; // I2C0 SCL
extern int PIN_CAM_SIOD; // I2C0 SDA
extern int PIN_CAM_RESETB;
extern int PIN_CAM_XCLK;
extern int PIN_CAM_VSYNC;
extern int PIN_CAM_Y2_PIO_BASE;
void arducam_init(struct arducam_config *config);
void arducam_capture_frame(struct arducam_config *config);
void arducam_reg_write(struct arducam_config *config, uint16_t reg, uint8_t value);
uint8_t arducam_reg_read(struct arducam_config *config, uint16_t reg);
void arducam_regs_write(struct arducam_config *config, struct senosr_reg* regs_list);
#endif

90
arducam/hm01b0_init.h Normal file
View File

@ -0,0 +1,90 @@
#ifndef HM01B0_INIT_H
#define HM01B0_INIT_H
#include "arducam.h"
#include <stdint.h>
struct senosr_reg hm01b0_324x244[] = {
{0x0103, 0x0},
{0x0100,0x00},
{0x1003,0x08},
{0x1007,0x08},
{0x3044,0x0A},
{0x3045,0x00},
{0x3047,0x0A},
{0x3050,0xC0},
{0x3051,0x42},
{0x3052,0x50},
{0x3053,0x00},
{0x3054,0x03},
{0x3055,0xF7},
{0x3056,0xF8},
{0x3057,0x29},
{0x3058,0x1F},
{0x3059,0x1E},
{0x3064,0x00},
{0x3065,0x04},
{0x1000,0x43},
{0x1001,0x40},
{0x1002,0x32},
{0x0350,0x7F},
{0x1006,0x01},
{0x1008,0x00},
{0x1009,0xA0},
{0x100A,0x60},
{0x100B,0x90},
{0x100C,0x40},
{0x3022,0x01},
{0x1012,0x01},
{0x2000,0x07},
{0x2003,0x00},
{0x2004,0x1C},
{0x2007,0x00},
{0x2008,0x58},
{0x200B,0x00},
{0x200C,0x7A},
{0x200F,0x00},
{0x2010,0xB8},
{0x2013,0x00},
{0x2014,0x58},
{0x2017,0x00},
{0x2018,0x9B},
{0x2100,0x01},
{0x2101,0x5F},
{0x2102,0x0A},
{0x2103,0x03},
{0x2104,0x05},
{0x2105,0x02},
{0x2106,0x14},
{0x2107,0x02},
{0x2108,0x03},
{0x2109,0x03},
{0x210A,0x00},
{0x210B,0x80},
{0x210C,0x40},
{0x210D,0x20},
{0x210E,0x03},
{0x210F,0x00},
{0x2110,0x85},
{0x2111,0x00},
{0x2112,0xA0},
{0x2150,0x03},
{0x0340,0x01},
{0x0341,0x7A},
{0x0342,0x01},
{0x0343,0x77},
{0x3010,0x00}, //bit[0] 1 enable QVGA
{0x0383,0x01},
{0x0387,0x01},
{0x0390,0x00},
{0x3011,0x70},
{0x3059,0x22},
{0x3060,0x30},
{0x0101,0x01},
{0x0104,0x01},
//{0x0390,0x03}, //1/4 binning
//{0x0383,0x03},
//{0x0387,0x03},
//{0x1012,0x03},
{0x0100,0x01},
{0xFFFF,0xFF},
};
#endif

265
arducam/ov2640_init.h Normal file
View File

@ -0,0 +1,265 @@
#ifndef OV2640_INIT_H
#define OV2640_INIT_H
#include "arducam.h"
#include <stdint.h>
struct senosr_reg ov2640_vga[] = {
{0xff, 0x00}, /* Device control register list Table 12 */
{0x2c, 0xff}, /* Reserved */
{0x2e, 0xdf}, /* Reserved */
{0xff, 0x01}, /* Device control register list Table 13 */
{0x3c, 0x32}, /* Reserved */
{0x11, 0x00}, /* Clock Rate Control */
{0x09, 0x02}, /* Common control 2 */
{0x04, 0xA8}, /* Mirror */
{0x13, 0xe5}, /* Common control 8 */
{0x14, 0x48}, /* Common control 9 */
{0x2c, 0x0c}, /* Reserved */
{0x33, 0x78}, /* Reserved */
{0x3a, 0x33}, /* Reserved */
{0x3b, 0xfB}, /* Reserved */
{0x3e, 0x00}, /* Reserved */
{0x43, 0x11}, /* Reserved */
{0x16, 0x10}, /* Reserved */
{0x4a, 0x81}, /* Reserved */
{0x21, 0x99}, /* Reserved */
{0x24, 0x40}, /* Luminance signal High range */
{0x25, 0x38}, /* Luminance signal low range */
{0x26, 0x82}, /* */
{0x5c, 0x00}, /* Reserved */
{0x63, 0x00}, /* Reserved */
{0x46, 0x3f}, /* Frame length adjustment */
{0x0c, 0x3c}, /* Common control 3 */
{0x61, 0x70}, /* Histogram algo low level */
{0x62, 0x80}, /* Histogram algo high level */
{0x7c, 0x05}, /* Reserved */
{0x20, 0x80}, /* Reserved */
{0x28, 0x30}, /* Reserved */
{0x6c, 0x00}, /* Reserved */
{0x6d, 0x80}, /* Reserved */
{0x6e, 0x00}, /* Reserved */
{0x70, 0x02}, /* Reserved */
{0x71, 0x94}, /* Reserved */
{0x73, 0xc1}, /* Reserved */
{0x3d, 0x34}, /* Reserved */
{0x5a, 0x57}, /* Reserved */
{0x12, 0x00}, /* Common control 7 */
{0x11, 0x00}, /* Clock Rate Control 2*/
{0x17, 0x11}, /* Horiz window start MSB 8bits */
{0x18, 0x75}, /* Horiz window end MSB 8bits */
{0x19, 0x01}, /* Vert window line start MSB 8bits */
{0x1a, 0x97}, /* Vert window line end MSB 8bits */
{0x32, 0x36},
{0x03, 0x0f},
{0x37, 0x40},
{0x4f, 0xbb},
{0x50, 0x9c},
{0x5a, 0x57},
{0x6d, 0x80},
{0x6d, 0x38},
{0x39, 0x02},
{0x35, 0x88},
{0x22, 0x0a},
{0x37, 0x40},
{0x23, 0x00},
{0x34, 0xa0},
{0x36, 0x1a},
{0x06, 0x02},
{0x07, 0xc0},
{0x0d, 0xb7},
{0x0e, 0x01},
{0x4c, 0x00},
{0xff, 0x00},
{0xe5, 0x7f},
{0xf9, 0xc0},
{0x41, 0x24},
{0xe0, 0x14},
{0x76, 0xff},
{0x33, 0xa0},
{0x42, 0x20},
{0x43, 0x18},
{0x4c, 0x00},
{0x87, 0xd0},
{0x88, 0x3f},
{0xd7, 0x03},
{0xd9, 0x10},
{0xd3, 0x82},
{0xc8, 0x08},
{0xc9, 0x80},
{0x7d, 0x00},
{0x7c, 0x03},
{0x7d, 0x48},
{0x7c, 0x08},
{0x7d, 0x20},
{0x7d, 0x10},
{0x7d, 0x0e},
{0x90, 0x00},
{0x91, 0x0e},
{0x91, 0x1a},
{0x91, 0x31},
{0x91, 0x5a},
{0x91, 0x69},
{0x91, 0x75},
{0x91, 0x7e},
{0x91, 0x88},
{0x91, 0x8f},
{0x91, 0x96},
{0x91, 0xa3},
{0x91, 0xaf},
{0x91, 0xc4},
{0x91, 0xd7},
{0x91, 0xe8},
{0x91, 0x20},
{0x92, 0x00},
{0x93, 0x06},
{0x93, 0xe3},
{0x93, 0x02},
{0x93, 0x02},
{0x93, 0x00},
{0x93, 0x04},
{0x93, 0x00},
{0x93, 0x03},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x96, 0x00},
{0x97, 0x08},
{0x97, 0x19},
{0x97, 0x02},
{0x97, 0x0c},
{0x97, 0x24},
{0x97, 0x30},
{0x97, 0x28},
{0x97, 0x26},
{0x97, 0x02},
{0x97, 0x98},
{0x97, 0x80},
{0x97, 0x00},
{0x97, 0x00},
{0xc3, 0xef},
{0xff, 0x00},
{0xba, 0xdc},
{0xbb, 0x08},
{0xb6, 0x24},
{0xb8, 0x33},
{0xb7, 0x20},
{0xb9, 0x30},
{0xb3, 0xb4},
{0xb4, 0xca},
{0xb5, 0x43},
{0xb0, 0x5c},
{0xb1, 0x4f},
{0xb2, 0x06},
{0xc7, 0x00},
{0xc6, 0x51},
{0xc5, 0x11},
{0xc4, 0x9c},
{0xbf, 0x00},
{0xbc, 0x64},
{0xa6, 0x00},
{0xa7, 0x1e},
{0xa7, 0x6b},
{0xa7, 0x47},
{0xa7, 0x33},
{0xa7, 0x00},
{0xa7, 0x23},
{0xa7, 0x2e},
{0xa7, 0x85},
{0xa7, 0x42},
{0xa7, 0x33},
{0xa7, 0x00},
{0xa7, 0x23},
{0xa7, 0x1b},
{0xa7, 0x74},
{0xa7, 0x42},
{0xa7, 0x33},
{0xa7, 0x00},
{0xa7, 0x23},
{0xc0, 0xc8},
{0xc1, 0x96},
{0x8c, 0x00},
{0x86, 0x3d},
{0x50, 0x92},
{0x51, 0x90},
{0x52, 0x2c},
{0x53, 0x00},
{0x54, 0x00},
{0x55, 0x88},
{0x5a, 0x50},
{0x5b, 0x3c},
{0x5c, 0x00},
{0xd3, 0x04},
{0x7f, 0x00},
{0xda, 0x00},
{0xe5, 0x1f},
{0xe1, 0x67},
{0xe0, 0x00},
{0xdd, 0x7f},
{0x05, 0x00},
{0xff, 0x00},
{0xe0, 0x04},
{0xc0, 0xc8},
{0xc1, 0x96},
{0x86, 0x3d},
{0x50, 0x92},
{0x51, 0x90},
{0x52, 0x2c},
{0x53, 0x00},
{0x54, 0x00},
{0x55, 0x88},
{0x57, 0x00},
{0x5a, 0x50},
{0x5b, 0x3c},
{0x5c, 0x00},
{0xd3, 0x04},
{0xe0, 0x00},
{0xFF, 0x00},
{0x05, 0x00},
{0xDA, 0x08},
{0xda, 0x09},
{0x98, 0x00},
{0x99, 0x00},
{0x00, 0x00},
{0xff, 0x00},
{0xe0, 0x04},
{0xc0, 0xc8},
{0xc1, 0x96},
{0x86, 0x3d},
{0x50, 0x89},
{0x51, 0x90},
{0x52, 0x2c},
{0x53, 0x00},
{0x54, 0x00},
{0x55, 0x88},
{0x57, 0x00},
{0x5a, 0xA0},
{0x5b, 0x78},
{0x5c, 0x00},
{0xd3, 0x02},
{0xFFFF, 0xFF}
};
struct senosr_reg ov2640_uxga_cif[] = {
{0xff, 0x00},
{0xe0, 0x04},
{0xc0, 0xc8},
{0xc1, 0x96},
{0x86, 0x35},
{0x50, 0x92},
{0x51, 0x90},
{0x52, 0x2c},
{0x53, 0x00},
{0x54, 0x00},
{0x55, 0x88},
{0x57, 0x00},
{0x5a, 0x58},
{0x5b, 0x48},
{0x5c, 0x00},
{0xd3, 0x08},
{0xFFFF, 0xFF}
};
#endif

21
image.pio Normal file
View File

@ -0,0 +1,21 @@
.program image
.wrap_target
wait 1 pin 9 // wait for hsync
wait 1 pin 8 // wait for rising pclk
in pins 1
wait 0 pin 8
.wrap
% c-sdk {
void image_program_init(PIO pio, uint sm, uint offset, uint pin_base) {
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, 1, false);
pio_sm_config c = image_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin_base);
sm_config_set_in_shift(&c, false, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_init(pio, sm, offset, &c);
//pio_sm_set_enabled(pio, sm, true);
}
%}

198
main.c Normal file
View File

@ -0,0 +1,198 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Copyright (c) 2022 Charles Kralapp (alfrescocavern.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "arducam/arducam.h"
#include "bsp/board.h"
#include "pico/stdlib.h"
#include "tusb.h"
#include "usb_descriptors.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTYPES
//--------------------------------------------------------------------+
/* Blink pattern
* - 250 ms : device not mounted
* - 1000 ms : device mounted
* - 2500 ms : device is suspended
*/
enum {
BLINK_NOT_MOUNTED = 250,
BLINK_MOUNTED = 1000,
BLINK_SUSPENDED = 2500,
};
static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
struct arducam_config config;
void led_blinking_task(void);
void video_task(void);
/*------------- MAIN -------------*/
int main(void) {
board_init();
// init device stack on configured roothub port
tud_init(BOARD_TUD_RHPORT);
uint8_t image_buf[324 * 324];
config.sccb = i2c0;
config.sccb_mode = I2C_MODE_16_8;
config.sensor_address = 0x24;
config.pin_sioc = PIN_CAM_SIOC;
config.pin_siod = PIN_CAM_SIOD;
config.pin_resetb = PIN_CAM_RESETB;
config.pin_xclk = PIN_CAM_XCLK;
config.pin_vsync = PIN_CAM_VSYNC;
config.pin_y2_pio_base = PIN_CAM_Y2_PIO_BASE;
config.pio = pio0;
config.pio_sm = 0;
config.dma_channel = 0;
config.image_buf = image_buf;
config.image_buf_size = sizeof(image_buf);
arducam_init(&config);
while (1) {
tud_task(); // tinyusb device task
led_blinking_task();
video_task();
}
return 0;
}
//--------------------------------------------------------------------+
// Device callbacks
//--------------------------------------------------------------------+
// Invoked when device is mounted
void tud_mount_cb(void) { blink_interval_ms = BLINK_MOUNTED; }
// Invoked when device is unmounted
void tud_umount_cb(void) { blink_interval_ms = BLINK_NOT_MOUNTED; }
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en) {
(void)remote_wakeup_en;
blink_interval_ms = BLINK_SUSPENDED;
}
// Invoked when usb bus is resumed
void tud_resume_cb(void) { blink_interval_ms = BLINK_MOUNTED; }
//--------------------------------------------------------------------+
// USB Video
//--------------------------------------------------------------------+
static unsigned frame_num = 0;
static unsigned tx_busy = 0;
static unsigned interval_ms = 1000 / FRAME_RATE;
static uint8_t frame_buffer[FRAME_WIDTH * FRAME_HEIGHT * 2];
static void get_frame(uint8_t *buffer) {
for (int i = 0; i < FRAME_HEIGHT; i++) {
for (int j = 0; j < FRAME_WIDTH * 2; j += 4) {
uint8_t c =
config.image_buf[(2 + 320 - 2 * i) * 324 + (2 + 40 + 2 * (j / 2))];
buffer[2 * FRAME_WIDTH * i + j] = c;
buffer[2 * FRAME_WIDTH * i + j + 1] = 128;
buffer[2 * FRAME_WIDTH * i + j + 2] = c;
buffer[2 * FRAME_WIDTH * i + j + 3] = 128;
}
}
}
void video_task(void) {
static unsigned start_ms = 0;
static unsigned already_sent = 0;
if (!tud_video_n_streaming(0, 0)) {
already_sent = 0;
frame_num = 0;
return;
}
if (!already_sent) {
already_sent = 1;
start_ms = board_millis();
get_frame(frame_buffer);
tud_video_n_frame_xfer(0, 0, (void *)frame_buffer,
FRAME_WIDTH * FRAME_HEIGHT * 2);
}
unsigned cur = board_millis();
if (cur - start_ms < interval_ms)
return; // not enough time
if (tx_busy)
return;
start_ms += interval_ms;
get_frame(frame_buffer);
tud_video_n_frame_xfer(0, 0, (void *)frame_buffer,
FRAME_WIDTH * FRAME_HEIGHT * 2);
}
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx,
uint_fast8_t stm_idx) {
(void)ctl_idx;
(void)stm_idx;
tx_busy = 0;
/* flip buffer */
arducam_capture_frame(&config);
}
int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx,
video_probe_and_commit_control_t const *parameters) {
(void)ctl_idx;
(void)stm_idx;
/* convert unit to ms from 100 ns */
interval_ms = parameters->dwFrameInterval / 10000;
return VIDEO_ERROR_NONE;
}
//--------------------------------------------------------------------+
// BLINKING TASK
//--------------------------------------------------------------------+
void led_blinking_task(void) {
static uint32_t start_ms = 0;
static bool led_state = false;
// Blink every interval ms
if (board_millis() - start_ms < blink_interval_ms)
return; // not enough time
start_ms += blink_interval_ms;
board_led_write(led_state);
led_state = 1 - led_state; // toggle
}

2
pipewire_reset.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/sh
systemctl --user restart pipewire.service

118
tusb_config.h Normal file
View File

@ -0,0 +1,118 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Copyright (c) 2022 Charles Kralapp (alfrescocavern.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_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
#error CFG_TUSB_MCU must be defined
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#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 -------------//
// The number of video control interfaces
#define CFG_TUD_VIDEO 1
#define CFG_TUD_CDC 1
#define CFG_TUD_MSC 0
#define CFG_TUD_HID 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// The number of video streaming interfaces
#define CFG_TUD_VIDEO_STREAMING 1
// video streaming endpoint size
#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 1023
#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
// CDC Endpoint transfer buffer size, more is faster
#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

164
usb_descriptors.c Normal file
View File

@ -0,0 +1,164 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Copyright (c) 2022 Charles Kralapp (alfrescocavern.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "usb_descriptors.h"
#include "tusb.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))
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
// 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 = 0xCafe,
.idProduct = USB_PID,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const *tud_descriptor_device_cb(void) {
return (uint8_t const *)&desc_device;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VIDEO_CAPTURE_DESC_LEN)
#if TU_CHECK_MCU(OPT_MCU_LPC175X_6X, OPT_MCU_LPC177X_8X, OPT_MCU_LPC40XX)
// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
// 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ...
#define EPNUM_VIDEO_IN 0x83
#elif TU_CHECK_MCU(OPT_MCU_NRF5X)
// nRF5x ISO can only be endpoint 8
#define EPNUM_VIDEO_IN 0x88
#else
#define EPNUM_VIDEO_IN 0x81
#endif
uint8_t const desc_fs_configuration[] = {
// Config number, interface count, string index, total length, attribute,
// power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500),
// IAD for Video Control
TUD_VIDEO_CAPTURE_DESCRIPTOR(4, EPNUM_VIDEO_IN, FRAME_WIDTH, FRAME_HEIGHT,
FRAME_RATE,
CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE)};
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
return desc_fs_configuration;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// array of pointer to string descriptors
char const *string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"alfrescocavern.com", // 1: Manufacturer
"HM01B0 Mini Webcam", // 2: Product
"123456", // 3: Serials, should use chip ID
"HM01B0 UVC", // 4: UVC Interface
};
static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long
// enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
(void)langid;
uint8_t chr_count;
if (index == 0) {
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
} else {
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
return NULL;
const char *str = string_desc_arr[index];
// Cap at max char
chr_count = (uint8_t)strlen(str);
if (chr_count > 31)
chr_count = 31;
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
return _desc_str;
}

122
usb_descriptors.h Normal file
View File

@ -0,0 +1,122 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jerzy Kasenbreg
* Copyright (c) 2021 Koji KITAYAMA
* Copyright (c) 2022 Charles Kralapp (alfrescocavern.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef _USB_DESCRIPTORS_H_
#define _USB_DESCRIPTORS_H_
/* 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_OUTPUT_TERMINAL 0x02
#define FRAME_WIDTH 96
#define FRAME_HEIGHT 96
#define FRAME_RATE 30
enum { ITF_NUM_VIDEO_CONTROL = 0, ITF_NUM_VIDEO_STREAMING, ITF_NUM_TOTAL };
#define TUD_VIDEO_CAPTURE_DESC_LEN \
(TUD_VIDEO_DESC_IAD_LEN /* control */ \
+ TUD_VIDEO_DESC_STD_VC_LEN + \
(TUD_VIDEO_DESC_CS_VC_LEN + 1 /*bInCollection*/) + \
TUD_VIDEO_DESC_CAMERA_TERM_LEN + \
TUD_VIDEO_DESC_OUTPUT_TERM_LEN /* Interface 1, Alternate 0 */ \
+ TUD_VIDEO_DESC_STD_VS_LEN + \
(TUD_VIDEO_DESC_CS_VS_IN_LEN + 1 /*bNumFormats x bControlSize*/) + \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN + \
TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN + \
TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN /* Interface 1, Alternate 1 */ \
+ TUD_VIDEO_DESC_STD_VS_LEN + 7 /* Endpoint */ \
)
/* Windows support YUY2 and NV12
* https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview
*/
#define TUD_VIDEO_DESC_CS_VS_FMT_YUY2(_fmtidx, _numfmtdesc, _frmidx, _asrx, \
_asry, _interlace, _cp) \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_YUY2, \
16, _frmidx, _asrx, _asry, _interlace, _cp)
#define TUD_VIDEO_DESC_CS_VS_FMT_NV12(_fmtidx, _numfmtdesc, _frmidx, _asrx, \
_asry, _interlace, _cp) \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_NV12, \
12, _frmidx, _asrx, _asry, _interlace, _cp)
#define TUD_VIDEO_DESC_CS_VS_FMT_M420(_fmtidx, _numfmtdesc, _frmidx, _asrx, \
_asry, _interlace, _cp) \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_M420, \
12, _frmidx, _asrx, _asry, _interlace, _cp)
#define TUD_VIDEO_DESC_CS_VS_FMT_I420(_fmtidx, _numfmtdesc, _frmidx, _asrx, \
_asry, _interlace, _cp) \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_I420, \
12, _frmidx, _asrx, _asry, _interlace, _cp)
#define TUD_VIDEO_CAPTURE_DESCRIPTOR(_stridx, _epin, _width, _height, _fps, \
_epsize) \
TUD_VIDEO_DESC_IAD(ITF_NUM_VIDEO_CONTROL, ITF_NUM_TOTAL, \
_stridx), /* Video control 0 */ \
TUD_VIDEO_DESC_STD_VC(ITF_NUM_VIDEO_CONTROL, 0, _stridx), \
TUD_VIDEO_DESC_CS_VC(/* UVC 1.5*/ 0x0150, /* wTotalLength - bLength */ \
TUD_VIDEO_DESC_CAMERA_TERM_LEN + \
TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \
UVC_CLOCK_FREQUENCY, 1), \
TUD_VIDEO_DESC_CAMERA_TERM( \
UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0, /*wObjectiveFocalLengthMin*/ 0, \
/*wObjectiveFocalLengthMax*/ 0, /*wObjectiveFocalLength*/ 0, \
/*bmControls*/ 0), \
TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, \
VIDEO_TT_STREAMING, 0, 1, \
0), /* Video stream alt. 0 */ \
TUD_VIDEO_DESC_STD_VS( \
1, 0, 0, \
0), /* Video stream header for without still image capture */ \
TUD_VIDEO_DESC_CS_VS_INPUT( \
/*bNumFormats*/ 1, /*wTotalLength - bLength */ \
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN + \
TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN + \
TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN, \
_epin, /*bmInfo*/ 0, \
/*bTerminalLink*/ UVC_ENTITY_CAP_OUTPUT_TERMINAL, \
/*bStillCaptureMethod*/ 0, /*bTriggerSupport*/ 0, \
/*bTriggerUsage*/ 0, \
/*bmaControls(1)*/ 0), /* Video stream format */ \
TUD_VIDEO_DESC_CS_VS_FMT_YUY2( \
/*bFormatIndex*/ 1, /*bNumFrameDescriptors*/ 1, \
/*bDefaultFrameIndex*/ 1, 0, 0, 0, \
/*bCopyProtect*/ 0), /* Video stream frame format */ \
TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT( \
/*bFrameIndex */ 1, 0, _width, _height, _width *_height * 16, \
_width * _height * 16 * _fps, _width * _height * 16, \
(10000000 / _fps), (10000000 / _fps), (10000000 / _fps) * _fps, \
(10000000 / _fps)), \
TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING( \
VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, \
VIDEO_COLOR_COEF_SMPTE170M), /* VS alt 1 */ \
TUD_VIDEO_DESC_STD_VS(1, 1, 1, 0), /* EP */ \
TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1)
#endif