commit cf3f9e9e8553d3a07cb4c216d98ec2fbb33d034b Author: shylie Date: Tue Sep 24 20:52:23 2024 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a83bb87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..faef00a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pico-ice-sdk"] + path = pico-ice-sdk + url = https://github.com/tinyvision-ai-inc/pico-ice-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c7eebbf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.13...3.27) + +include(pico_sdk_import.cmake) + +project(usb-video C CXX ASM) +pico_sdk_init() + +add_subdirectory(pico-ice-sdk) + +add_executable(usb-video main.cpp usb_descriptors.c) +target_link_libraries(usb-video pico_ice_sdk pico_ice_usb pico_stdio_usb pico_bootsel_via_double_reset) +target_include_directories(usb-video PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_BINARY_DIR}) + +pico_add_extra_outputs(usb-video) +pico_enable_stdio_usb(usb-video 0) +pico_enable_stdio_uart(usb-video 0) \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..1ddf5f6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,133 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +static void video_task(); + +int main() +{ + stdio_init_all(); + + uart_init(uart0, 115200); + gpio_set_function(0, GPIO_FUNC_UART); + gpio_set_function(1, GPIO_FUNC_UART); + + ice_led_init(); + ice_usb_init(); + + ice_fpga_init(12); + ice_fpga_start(); + + tud_init(0); + + if (board_init_after_tusb) + { + board_init_after_tusb(); + } + + while (true) + { + tud_task(); + video_task(); + } + return 0; +} + +static unsigned int interval_ms = 1000 / FRAME_RATE; +static unsigned int frame_num = 0; +static bool tx_busy = false; + +static uint8_t frame_buffer[FRAME_WIDTH * FRAME_HEIGHT * 16 / 8] = { }; + +static void fill_color_bar(uint8_t* buffer, const unsigned int start_position) +{ + static constexpr uint8_t bar_color[8][4] = + { + /* Y, U, Y, V */ + { 235, 128, 235, 128 }, /* 100% White */ + { 219, 16, 219, 138 }, /* Yellow */ + { 188, 154, 188, 16 }, /* Cyan */ + { 173, 42, 173, 26 }, /* Green */ + { 78, 214, 78, 230 }, /* Magenta */ + { 63, 102, 63, 240 }, /* Red */ + { 32, 240, 32, 118 }, /* Blue */ + { 16, 128, 16, 128 }, /* Black */ + }; + + uint8_t* p; + + const uint8_t* end = &buffer[FRAME_WIDTH * 2]; + const unsigned int index = (FRAME_WIDTH / 2 - 1) - (start_position % (FRAME_WIDTH / 2)); + + p = &buffer[index * 4]; + for (unsigned int i = 0; i < 8; i++) + { + for (int j = 0; j < FRAME_WIDTH / (2 * 8); j++) + { + memcpy(p, &bar_color[i], 4); + p += 4; + if (p >= end) { p = buffer; } + } + } + + p = &buffer[FRAME_WIDTH * 2]; + for (unsigned int i = 1; i < FRAME_HEIGHT; i++) + { + memcpy(p, buffer, FRAME_WIDTH * 2); + p += FRAME_WIDTH * 2; + } +} + +void video_task() +{ + static bool already_sent = false; + static unsigned int start_ms = 0; + + // don't send data if not streaming + if (!tud_video_n_streaming(0, 0)) + { + already_sent = false; + frame_num = 0; + return; + } + + if (!already_sent) + { + already_sent = true; + tx_busy = true; + start_ms = board_millis(); + + fill_color_bar(frame_buffer, frame_num); + + tud_video_n_frame_xfer(0, 0, frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8); + } + + const unsigned int cur = board_millis(); + if (cur - start_ms < interval_ms) { return; } + if (tx_busy) { return; } + start_ms += interval_ms; + tx_busy = true; + + fill_color_bar(frame_buffer, frame_num); + tud_video_n_frame_xfer(0, 0, frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8); +} + +void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + tx_busy = false; + + frame_num++; +} + +int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, const video_probe_and_commit_control_t* parameters) +{ + interval_ms = parameters->dwFrameInterval / 10000; + return VIDEO_ERROR_NONE; +} \ No newline at end of file diff --git a/pico-ice-sdk b/pico-ice-sdk new file mode 160000 index 0000000..4ebe348 --- /dev/null +++ b/pico-ice-sdk @@ -0,0 +1 @@ +Subproject commit 4ebe348ffcb4feb3e2756d101f594679eb02d97c diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..a0721d0 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,84 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/tusb_config.h b/tusb_config.h new file mode 100644 index 0000000..3514528 --- /dev/null +++ b/tusb_config.h @@ -0,0 +1,97 @@ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2022 TinyVision.ai Inc. + * + * 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. + */ +#pragma once + +// pico-ice-sdk +#include "boards/pico_ice.h" +#include "ice_flash.h" + +// RHPort number used for device can be defined by board.mk, port 0 for pico-ice +#define BOARD_DEVICE_RHPORT_NUM 0 + +// Device mode with rhport and speed defined by board.mk +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE + +// Either full or high speed supported by RP2040 +#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + +// 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 OPT_MODE_FULL_SPEED + +// Device classes +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 1 +#define CFG_TUD_DFU 1 +#define CFG_TUD_DFU_ALT 2 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_VIDEO 1 +#define CFG_TUD_VIDEO_STREAMING 1 + +// Enable TinyUF2 +#define ICE_USB_USE_TINYUF2_MSC 1 + +// CDC FIFO size of TX and RX and Endpoint buffer size +#define CFG_TUD_CDC_RX_BUFSIZE 512 +#define CFG_TUD_CDC_TX_BUFSIZE 512 +#define CFG_TUD_CDC_EP_BUFSIZE 512 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_BUFSIZE ICE_FLASH_SECTOR_SIZE + +// Must be a multiple of flash page size +#define CFG_TUD_DFU_XFER_BUFSIZE 256 + +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 +#define CFG_TUD_VIDEO_STREAMING_BULK 0 + +#define FRAME_WIDTH 32 +#define FRAME_HEIGHT 18 +#define FRAME_RATE 60 + +// Temporarily here until ice_usb.h has necessary info +// TODO: make PR to modify ice_usb.h as needed +#define TUD_VIDEO_CAPTURE_DESC_UNCOMPR_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 */\ + ) \ No newline at end of file diff --git a/usb_descriptors.c b/usb_descriptors.c new file mode 100644 index 0000000..b3bcf10 --- /dev/null +++ b/usb_descriptors.c @@ -0,0 +1,96 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2022 TinyVision.ai Inc. + * + * 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 "ice_usb.h" + +#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01 +#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x02 +#define UVC_CLOCK_FREQUENCY 27000000 + +enum { STRID_VIDEO = 5 }; + +#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_CAPTURE_DESCRIPTOR_UNCOMPR(_stridx, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(ITF_NUM_VIDEO_CONTROL, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(ITF_NUM_VIDEO_CONTROL, 0, _stridx), \ + /* Header: UVC 1.5, length of followed descs, clock (deprecated), streaming interfaces */ \ + TUD_VIDEO_DESC_CS_VC(0x0150, TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, UVC_CLOCK_FREQUENCY, ITF_NUM_VIDEO_STREAMING), \ + /* Camera Terminal: ID, bAssocTerminal, iTerminal, focal min, max, length, bmControl */ \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0, 0, 0, 0, 0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, UVC_ENTITY_CAP_INPUT_TERMINAL, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(ITF_NUM_VIDEO_STREAMING, 0, 0, _stridx), \ + /* 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 / 8, \ + (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(ITF_NUM_VIDEO_STREAMING, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +enum { + ITF_NUM_CDC0, ITF_NUM_CDC0_DATA, + ITF_NUM_MSC0, + ITF_NUM_DFU, + ITF_NUM_VIDEO_CONTROL, + ITF_NUM_VIDEO_STREAMING, + ITF_NUM_TOTAL +}; + +uint8_t const tud_desc_configuration[CONFIG_TOTAL_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 500/*mA*/), + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC0, STRID_CDC+0, EPIN+1, 8, EPOUT+2, EPIN+2, 64), + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC0, STRID_MSC+0, EPOUT+3, EPIN+3, 64), + TUD_DFU_DESCRIPTOR(ITF_NUM_DFU, CFG_TUD_DFU_ALT, STRID_DFU, DFU_ATTR_CAN_DOWNLOAD, 1000, CFG_TUD_DFU_XFER_BUFSIZE), + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_VIDEO, EPIN+4, FRAME_WIDTH, FRAME_HEIGHT, FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE) +}; + +char const *tud_string_desc[STRID_NUM_TOTAL] = { + [STRID_LANGID] = USB_LANG_EN, + [STRID_MANUFACTURER] = USB_MANUFACTURER, + [STRID_PRODUCT] = USB_PRODUCT, + [STRID_SERIAL_NUMBER] = usb_serial_number, + [STRID_VENDOR] = USB_VENDOR, + [STRID_VIDEO] = "RP2040 camera", + [STRID_CDC+0] = "RP2040 logs", + [STRID_MSC+0] = "iCE40 MSC (Flash)", + [STRID_DFU+0] = "iCE40 DFU (Flash)", + [STRID_DFU+1] = "iCE40 DFU (CRAM)" +};