diff --git a/examples/device/CMakeLists.txt b/examples/device/CMakeLists.txt index edf5ab8054..78bc98903b 100644 --- a/examples/device/CMakeLists.txt +++ b/examples/device/CMakeLists.txt @@ -26,3 +26,4 @@ family_add_subdirectory(uac2_headset) family_add_subdirectory(usbtmc) family_add_subdirectory(video_capture) family_add_subdirectory(webusb_serial) +family_add_subdirectory(ch341_serial) diff --git a/examples/device/ch341_serial/CMakeLists.txt b/examples/device/ch341_serial/CMakeLists.txt new file mode 100644 index 0000000000..abc4d91da9 --- /dev/null +++ b/examples/device/ch341_serial/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. -) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT}) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c + ) + +# Example include +target_include_directories(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example... see the corresponding function +# in hw/bsp/FAMILY/family.cmake for details. +family_configure_device_example(${PROJECT}) \ No newline at end of file diff --git a/examples/device/ch341_serial/Makefile b/examples/device/ch341_serial/Makefile new file mode 100644 index 0000000000..5a455078e1 --- /dev/null +++ b/examples/device/ch341_serial/Makefile @@ -0,0 +1,12 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../rules.mk diff --git a/examples/device/ch341_serial/src/main.c b/examples/device/ch341_serial/src/main.c new file mode 100644 index 0000000000..cd2133edcd --- /dev/null +++ b/examples/device/ch341_serial/src/main.c @@ -0,0 +1,132 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 + +#include "bsp/board.h" +#include "tusb.h" + +// Change this to 1 and event callback will be logged to stdout +#define CH341_LOG_EVENTS 0 + +#if (CH341_LOG_EVENTS) +static const char * _par_str[] = +{ + "N", + "O", + "E", + "M", + "S" +}; +#endif + +//------------- prototypes -------------// +static void ch341_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + // init device stack on configured roothub port + tud_init(BOARD_TUD_RHPORT); + + while (1) + { + tud_task(); // tinyusb device task + ch341_task(); + } + + return 0; +} + +// Echo back to terminal +static void echo_serial_port( uint8_t buf[], uint32_t count) +{ + tud_ch341_write(buf, count); + tud_ch341_write_flush(); +} + +//--------------------------------------------------------------------+ +// USB CH341 +//--------------------------------------------------------------------+ +static void ch341_task(void) +{ + // connected() The serial port has been opened atleast once + // Will continue to return true even after the serial port is closed. + if ( tud_ch341_connected()) + { + if ( tud_ch341_available() ) + { + uint8_t buf[64]; + + uint32_t count = tud_ch341_read(buf, sizeof(buf)); + + // echo back to terminal + echo_serial_port(buf, count); + } + } +} + +// Invoked when DTR/RTS changes +void tud_ch341_line_state_cb(ch341_line_state_t line_state) +{ +#if (CH341_LOG_EVENTS) + printf("DTR=%u, RTS=%u\r\n", + line_state & CH341_LINE_STATE_DTR_ACTIVE ? 1 : 0, + line_state & CH341_LINE_STATE_RTS_ACTIVE ? 1 : 0); +#else + (void)(line_state); +#endif +} + +// Invoked when line coding changes +void tud_ch341_line_coding_cb(ch341_line_coding_t const* p_line_coding) +{ +#if (CH341_LOG_EVENTS) + printf("BITRATE=%lu (%s%u%u) RX:%s TX:%s\r\n", + (unsigned long)p_line_coding->bit_rate, + _par_str[p_line_coding->parity], + p_line_coding->data_bits, + p_line_coding->stop_bits ? 2 : 1, + p_line_coding->rx_en ? "ON":"OFF", + p_line_coding->tx_en ? "ON":"OFF"); +#else + (void)(p_line_coding); +#endif +} + +// Invoked when a break signal is received +void tud_ch341_send_break_cb(bool is_break_active) +{ +#if (CH341_LOG_EVENTS) + printf("RCV BREAK=%s\r\n", is_break_active ? "ON" : "OFF"); +#else + (void)(is_break_active); +#endif +} diff --git a/examples/device/ch341_serial/src/tusb_config.h b/examples/device/ch341_serial/src/tusb_config.h new file mode 100644 index 0000000000..ad5a3ce04d --- /dev/null +++ b/examples/device/ch341_serial/src/tusb_config.h @@ -0,0 +1,104 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 board.mk +#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 + +#ifndef CFG_TUSB_RHPORT0_MODE +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_CH341 1 + +// CH341 Endpoint max packet sizes (should not change) +#define CFG_TUD_CH341_EP_RX_MAX_PACKET (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CH341_EP_TX_MAX_PACKET CFG_TUD_CH341_EP_RX_MAX_PACKET +#define CFG_TUD_CH341_EP_TXNOTIFY_MAX_PACKET (8) + +// CH341 buffer size for TX and RX data (must be an interval of max packet size) +// more is faster +#define CFG_TUD_CH341_FIFO_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/device/ch341_serial/src/usb_descriptors.c b/examples/device/ch341_serial/src/usb_descriptors.c new file mode 100644 index 0000000000..ea44e9412c --- /dev/null +++ b/examples/device/ch341_serial/src/usb_descriptors.c @@ -0,0 +1,246 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Portions Copyright (c) 2022 Travis Robinson (libusbdotnet@gmail.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 "tusb.h" + +// QinHeng Electronics +#define USB_VID 0x1A86 +// CH341 serial converter +#define USB_PID 0x7523 + +//--------------------------------------------------------------------+ +// Generic Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_GEN_INTERFACE_DESCRIPTOR(_itfnum, _altsetting, _num_endpoints, _itfclass, _itfsubclass, _itfprotocol, _itfstridx) \ + 9, TUSB_DESC_INTERFACE, _itfnum, _altsetting, _num_endpoints, _itfclass, _itfsubclass, _itfprotocol, _itfstridx + +#define TUD_GEN_BULK_EP_DESCRIPTOR(_epnum, _epsize) \ + 7, TUSB_DESC_ENDPOINT, _epnum, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +#define TUD_GEN_INTERRUPT_EP_DESCRIPTOR(_epnum, _epsize, _interval) \ + 7, TUSB_DESC_ENDPOINT, _epnum, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _interval + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, +#if TUD_OPT_HIGH_SPEED + // atleast 0x0200 required for HS + .bcdUSB = 0x0200, +#else + // this is what's in a genuine CH340G's descriptor + .bcdUSB = 0x0110, +#endif + .bDeviceClass = 0xFF, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0263, // CH340G + + // A CH340G only has a product string. All other string indexes are 0. + // The driver doesn't seen to care so we will specify all of them. + .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 +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_CH341, + ITF_NUM_TOTAL +}; + +// interface + 3 endpoints +#define TUD_CH341_DESC_LEN (9+7+7+7) +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CH341_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == 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 etc ... + #define EPNUM_CH341_0_NOTIF 0x81 + #define EPNUM_CH341_0_OUT 0x02 + #define EPNUM_CH341_0_IN 0x82 +#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X + // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_CH341_0_NOTIF 0x81 + #define EPNUM_CH341_0_OUT 0x02 + #define EPNUM_CH341_0_IN 0x83 +#else + #define EPNUM_CH341_0_NOTIF 0x81 + #define EPNUM_CH341_0_OUT 0x02 + #define EPNUM_CH341_0_IN 0x82 +#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, 0x80, 100), + TUD_GEN_INTERFACE_DESCRIPTOR(0, 0, 3, 0xFF, 1, 2 , 0), + TUD_GEN_BULK_EP_DESCRIPTOR(EPNUM_CH341_0_IN, CFG_TUD_CH341_EP_TX_MAX_PACKET), + TUD_GEN_BULK_EP_DESCRIPTOR(EPNUM_CH341_0_OUT, CFG_TUD_CH341_EP_RX_MAX_PACKET), + TUD_GEN_INTERRUPT_EP_DESCRIPTOR(EPNUM_CH341_0_NOTIF, 8, 1) + }; + +#if TUD_OPT_HIGH_SPEED + +// TEST: Im not sure if the ch341 windows driver will allow high speed descriptors. +// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration + +uint8_t const desc_hs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x80, 100), + TUD_GEN_INTERFACE_DESCRIPTOR(0, 0, 3, 0xFF, 1, 2 , 0), + TUD_GEN_BULK_EP_DESCRIPTOR(EPNUM_CH341_0_IN, CFG_TUD_CH341_EP_TX_MAX_PACKET), + TUD_GEN_BULK_EP_DESCRIPTOR(EPNUM_CH341_0_OUT, CFG_TUD_CH341_EP_RX_MAX_PACKET), + TUD_GEN_INTERRUPT_EP_DESCRIPTOR(EPNUM_CH341_0_NOTIF, 8, 1) +}; + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +tusb_desc_device_qualifier_t const desc_device_qualifier = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + .bDeviceClass = 0xFF, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const*) &desc_device_qualifier; +} + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + // if link speed is high return fullspeed config, and vice versa + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration; +} + +#endif // highspeed + +// 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 + +#if TUD_OPT_HIGH_SPEED + // Although we are highspeed, host may be fullspeed. + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; +#else + return desc_fs_configuration; +#endif +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "CH341 serial converter", // 2: Product + "123456", // 3: Serials, should use chip ID +}; + +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 = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; iregister_data[REG_DIDX_DIVISOR]; + uint8_t baud_fact = (p_ch341->register_data[REG_DIDX_PRESCALER] >> 2) & 0x01; + uint8_t baud_ps = p_ch341->register_data[REG_DIDX_PRESCALER] & 0x03; + uint32_t calc_bit_rate = (uint32_t)(CH341_CLKRATE / ((1 << (12 - 3 * baud_ps - baud_fact)) * baud_div)); + int index; + + for (index = 0; index < (int)((sizeof(ch341_known_baud_rates) / sizeof(uint32_t))); index++) + { + int max_diff = (int)((ch341_known_baud_rates[index] * 2) / 1000); + if (calc_bit_rate >= ch341_known_baud_rates[index] - max_diff && calc_bit_rate <= ch341_known_baud_rates[index] + max_diff) + { + p_ch341->line_coding.bit_rate = ch341_known_baud_rates[index]; + return; + } + if (calc_bit_rate < ch341_known_baud_rates[index]) + break; + } + p_ch341->line_coding.bit_rate = calc_bit_rate; +} + +static inline void ch341_decode_lcr(ch341d_interface_t *p_ch341) +{ + // decode rx/tx enable + uint8_t lcr = p_ch341->register_data[REG_DIDX_LCR]; + p_ch341->line_coding.tx_en = (lcr & CH341_LCR_ENABLE_TX) ? true : false; + p_ch341->line_coding.rx_en = (lcr & CH341_LCR_ENABLE_RX) ? true : false; + + // decode parity (0=None, 1=Odd, 2=Even, 3=Mark, 4=Space) + if (lcr & CH341_LCR_ENABLE_PAR) + { + if ((lcr & (CH341_LCR_PAR_EVEN)) == 0) + p_ch341->line_coding.parity = 1; // Odd + else + p_ch341->line_coding.parity = 2; // Even + + if ((lcr & (CH341_LCR_MARK_SPACE))) + p_ch341->line_coding.parity+=2; + } + else + { + p_ch341->line_coding.parity = 0; // None + } + + // decode stop bits (0=1 stop bit, 2=2 stop bits) + p_ch341->line_coding.stop_bits = (lcr & CH341_LCR_STOP_BITS_2) ? 2 : 0; + + // decode data bits + if ((lcr & 0x3) == CH341_LCR_CS5) + p_ch341->line_coding.data_bits = 5; + else if ((lcr & 0x3) == CH341_LCR_CS6) + p_ch341->line_coding.data_bits = 6; + else if ((lcr & 0x3) == CH341_LCR_CS7) + p_ch341->line_coding.data_bits = 7; + else + p_ch341->line_coding.data_bits = 8; +} + +static inline ch341_line_state_t ch341_decode_mcr(ch341d_interface_t *p_ch341, uint16_t mcr) +{ + p_ch341->register_data[REG_DIDX_MCR_MSR] = (p_ch341->register_data[REG_DIDX_MCR_MSR] & (~(CH341_BIT_DTR | CH341_BIT_RTS))) | (mcr & (CH341_BIT_DTR | CH341_BIT_RTS)); + + ch341_line_state_t line_state = 0; + if (mcr & CH341_BIT_DTR) + line_state &= ~CH341_LINE_STATE_DTR_ACTIVE; + else + line_state |= CH341_LINE_STATE_DTR_ACTIVE; + + if (mcr & CH341_BIT_RTS) + line_state &= ~CH341_LINE_STATE_RTS_ACTIVE; + else + line_state |= CH341_LINE_STATE_RTS_ACTIVE; + + return line_state; +} + +static void ch341_write_regs(ch341d_interface_t *p_ch341, uint16_t wValue, uint16_t wIndex) +{ + int i; + + for (i = 0; i < 2; i++) + { + uint8_t reg = wValue & 0xFF; + uint8_t regval = wIndex & 0xFF; + switch (reg) + { + case CH341_REG_BREAK: + p_ch341->register_data[REG_DIDX_BREAK] = regval; + p_ch341->break_state_changed = true; + break; + case CH341_REG_DIVISOR: + p_ch341->register_data[REG_DIDX_DIVISOR] = regval; + p_ch341->line_coding_changed = true; + break; + case CH341_REG_LCR: + p_ch341->register_data[REG_DIDX_LCR] = regval; + p_ch341->line_coding_changed = true; + break; + case CH341_REG_LCR2: + p_ch341->register_data[REG_DIDX_LCR2] = regval; + break; + case CH341_REG_PRESCALER: + regval &= 0x7; + p_ch341->register_data[REG_DIDX_PRESCALER] = regval; + p_ch341->line_coding_changed = true; + break; + case CH341_REG_0x0F: + p_ch341->register_data[REG_DIDX_0x0F] = regval; + break; + case CH341_REG_0x27: + p_ch341->register_data[REG_DIDX_0x27] = regval; + break; + case CH341_REG_0x2C: + p_ch341->register_data[REG_DIDX_0x2C] = regval; + break; + default: + break; + } + + wValue >>= 8; + wIndex >>= 8; + } +} + +static void ch341_read_regs(ch341d_interface_t *p_ch341, uint8_t* data, uint16_t wValue) +{ + int i; + + for (i = 0; i < 2; i++) + { + uint8_t reg = wValue & 0xFF; + switch (reg) + { + case CH341_REG_BREAK: + data[i] = p_ch341->register_data[REG_DIDX_BREAK]; + break; + case CH341_REG_DIVISOR: + data[i] = p_ch341->register_data[REG_DIDX_DIVISOR]; + break; + case CH341_REG_LCR: + data[i] = p_ch341->register_data[REG_DIDX_LCR]; + break; + case CH341_REG_LCR2: + data[i] = p_ch341->register_data[REG_DIDX_LCR2]; + break; + case CH341_REG_PRESCALER: + data[i] = p_ch341->register_data[REG_DIDX_PRESCALER]; + break; + case CH341_REG_0x0F: + data[i] = p_ch341->register_data[REG_DIDX_0x0F]; + break; + case CH341_REG_0x27: + data[i] = p_ch341->register_data[REG_DIDX_0x27]; + break; + case CH341_REG_0x2C: + data[i] = p_ch341->register_data[REG_DIDX_0x2C]; + break; + case CH341_REG_MCR_MSR: + data[i] = p_ch341->register_data[REG_DIDX_MCR_MSR]; + break; + case CH341_REG_MCR_MSR2: + data[i] = p_ch341->register_data[REG_DIDX_MCR_MSR2]; + break; + default: + break; + } + wValue >>= 8; + } +} + +static void _prep_out_transaction (ch341d_interface_t* p_ch341) +{ + uint8_t const rhport = BOARD_TUD_RHPORT; + uint16_t available = tu_fifo_remaining(&p_ch341->rx_ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= sizeof(p_ch341->epout_buf), ); + + // claim endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_ch341->ep_out), ); + + // fifo can be changed before endpoint is claimed + available = tu_fifo_remaining(&p_ch341->rx_ff); + + if ( available >= sizeof(p_ch341->epout_buf) ) + { + usbd_edpt_xfer(rhport, p_ch341->ep_out, p_ch341->epout_buf, sizeof(p_ch341->epout_buf)); + } + else + { + // Release endpoint since we don't make any transfer + usbd_edpt_release(rhport, p_ch341->ep_out); + } +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_ch341_connected(void) +{ + return tud_ready() && _ch341d_itf[0].connected; +} + +ch341_line_state_t tud_ch341_get_line_state (void) +{ + return _ch341d_itf[0].line_state; +} + +uint32_t tud_ch341_set_modem_state(ch341_modem_state_t modem_states) +{ + ch341d_interface_t *p_ch341 = &_ch341d_itf[0]; + uint8_t buffer[4]; + + modem_states &= CH341_MODEM_STATE_ALL; + + // FIXME? I beleive these signals are all active=low. I can only test the CTS line with + // my CH340G breakout board and I know that with nothing connected or the CTS line shorted + // to positive, the value transferred is 0xF (all 1's). With the CTS line shorted to ground, + // the value is 0xE (all 1's except CTS) + modem_states = (~modem_states) & CH341_MODEM_STATE_ALL; + + p_ch341->register_data[REG_DIDX_MCR_MSR] = (p_ch341->register_data[REG_DIDX_MCR_MSR] & 0xF0) | (modem_states); + return 1; + + buffer[0] = 0x08; + + // This is sometimes 0x3F and other times 0xBF but I beleieve that's because when I short + // CTS to ground there is no de-bounce so it flickers. + buffer[1] = 0x3F; + + buffer[2] = 0x90 | modem_states; + + // Register MCR_MSR2 is presumably for future use as it's always 0xEE but I beleive it to be + // for additional mcr/msr status. + buffer[3] = p_ch341->register_data[REG_DIDX_MCR_MSR2]; + + // write fifo + uint16_t ret = tu_fifo_write_n(&p_ch341->txnotify_ff, buffer, 4); + + if (ret == 4) + { + // start transfer + ret = tud_ch341_notify_flush(); + } + return ret; +} + +ch341_modem_state_t tud_ch341_get_modem_state(void) +{ + ch341d_interface_t *p_ch341 = &_ch341d_itf[0]; + ch341_modem_state_t modem_states = p_ch341->register_data[REG_DIDX_MCR_MSR]; + modem_states = (~modem_states) & CH341_MODEM_STATE_ALL; + + return modem_states; +} + +void tud_ch341_get_line_coding (ch341_line_coding_t* coding) +{ + (*coding) = _ch341d_itf[0].line_coding; +} + +void tud_ch341_set_wanted_char (char wanted) +{ + _ch341d_itf[0].wanted_char = wanted; +} + + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_ch341_available(void) +{ + return tu_fifo_count(&_ch341d_itf[0].rx_ff); +} + +uint32_t tud_ch341_read(void* buffer, uint32_t bufsize) +{ + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + uint32_t num_read = tu_fifo_read_n(&p_ch341->rx_ff, buffer, bufsize); + _prep_out_transaction(p_ch341); + return num_read; +} + +bool tud_ch341_peek(uint8_t* chr) +{ + return tu_fifo_peek(&_ch341d_itf[0].rx_ff, chr); +} + +void tud_ch341_read_flush (void) +{ + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + tu_fifo_clear(&p_ch341->rx_ff); + _prep_out_transaction(p_ch341); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_ch341_write(void const* buffer, uint32_t bufsize) +{ + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + uint16_t ret = tu_fifo_write_n(&p_ch341->tx_ff, buffer, bufsize); + + // flush if queue more than packet size + if (tu_fifo_count(&p_ch341->tx_ff) >= sizeof(p_ch341->epin_buf)) + { + tud_ch341_write_flush(); + } + + return ret; +} + +uint32_t tud_ch341_write_flush (void) +{ + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + + // Skip if usb is not ready yet + TU_VERIFY( tud_ready(), 0 ); + + // No data to send + if ( !tu_fifo_count(&p_ch341->tx_ff) ) return 0; + + uint8_t const rhport = BOARD_TUD_RHPORT; + + // Claim the endpoint + TU_VERIFY( usbd_edpt_claim(rhport, p_ch341->ep_in), 0 ); + + // Pull data from FIFO + uint16_t const count = tu_fifo_read_n(&p_ch341->tx_ff, p_ch341->epin_buf, sizeof(p_ch341->epin_buf)); + + if ( count ) + { + TU_ASSERT( usbd_edpt_xfer(rhport, p_ch341->ep_in, p_ch341->epin_buf, count), 0 ); + return count; + }else + { + // Release endpoint since we don't make any transfer + // Note: data is dropped if terminal is not connected + usbd_edpt_release(rhport, p_ch341->ep_in); + return 0; + } +} +uint32_t tud_ch341_notify_flush(void) +{ + ch341d_interface_t *p_ch341 = &_ch341d_itf[0]; + + // Skip if usb is not ready yet + TU_VERIFY(tud_ready(), 0); + + // No data to send + if (!tu_fifo_count(&p_ch341->txnotify_ff)) + return 0; + + uint8_t const rhport = BOARD_TUD_RHPORT; + + // Claim the endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_ch341->ep_notif), 0); + + // Pull data from FIFO + uint16_t const count = tu_fifo_read_n(&p_ch341->txnotify_ff, p_ch341->txnotify_ff_buf, sizeof(p_ch341->txnotify_ff_buf)); + + if (count) + { + TU_ASSERT(usbd_edpt_xfer(rhport, p_ch341->ep_notif, p_ch341->txnotify_ff_buf, count), 0); + return count; + } + else + { + // Release endpoint since we don't make any transfer + // Note: data is dropped if terminal is not connected + usbd_edpt_release(rhport, p_ch341->ep_notif); + return 0; + } +} + +uint32_t tud_ch341_write_available (void) +{ + return tu_fifo_remaining(&_ch341d_itf[0].tx_ff); +} + +bool tud_ch341_write_clear (void) +{ + return tu_fifo_clear(&_ch341d_itf[0].tx_ff); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void ch341d_init(void) +{ + tu_memclr(_ch341d_itf, sizeof(_ch341d_itf)); + + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + + p_ch341->wanted_char = -1; + + // default line coding is : stop bit = 1, parity = none, data bits = 8 + p_ch341->line_coding.bit_rate = 115200; + p_ch341->line_coding.stop_bits = 0; + p_ch341->line_coding.parity = 0; + p_ch341->line_coding.data_bits = 8; + p_ch341->line_coding.rx_en = false; + p_ch341->line_coding.tx_en = false; + + p_ch341->line_state = 0; + + p_ch341->register_data[REG_DIDX_0x0F] = 0xD2; + p_ch341->register_data[REG_DIDX_0x27] = 0x00; + p_ch341->register_data[REG_DIDX_0x2C] = 0x0B; + p_ch341->register_data[REG_DIDX_BREAK] = 0xBF; + p_ch341->register_data[REG_DIDX_DIVISOR] = 0xCC; + p_ch341->register_data[REG_DIDX_LCR] = 0xC3; + p_ch341->register_data[REG_DIDX_LCR2] = 0x00; + p_ch341->register_data[REG_DIDX_PRESCALER] = 0x03; + p_ch341->register_data[REG_DIDX_MCR_MSR] = 0xFF; + p_ch341->register_data[REG_DIDX_MCR_MSR2] = 0xEE; + + // Config RX fifo + tu_fifo_config(&p_ch341->rx_ff, p_ch341->rx_ff_buf, TU_ARRAY_SIZE(p_ch341->rx_ff_buf), 1, false); + // Config TX fifo + tu_fifo_config(&p_ch341->tx_ff, p_ch341->tx_ff_buf, TU_ARRAY_SIZE(p_ch341->tx_ff_buf), 1, false); + // Config TX NOTIFY fifo + tu_fifo_config(&p_ch341->txnotify_ff, p_ch341->txnotify_ff_buf, TU_ARRAY_SIZE(p_ch341->txnotify_ff_buf), 1, false); + +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&p_ch341->rx_ff, NULL, osal_mutex_create(&p_ch341->rx_ff_mutex)); + tu_fifo_config_mutex(&p_ch341->tx_ff, osal_mutex_create(&p_ch341->tx_ff_mutex), NULL); + tu_fifo_config_mutex(&p_ch341->txnotify_ff, osal_mutex_create(&p_ch341->txnotify_ff_mutex), NULL); +#endif +} + +void ch341d_reset(uint8_t rhport) +{ + (void) rhport; + + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + + tu_memclr(p_ch341, ITF_MEM_RESET_SIZE); + tu_fifo_clear(&p_ch341->rx_ff); + tu_fifo_clear(&p_ch341->tx_ff); + tu_fifo_clear(&p_ch341->txnotify_ff); + tu_fifo_set_overwritable(&p_ch341->tx_ff, false); + tu_fifo_set_overwritable(&p_ch341->txnotify_ff, false); +} + +uint16_t ch341d_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + // CH340G interface class, subclass, and protocol + TU_VERIFY(0xFF == itf_desc->bInterfaceClass && + 0x01 == itf_desc->bInterfaceSubClass && + 0x02 == itf_desc->bInterfaceProtocol, 0); + + ch341d_interface_t * p_ch341 = &_ch341d_itf[0]; + + //------------- CH341 Interface -------------// + p_ch341->itf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + while (p_desc && drv_len < max_len) + { + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) + { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *)p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_BULK) + { + if (p_ch341->ep_in == 0) + { + // Open endpoint pair + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &p_ch341->ep_out, &p_ch341->ep_in), 0); + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + } + else if (desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) + { + if (p_ch341->ep_notif == 0) + { + // Open notification endpoint + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + p_ch341->ep_notif = desc_ep->bEndpointAddress; + } + } + } + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // Prepare for incoming data + _prep_out_transaction(p_ch341); + + return drv_len; +} + +// The CH341 driver sends vendor requests only. we will pipe these into the ch341d_control_xfer_cb so it can handle everything +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + return ch341d_control_xfer_cb(rhport, stage, request); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool ch341d_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + + if ((request->bmRequestType_bit.type) == TUSB_REQ_TYPE_VENDOR && ((request->bmRequestType_bit.recipient) == TUSB_REQ_RCPT_DEVICE)) + { + switch(request->bRequest) + { + case CH341_REQ_READ_VERSION: // (0x5F) + if (stage == CONTROL_STAGE_SETUP) + { + p_ch341->ep0_in_buf[0] = 0x31; + p_ch341->ep0_in_buf[1] = 0; + tud_control_xfer(rhport, request, p_ch341->ep0_in_buf, 2); + } + break; + + case CH341_REQ_READ_REG: // (0x95) + if (stage == CONTROL_STAGE_SETUP) + { + ch341_read_regs(p_ch341, p_ch341->ep0_in_buf, request->wValue); + tud_control_xfer(rhport, request, p_ch341->ep0_in_buf, 2); + } + break; + + case CH341_REQ_WRITE_REG: // (0x9A) + if (stage == CONTROL_STAGE_SETUP) + { + tud_control_status(rhport, request); + } + else if (stage == CONTROL_STAGE_ACK) + { + ch341_write_regs(p_ch341, request->wValue, request->wIndex); + } + break; + + + case CH341_REQ_SERIAL_INIT: // (0xA1) + if (stage == CONTROL_STAGE_SETUP) + { + tud_control_status(rhport, request); + } + else if (stage == CONTROL_STAGE_ACK) + { + // wValue = LCR/LCR2 + // wIndex = BAUDDIV/PRESCALAR + if (request->wValue && request->wIndex) + { + ch341_write_regs(p_ch341, CH341_REG_LCR << 8 | CH341_REG_LCR2, request->wValue); + ch341_write_regs(p_ch341, CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER, request->wIndex); + p_ch341->line_state_changed = true; + p_ch341->connected = true; + } + } + break; + + case CH341_REQ_MODEM_CTRL: // (0xA4) + if (stage == CONTROL_STAGE_SETUP) + { + tud_control_status(rhport, request); + } + else if (stage == CONTROL_STAGE_ACK) + { + p_ch341->line_state = ch341_decode_mcr(p_ch341, request->wValue); + p_ch341->line_state_changed = true; + } + break; + + default: + return false; + } + + if (p_ch341->line_state_changed) + { + p_ch341->line_state_changed = false; + if (tud_ch341_line_state_cb) + tud_ch341_line_state_cb(p_ch341->line_state); + } + if (p_ch341->line_coding_changed) + { + p_ch341->line_coding_changed = false; + ch341_decode_bit_rate(p_ch341); + ch341_decode_lcr(p_ch341); + if (tud_ch341_line_coding_cb) + tud_ch341_line_coding_cb(&p_ch341->line_coding); + } + if (p_ch341->break_state_changed) + { + p_ch341->break_state_changed = false; + if (tud_ch341_send_break_cb) + tud_ch341_send_break_cb(p_ch341->register_data[REG_DIDX_BREAK]); + } + + return true; + } + return false; +} + +bool ch341d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + + ch341d_interface_t* p_ch341 = &_ch341d_itf[0]; + + // Received new data + if ( ep_addr == p_ch341->ep_out ) + { + tu_fifo_write_n(&p_ch341->rx_ff, &p_ch341->epout_buf, xferred_bytes); + + // Check for wanted char and invoke callback if needed + if ( tud_ch341_rx_wanted_cb && (((signed char) p_ch341->wanted_char) != -1) ) + { + for ( uint32_t i = 0; i < xferred_bytes; i++ ) + { + if ( (p_ch341->wanted_char == p_ch341->epout_buf[i]) && !tu_fifo_empty(&p_ch341->rx_ff) ) + { + tud_ch341_rx_wanted_cb(p_ch341->wanted_char); + } + } + } + + // invoke receive callback (if there is still data) + if (tud_ch341_rx_cb && !tu_fifo_empty(&p_ch341->rx_ff) ) tud_ch341_rx_cb(); + + // prepare for OUT transaction + _prep_out_transaction(p_ch341); + } + + // Data sent to host, we continue to fetch from tx fifo to send. + // Note: This will cause incorrect baudrate set in line coding. + // Though maybe the baudrate is not really important !!! + if ( ep_addr == p_ch341->ep_in ) + { + // invoke transmit callback to possibly refill tx fifo + if ( tud_ch341_tx_complete_cb ) tud_ch341_tx_complete_cb(); + + if ( 0 == tud_ch341_write_flush() ) + { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP Packet size and not zero + if (!tu_fifo_count(&p_ch341->tx_ff) && xferred_bytes && (0 == (xferred_bytes & (CFG_TUD_CH341_EP_TX_MAX_PACKET-1))) ) + { + if ( usbd_edpt_claim(rhport, p_ch341->ep_in) ) + { + usbd_edpt_xfer(rhport, p_ch341->ep_in, NULL, 0); + } + } + } + } + + return true; +} + +#endif diff --git a/src/class/ch341/ch341_device.h b/src/class/ch341/ch341_device.h new file mode 100644 index 0000000000..9ba74bbbbf --- /dev/null +++ b/src/class/ch341/ch341_device.h @@ -0,0 +1,189 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Portions Copyright (c) 2022 Travis Robinson (libusbdotnet@gmail.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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_CH341_DEVICE_H_ +#define _TUSB_CH341_DEVICE_H_ + +#include "common/tusb_common.h" +#include "ch341.h" + +//////////////////////////////////////////////////////////////////////////////// +// TR 06-09-22 NOTE: /////////////////////////////////////////////////////////// +// I've left the "_n_" functions in to maintain some API compatibility with the +// CDC implementation but there is no way to have more than one CH341 interface +// due to the nature of how host and device communicate. +//////////////////////////////////////////////////////////////////////////////// + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_CH341_EP_RX_MAX_PACKET + #define CFG_TUD_CH341_EP_RX_MAX_PACKET (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +#ifndef CFG_TUD_CH341_EP_TX_MAX_PACKET + #define CFG_TUD_CH341_EP_TX_MAX_PACKET CFG_TUD_CH341_EP_RX_MAX_PACKET +#endif + +#ifndef CFG_TUD_CH341_EP_TXNOTIFY_MAX_PACKET + #define CFG_TUD_CH341_EP_TXNOTIFY_MAX_PACKET (8) +#endif + +#ifndef CFG_TUD_CH341_FIFO_SIZE + #define CFG_TUD_CH341_FIFO_SIZE CFG_TUD_CH341_EP_RX_MAX_PACKET +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/** \addtogroup CH341_Serial Serial + * @{ + * \defgroup CH341_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Single Port) +// The CH341 is a vendor class device that sends it's requests directly +// to the device, hence there can be only 1 CH341 interface per device. +//--------------------------------------------------------------------+ + +// Check if terminal is connected to this port +bool tud_ch341_connected (void); + +// Get current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) +ch341_line_state_t tud_ch341_get_line_state (void); + +// Get current line encoding: bit rate, stop bits parity etc .. +void tud_ch341_get_line_coding (ch341_line_coding_t* coding); + +// Set special character that will trigger tud_ch341_rx_wanted_cb() callback on receiving +void tud_ch341_set_wanted_char (char wanted); + +// Get the number of bytes available for reading +uint32_t tud_ch341_available (void); + +// Read received bytes +uint32_t tud_ch341_read (void* buffer, uint32_t bufsize); + +// Read a byte, return -1 if there is none +static inline +int32_t tud_ch341_read_char (void); + +// Clear the received FIFO +void tud_ch341_read_flush (void); + +// Get a byte from FIFO at the specified position without removing it +bool tud_ch341_peek (uint8_t* ui8); + +// Write bytes to TX FIFO, data may remain in the FIFO for a while +uint32_t tud_ch341_write (void const* buffer, uint32_t bufsize); + +// Write a byte +static inline +uint32_t tud_ch341_write_char (char ch); + +// Write a null-terminated string +static inline +uint32_t tud_ch341_write_str (char const* str); + +// Force sending data if possible, return number of forced bytes +uint32_t tud_ch341_write_flush (void); + +// Return the number of bytes (characters) available for writing to TX FIFO buffer in a single n_write operation. +uint32_t tud_ch341_write_available (void); + +// Clear the transmit FIFO +bool tud_ch341_write_clear (void); + +// Send line events to host. (IE: CTS, DSR, RI, DCD) +uint32_t tud_ch341_set_modem_state(ch341_modem_state_t modem_states); + +// Returns the current state of the modem. (IE: CTS, DSR, RI, DCD) +ch341_modem_state_t tud_ch341_get_modem_state(void); + +// Force sending notify data if possible, return number of forced bytes +uint32_t tud_ch341_notify_flush(void); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +TU_ATTR_WEAK void tud_ch341_rx_cb(void); + +// Invoked when received `wanted_char` +TU_ATTR_WEAK void tud_ch341_rx_wanted_cb(char wanted_char); + +// Invoked when space becomes available in TX buffer +TU_ATTR_WEAK void tud_ch341_tx_complete_cb(void); + +// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE +TU_ATTR_WEAK void tud_ch341_line_state_cb(ch341_line_state_t line_state); + +// Invoked when line coding is change via SET_LINE_CODING +TU_ATTR_WEAK void tud_ch341_line_coding_cb(ch341_line_coding_t const* p_line_coding); + +// Invoked when received send break +TU_ATTR_WEAK void tud_ch341_send_break_cb(bool is_break_active); + +//--------------------------------------------------------------------+ +// Inline functions +//--------------------------------------------------------------------+ +static inline int32_t tud_ch341_read_char (void) +{ + uint8_t ch; + return tud_ch341_read(&ch, 1) ? (int32_t) ch : -1; +} + +static inline uint32_t tud_ch341_write_char (char ch) +{ + return tud_ch341_write((void const*)&ch, 1); +} + +static inline uint32_t tud_ch341_write_str (char const* str) +{ + return tud_ch341_write((uint8_t const*)str, strlen(str)); +} + +/** @} */ +/** @} */ + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void ch341d_init (void); +void ch341d_reset (uint8_t rhport); +uint16_t ch341d_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool ch341d_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool ch341d_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CH341_DEVICE_H_ */ diff --git a/src/device/usbd.c b/src/device/usbd.c index c199e647e1..6c3ffa2eb4 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -233,6 +233,18 @@ static usbd_class_driver_t const _usbd_driver[] = .sof = NULL }, #endif + + #if CFG_TUD_CH341 + { + DRIVER_NAME("CH341") + .init = ch341d_init, + .reset = ch341d_reset, + .open = ch341d_open, + .control_xfer_cb = ch341d_control_xfer_cb, + .xfer_cb = ch341d_xfer_cb, + .sof = NULL + }, + #endif }; enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; diff --git a/src/tusb.h b/src/tusb.h index b776d7d010..9431334e84 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -113,6 +113,10 @@ #if CFG_TUD_BTH #include "class/bth/bth_device.h" #endif + + #if CFG_TUD_CH341 + #include "class/ch341/ch341_device.h" + #endif #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index e0447b10c5..af2324fbcd 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -352,6 +352,10 @@ typedef int make_iso_compilers_happy; #define CFG_TUD_BTH 0 #endif +#ifndef CFG_TUD_CH341 + #define CFG_TUD_CH341 0 +#endif + #ifndef CFG_TUD_ECM_RNDIS #ifdef CFG_TUD_NET #warning "CFG_TUD_NET is renamed to CFG_TUD_ECM_RNDIS"