diff --git a/Kconfig b/Kconfig index 39fb6ff4..272d5454 100644 --- a/Kconfig +++ b/Kconfig @@ -138,6 +138,11 @@ if CHERRYUSB prompt "Enable usb dfu device" default n + config CHERRYUSB_DEVICE_DISPLAY + bool + prompt "Enable usb display device" + default n + config USBDEV_REQUEST_BUFFER_LEN int prompt "Set device control transfer max buffer size" @@ -237,6 +242,10 @@ if CHERRYUSB bool prompt "webusb_hid" depends on CHERRYUSB_DEVICE_HID + config CHERRYUSB_DEVICE_TEMPLATE_DISPLAY + bool + prompt "display" + depends on CHERRYUSB_DEVICE_DISPLAY endchoice endif diff --git a/Kconfig.rtt b/Kconfig.rtt index 162af731..b8acf614 100644 --- a/Kconfig.rtt +++ b/Kconfig.rtt @@ -139,6 +139,11 @@ if RT_USING_CHERRYUSB prompt "Enable usb dfu device" default n + config RT_CHERRYUSB_DEVICE_DISPLAY + bool + prompt "Enable usb display device" + default n + config RT_CHERRYUSB_DEVICE_CDC_ACM_CHARDEV bool prompt "Enable chardev for cdc acm device" @@ -247,6 +252,10 @@ if RT_USING_CHERRYUSB bool prompt "webusb_hid" depends on RT_CHERRYUSB_DEVICE_HID + config RT_CHERRYUSB_DEVICE_TEMPLATE_DISPLAY + bool + prompt "display" + depends on RT_CHERRYUSB_DEVICE_DISPLAY config RT_CHERRYUSB_DEVICE_TEMPLATE_ADB bool prompt "adb" diff --git a/Kconfig.rttpkg b/Kconfig.rttpkg index 4583fd09..3fa33e87 100644 --- a/Kconfig.rttpkg +++ b/Kconfig.rttpkg @@ -138,6 +138,11 @@ if PKG_USING_CHERRYUSB prompt "Enable usb dfu device" default n + config PKG_CHERRYUSB_DEVICE_DISPLAY + bool + prompt "Enable usb display device" + default n + config PKG_CHERRYUSB_DEVICE_CDC_ACM_CHARDEV bool prompt "Enable chardev for cdc acm device" @@ -246,6 +251,10 @@ if PKG_USING_CHERRYUSB bool prompt "webusb_hid" depends on PKG_CHERRYUSB_DEVICE_HID + config PKG_CHERRYUSB_DEVICE_TEMPLATE_DISPLAY + bool + prompt "display" + depends on PKG_CHERRYUSB_DEVICE_DISPLAY config PKG_CHERRYUSB_DEVICE_TEMPLATE_ADB bool prompt "adb" diff --git a/README.md b/README.md index 51a59960..3f68ebf8 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ CherryUSB Device Stack has the following functions: - Support Remote NDIS (RNDIS) - Support Media Transfer Protocol (MTP) - Support WINUSB1.0, WINUSB2.0, WEBUSB, BOS +- Support Vendor display ([xfz1986_usb_graphic_driver](https://github.com/chuanjinpang/win10_idd_xfz1986_usb_graphic_driver_display)) - Support Vendor class - Support UF2 - Support Android Debug Bridge (Only support shell) diff --git a/README_zh.md b/README_zh.md index 53f225f5..89aefe14 100644 --- a/README_zh.md +++ b/README_zh.md @@ -75,6 +75,7 @@ CherryUSB Device 协议栈当前实现以下功能: - 支持 Remote NDIS (RNDIS) - 支持 Media Transfer Protocol (MTP) - 支持 WINUSB1.0、WINUSB2.0、WEBUSB、BOS +- 支持 Vendor display ([xfz1986_usb_graphic_driver](https://github.com/chuanjinpang/win10_idd_xfz1986_usb_graphic_driver_display)) - 支持 Vendor 类 class - 支持 UF2 - 支持 Android Debug Bridge (Only support shell) diff --git a/SConscript b/SConscript index eafc5722..bea245a2 100644 --- a/SConscript +++ b/SConscript @@ -17,6 +17,7 @@ path += [cwd + '/class/dfu'] path += [cwd + '/class/serial'] path += [cwd + '/class/vendor/net'] path += [cwd + '/class/vendor/wifi'] +path += [cwd + '/class/vendor/display'] src = [] LIBS = [] @@ -136,6 +137,11 @@ if GetDepend(['PKG_CHERRYUSB_DEVICE']): src += Glob('class/cdc/usbd_cdc_ncm.c') if GetDepend(['PKG_CHERRYUSB_DEVICE_DFU']): src += Glob('class/dfu/usbd_dfu.c') + if GetDepend(['PKG_CHERRYUSB_DEVICE_DISPLAY']): + src += Glob('class/vendor/display/usbd_display.c') + src += Glob('third_party/cherrymp/chry_mempool.c') + src += Glob('third_party/cherrymp/chry_mempool_osal_rtthread.c') + path += [cwd + '/third_party/cherrymp'] if GetDepend(['PKG_CHERRYUSB_DEVICE_ADB']): src += Glob('class/adb/usbd_adb.c') src += Glob('platform/rtthread/usbd_adb_shell.c') @@ -179,6 +185,8 @@ if GetDepend(['PKG_CHERRYUSB_DEVICE']): src += Glob('demo/winusb2.0_cdc_template.c') if GetDepend(['PKG_CHERRYUSB_DEVICE_TEMPLATE_WEBUSB_HID']): src += Glob('demo/webusb_hid_template.c') + if GetDepend(['PKG_CHERRYUSB_DEVICE_TEMPLATE_DISPLAY']): + src += Glob('demo/display/usbdisplay_template.c') if GetDepend(['PKG_CHERRYUSB_DEVICE_TEMPLATE_ADB']): src += Glob('demo/adb/usbd_adb_template.c') if GetDepend(['PKG_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM_CHARDEV']): diff --git a/cherryusb.cmake b/cherryusb.cmake index 69573249..9766c253 100644 --- a/cherryusb.cmake +++ b/cherryusb.cmake @@ -50,6 +50,7 @@ list( ${CMAKE_CURRENT_LIST_DIR}/class/serial ${CMAKE_CURRENT_LIST_DIR}/class/vendor/net ${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi + ${CMAKE_CURRENT_LIST_DIR}/class/vendor/display ${CMAKE_CURRENT_LIST_DIR}/class/aoa ${CMAKE_CURRENT_LIST_DIR}/class/gamepad ) @@ -89,6 +90,9 @@ if(CONFIG_CHERRYUSB_DEVICE) if(CONFIG_CHERRYUSB_DEVICE_GAMEPAD) list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/gamepad/usbd_gamepad.c) endif() + if(CONFIG_CHERRYUSB_DEVICE_DISPLAY) + list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/display/usbd_display.c) + endif() if(CONFIG_CHERRYUSB_DEVICE_FSDEV_ST) list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/fsdev/usb_dc_fsdev.c) diff --git a/class/vendor/display/usbd_display.c b/class/vendor/display/usbd_display.c new file mode 100644 index 00000000..2ad0f745 --- /dev/null +++ b/class/vendor/display/usbd_display.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usbd_core.h" +#include "usbd_display.h" +#include "chry_mempool.h" + +struct usbd_disp_frame_header { + uint16_t crc16; //payload crc16 + uint8_t type; //raw rgb,yuv,jpg,other + uint8_t cmd; + uint16_t x; //32bit + uint16_t y; + uint16_t width; //32bit + uint16_t height; + uint32_t frame_id : 10; + uint32_t payload_total : 22; //payload max 4MB +} __PACKED; + +struct usbd_display_priv { + struct chry_mempool pool; + struct usbd_endpoint out_ep; + struct usbd_endpoint in_ep; + struct usbd_display_frame *current_frame; +} g_usbd_display; + +int usbd_display_frame_create(struct usbd_display_frame *frame, uint32_t count) +{ + return chry_mempool_create(&g_usbd_display.pool, frame, sizeof(struct usbd_display_frame), count); +} + +struct usbd_display_frame *usbd_display_frame_alloc(void) +{ + return (struct usbd_display_frame *)chry_mempool_alloc(&g_usbd_display.pool); +} + +int usbd_display_frame_free(struct usbd_display_frame *frame) +{ + return chry_mempool_free(&g_usbd_display.pool, (uintptr_t *)frame); +} + +int usbd_display_frame_send(struct usbd_display_frame *frame) +{ + return chry_mempool_send(&g_usbd_display.pool, (uintptr_t *)frame); +} + +int usbd_display_frame_recv(struct usbd_display_frame **frame, uint32_t timeout) +{ + return chry_mempool_recv(&g_usbd_display.pool, (uintptr_t **)frame, timeout); +} + +uint8_t usb_dispay_dummy[512]; +volatile uint32_t usb_display_buf_offset; +volatile bool usb_display_ignore_frame; + +static void display_notify_handler(uint8_t busid, uint8_t event, void *arg) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONFIGURED: + usb_display_buf_offset = 0; + usb_display_ignore_frame = true; + g_usbd_display.current_frame = NULL; + usbd_ep_start_read(busid, g_usbd_display.out_ep.ep_addr, usb_dispay_dummy, usbd_get_ep_mps(0, g_usbd_display.out_ep.ep_addr)); + break; + default: + break; + } +} + +void usbd_display_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + if (usb_display_ignore_frame) { + // alloc frame for next at the end of current frame + if ((nbytes % usbd_get_ep_mps(0, g_usbd_display.out_ep.ep_addr)) || (nbytes == 0)) { + if (g_usbd_display.current_frame == NULL) { + g_usbd_display.current_frame = usbd_display_frame_alloc(); + if (g_usbd_display.current_frame) { + usb_display_ignore_frame = false; + usb_display_buf_offset = 0; + + goto get_frame; + } else { + goto drop_frame; + } + } else { + usb_display_ignore_frame = false; + usb_display_buf_offset = 0; + + goto get_frame; + } + } else { + goto drop_frame; + } + } else { + struct usbd_disp_frame_header *header = (struct usbd_disp_frame_header *)&g_usbd_display.current_frame->frame_buf[0]; + struct usbd_display_frame *frame; + + if (header->payload_total > g_usbd_display.current_frame->frame_bufsize) { + USB_LOG_ERR("frame overflow, drop it\r\n"); + usb_display_ignore_frame = true; + + goto drop_frame; + } + + usb_display_buf_offset += nbytes; + + if ((nbytes % usbd_get_ep_mps(0, g_usbd_display.out_ep.ep_addr)) || (nbytes == 0)) { + frame = g_usbd_display.current_frame; + g_usbd_display.current_frame = NULL; + + frame->frame_format = header->type; + frame->frame_size = header->payload_total; + usbd_display_frame_send(frame); + + g_usbd_display.current_frame = usbd_display_frame_alloc(); + if (g_usbd_display.current_frame) { + usb_display_ignore_frame = false; + usb_display_buf_offset = 0; + + goto get_frame; + } else { + usb_display_ignore_frame = true; + + goto drop_frame; + } + } else { + goto get_frame; + } + } + return; + +drop_frame: + // drop current frame + usbd_ep_start_read(busid, g_usbd_display.out_ep.ep_addr, usb_dispay_dummy, usbd_get_ep_mps(0, g_usbd_display.out_ep.ep_addr)); + return; +get_frame: + usbd_ep_start_read(busid, g_usbd_display.out_ep.ep_addr, &g_usbd_display.current_frame->frame_buf[usb_display_buf_offset], 16384); + return; +} + +void usbd_display_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ +} + +struct usbd_interface *usbd_display_init_intf(struct usbd_interface *intf, + const uint8_t out_ep, + const uint8_t in_ep, + struct usbd_display_frame *frame, + uint32_t count) +{ + intf->class_interface_handler = NULL; + intf->class_endpoint_handler = NULL; + intf->vendor_handler = NULL; + intf->notify_handler = display_notify_handler; + + g_usbd_display.out_ep.ep_addr = out_ep; + g_usbd_display.out_ep.ep_cb = usbd_display_bulk_out; + g_usbd_display.in_ep.ep_addr = in_ep; + g_usbd_display.in_ep.ep_cb = usbd_display_bulk_in; + usbd_add_endpoint(0, &g_usbd_display.out_ep); + usbd_add_endpoint(0, &g_usbd_display.in_ep); + + for (uint32_t i = 0; i < count; i++) { + USB_ASSERT_MSG(frame[i].frame_bufsize % 16384, "frame_bufsize must be the multiple of 16384"); + } + + usbd_display_frame_create(frame, count); + return intf; +} + +int usbd_display_dequeue(struct usbd_display_frame **frame, uint32_t timeout) +{ + return usbd_display_frame_recv(frame, timeout); +} + +int usbd_display_enqueue(struct usbd_display_frame *frame) +{ + return usbd_display_frame_free(frame); +} \ No newline at end of file diff --git a/class/vendor/display/usbd_display.h b/class/vendor/display/usbd_display.h new file mode 100644 index 00000000..9eac9903 --- /dev/null +++ b/class/vendor/display/usbd_display.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef USBD_DISPLAY_H +#define USBD_DISPLAY_H + +#define USBD_DISPLAY_TYPE_RGB565 0 +#define USBD_DISPLAY_TYPE_RGB888 1 +#define USBD_DISPLAY_TYPE_YUV420 2 +#define USBD_DISPLAY_TYPE_JPG 3 + +struct usbd_display_frame { + uint8_t *frame_buf; + uint32_t frame_bufsize; + uint32_t frame_format; + uint32_t frame_size; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Init display interface driver */ +struct usbd_interface *usbd_display_init_intf(struct usbd_interface *intf, + const uint8_t out_ep, + const uint8_t in_ep, + struct usbd_display_frame *frame, + uint32_t count); + +int usbd_display_dequeue(struct usbd_display_frame **frame, uint32_t timeout); +int usbd_display_enqueue(struct usbd_display_frame *frame); + +#ifdef __cplusplus +} +#endif + +#endif /* USBD_DISPLAY_H */ diff --git a/demo/display/README.md b/demo/display/README.md new file mode 100644 index 00000000..a89f4e3c --- /dev/null +++ b/demo/display/README.md @@ -0,0 +1,3 @@ +Thanks to https://github.com/chuanjinpang/win10_idd_xfz1986_usb_graphic_driver_display project. + +Install tools/display/xfz1986_usb_graphic_250224_rc_sign.exe in windows before use. \ No newline at end of file diff --git a/demo/display/usbdisplay_template.c b/demo/display/usbdisplay_template.c new file mode 100644 index 00000000..3d670258 --- /dev/null +++ b/demo/display/usbdisplay_template.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_display.h" + +#define DISPLAY_IN_EP 0x81 +#define DISPLAY_OUT_EP 0x02 + +#define USBD_VID 0x303A +#define USBD_PID 0x2987 +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +#define USB_CONFIG_SIZE (9 + 9 + 7 + 7) + +#ifdef CONFIG_USB_HS +#define DISPLAY_EP_MPS 512 +#else +#define DISPLAY_EP_MPS 64 +#endif + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID, 0x0101, 0x01) +}; + +static const uint8_t config_descriptor[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0x00, 0x00, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(DISPLAY_IN_EP, 0x02, DISPLAY_EP_MPS, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(DISPLAY_OUT_EP, 0x02, DISPLAY_EP_MPS, 0x00), +}; + +static const uint8_t device_quality_descriptor[] = { + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "cherryusb_R640x480_Ergb16_Fps30_Bl128", /* Product */ + // "cherryusb_R640x480_Ejpg9_Fps30_Bl128", /* Product */ + "2022123456", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + return config_descriptor; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + if (index >= (sizeof(string_descriptors) / sizeof(char *))) { + return NULL; + } + + return string_descriptors[index]; +} + +const struct usb_descriptor usbdisplay_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback +}; + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +struct usbd_interface intf0; + +struct usbd_display_frame frame_pool[2]; + +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t usb_display_buffer[2][640 * 480 * 2]; + +void usbdisplay_init(uint8_t busid, uintptr_t reg_base) +{ + frame_pool[0].frame_buf = usb_display_buffer[0]; + frame_pool[0].frame_bufsize = 640 * 480 * 2; + frame_pool[1].frame_buf = usb_display_buffer[1]; + frame_pool[1].frame_bufsize = 640 * 480 * 2; + + usbd_desc_register(busid, &usbdisplay_descriptor); + + usbd_add_interface(busid, usbd_display_init_intf(&intf0, DISPLAY_OUT_EP, DISPLAY_IN_EP, frame_pool, 2)); + usbd_initialize(busid, reg_base, usbd_event_handler); +} + +void usbdisplay_poll(void) +{ + struct usbd_display_frame *frame; + int ret; + + ret = usbd_display_dequeue(&frame, 0xffffffff); // timeout is not useful for baremental + if (ret < 0) { + return; + } + USB_LOG_INFO("frame type: %u, frame size %u\r\n", frame->frame_format, frame->frame_size); + usbd_display_enqueue(frame); +} \ No newline at end of file diff --git a/tools/display/xfz1986_usb_graphic_250224_rc_sign.exe b/tools/display/xfz1986_usb_graphic_250224_rc_sign.exe new file mode 100644 index 00000000..81c8eb4c Binary files /dev/null and b/tools/display/xfz1986_usb_graphic_250224_rc_sign.exe differ