Initial commit

This commit is contained in:
shylie 2024-09-24 20:52:23 -04:00
commit cf3f9e9e85
8 changed files with 432 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
.idea/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "pico-ice-sdk"]
path = pico-ice-sdk
url = https://github.com/tinyvision-ai-inc/pico-ice-sdk.git

16
CMakeLists.txt Normal file
View File

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

133
main.cpp Normal file
View File

@ -0,0 +1,133 @@
#include <ice_fpga.h>
#include <ice_led.h>
#include <ice_usb.h>
#include <tusb.h>
#include <bsp/board_api.h>
#include <pico/stdio.h>
#include <hardware/gpio.h>
#include <hardware/uart.h>
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;
}

1
pico-ice-sdk Submodule

@ -0,0 +1 @@
Subproject commit 4ebe348ffcb4feb3e2756d101f594679eb02d97c

84
pico_sdk_import.cmake Normal file
View File

@ -0,0 +1,84 @@
# This is a copy of <PICO_SDK_PATH>/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})

97
tusb_config.h Normal file
View File

@ -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 */\
)

96
usb_descriptors.c Normal file
View File

@ -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)"
};