diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a80ed8f --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index ea4bbd9..952a708 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ -# HM01B0_Firmware +# Raspberry Pi Pico + Arducam hm01b0 UVC Firmware -Raspberry Pi Pico + Arducam hm01b0 UVC Firmware \ No newline at end of file +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. diff --git a/arducam/arducam.c b/arducam/arducam.c new file mode 100644 index 0000000..7c7ce43 --- /dev/null +++ b/arducam/arducam.c @@ -0,0 +1,337 @@ +#include +#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< 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++; + } +} diff --git a/arducam/arducam.h b/arducam/arducam.h new file mode 100644 index 0000000..c421c48 --- /dev/null +++ b/arducam/arducam.h @@ -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 diff --git a/arducam/hm01b0_init.h b/arducam/hm01b0_init.h new file mode 100644 index 0000000..3a55319 --- /dev/null +++ b/arducam/hm01b0_init.h @@ -0,0 +1,90 @@ +#ifndef HM01B0_INIT_H +#define HM01B0_INIT_H +#include "arducam.h" +#include +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 \ No newline at end of file diff --git a/arducam/ov2640_init.h b/arducam/ov2640_init.h new file mode 100644 index 0000000..5ceef4a --- /dev/null +++ b/arducam/ov2640_init.h @@ -0,0 +1,265 @@ +#ifndef OV2640_INIT_H +#define OV2640_INIT_H +#include "arducam.h" +#include +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 diff --git a/image.pio b/image.pio new file mode 100644 index 0000000..78b3afe --- /dev/null +++ b/image.pio @@ -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); +} +%} diff --git a/main.c b/main.c new file mode 100644 index 0000000..fb60c61 --- /dev/null +++ b/main.c @@ -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 +#include +#include + +#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 +} diff --git a/pipewire_reset.sh b/pipewire_reset.sh new file mode 100644 index 0000000..eca216f --- /dev/null +++ b/pipewire_reset.sh @@ -0,0 +1,2 @@ +#!/bin/sh +systemctl --user restart pipewire.service \ No newline at end of file diff --git a/tusb_config.h b/tusb_config.h new file mode 100644 index 0000000..97fd486 --- /dev/null +++ b/tusb_config.h @@ -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_ */ \ No newline at end of file diff --git a/usb_descriptors.c b/usb_descriptors.c new file mode 100644 index 0000000..d0f8919 --- /dev/null +++ b/usb_descriptors.c @@ -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; +} diff --git a/usb_descriptors.h b/usb_descriptors.h new file mode 100644 index 0000000..c325b34 --- /dev/null +++ b/usb_descriptors.h @@ -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