尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ESP32-S3 USB CDC虚拟串口:替代UART做调试通道

ESP32-S3 USB CDC虚拟串口:替代UART做调试通道 ESP32-S3 USB CDC 虚拟串口替代 UART 做调试通道先说结论ESP32-S3 的 USB CDC 适合做调试日志输出和固件升级通道延迟比硬件 UART 低、吞吐量更高。但不适合做高频实时数据流**USB 有 1ms 帧间隔限制。**ESP32-S3 有个很实用的功能——原生 USB 接口。不需要 CH340/CP2102 等 USB 转串口芯片直接一根 USB 线连电脑就能做串口调试。用它做调试通道比硬件 UART 更方便不占 UART 引脚波特率不受限可以同时输出调试日志和做 USB 设备HID/MSC 等一、USB CDC 基本配置ESP-IDF 中启用 USB CDC// menuconfig中启用// Component config → USB-OTG → USB-OTG support// Component config → TinyUSB → CDC#includetinyusb.hvoidusb_cdc_init(void){// TinyUSB配置consttinyusb_config_ttusb_cfg{.device_descriptorNULL,// 用默认.string_descriptorNULL,.external_phyfalse,};ESP_ERROR_CHECK(tinyusb_driver_install(tusb_cfg));// CDC配置tinyusb_config_cdcacm_tacm_cfg{.usb_devTINYUSB_USBDEV_0,.cdc_portTINYUSB_CDC_ACM_0,.rx_unread_buf_sz64,.callback_rxNULL,.callback_rx_wanted_charNULL,.callback_line_state_changedNULL,.callback_line_coding_changedNULL,};ESP_ERROR_CHECK(tusb_cdcacm_init(acm_cfg));}二、替代 printf 做调试输出ESP-IDF 默认把printf重定向到 UART0。改成 USB CDC#includeesp_log.h#includetinyusb.h#includetusb_cdc_acm.hstaticuint8_ttx_buf[1024];staticuint16_ttx_len0;// 自定义vprintf输出到USB CDCintusb_cdc_vprintf(constchar*fmt,va_list args){intlenvsnprintf((char*)tx_buftx_len,sizeof(tx_buf)-tx_len,fmt,args);tx_lenlen;// 遇到换行或缓冲区满时发送if(tx_len0(tx_buf[tx_len-1]\n||tx_lensizeof(tx_buf)-64)){tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0,tx_buf,tx_len);tx_len0;}returnlen;}voidapp_main(void){usb_cdc_init();// 重定向esp_log到USB CDCesp_log_set_vprintf(usb_cdc_vprintf);// 现在ESP_LOGI等输出到USB CDCESP_LOGI(MAIN,USB CDC ready);// 也可以直接printfprintf(Hello from USB CDC\n);}三、USB CDC 双向通信CDC 不只是输出还可以接收数据做命令行#defineRX_BUF_SIZE512staticuint8_trx_buf[RX_BUF_SIZE];staticuint16_trx_len0;// CDC接收回调voidcdc_rx_callback(intitf,uint8_tconst*buf,size_tlen,void*ctx){for(size_ti0;ilen;i){if(buf[i]\n||buf[i]\r){// 命令完成处理rx_buf[rx_len]\0;process_command((char*)rx_buf);rx_len0;}elseif(rx_lenRX_BUF_SIZE-1){rx_buf[rx_len]buf[i];}}}voidusb_cdc_init_with_rx(void){// ... 前面的初始化代码 ...tinyusb_config_cdcacm_tacm_cfg{.usb_devTINYUSB_USBDEV_0,.cdc_portTINYUSB_CDC_ACM_0,.rx_unread_buf_sz512,.callback_rxcdc_rx_callback,.callback_rx_wanted_charNULL,.callback_line_state_changedcdc_line_state_cb,.callback_line_coding_changedNULL,};ESP_ERROR_CHECK(tusb_cdcacm_init(acm_cfg));}// 行状态变化DTR/RTSvoidcdc_line_state_cb(intitf,bool dtr,bool rts,void*ctx){if(dtr){printf(Terminal connected\n);}else{printf(Terminal disconnected\n);}}// 命令处理voidprocess_command(constchar*cmd){if(strcmp(cmd,help)0){printf(Commands:\n);printf( help - Show this help\n);printf( info - System info\n);printf( led N - Set LED brightness\n);}elseif(strcmp(cmd,info)0){printf(Chip: ESP32-S3 rev %d\n,esp_chip_get_revision());printf(Free heap: %lu bytes\n,(unsignedlong)esp_get_free_heap_size());printf(Uptime: %lu s\n,(unsignedlong)(xTaskGetTickCount()*portTICK_PERIOD_MS/1000));}elseif(strncmp(cmd,led ,4)0){intbrightnessatoi(cmd4);led_set_brightness(brightness);printf(LED brightness: %d\n,brightness);}else{printf(Unknown command: %s\n,cmd);}}四、USB CDC 做 OTA 升级通道不用 UART/HTTP 做 OTA直接通过 USB 推送固件#defineOTA_BUF_SIZE4096staticesp_ota_handle_tota_handle0;staticesp_partition_t*ota_partitionNULL;staticuint32_tota_written0;staticuint32_tota_total0;staticuint8_tota_buf[OTA_BUF_SIZE];voidota_via_usb_init(void){constesp_partition_t*partesp_partition_find_first(ESP_PARTITION_TYPE_APP,ESP_PARTITION_SUBTYPE_APP_OTA_0,NULL);ota_partition(esp_partition_t*)part;}// OTA命令处理voidprocess_ota_command(constuint8_t*data,size_tlen){// 协议[CMD] [SIZE_4B] [DATA...]uint8_tcmddata[0];switch(cmd){case0x01:{// STARTuint32_ttotal_size(data[1]24)|(data[2]16)|(data[3]8)|data[4];ota_totaltotal_size;ota_written0;esp_err_terresp_ota_begin(ota_partition,OTA_WITH_SEQUENTIAL_WRITES,ota_handle);if(err!ESP_OK){printf(OTA begin failed: %s\n,esp_err_to_name(err));send_ota_response(0xFF);// NACK}else{printf(OTA started, size: %u\n,total_size);send_ota_response(0x00);// ACK}break;}case0x02:{// DATAuint32_tchunk_sizelen-1;esp_err_terresp_ota_write(ota_handle,data1,chunk_size);if(err!ESP_OK){printf(OTA write failed: %s\n,esp_err_to_name(err));send_ota_response(0xFF);}else{ota_writtenchunk_size;send_ota_response(0x00);// 进度if(ota_written%409600){printf(OTA progress: %u/%u (%.1f%%)\n,ota_written,ota_total,(float)ota_written/ota_total*100);}}break;}case0x03:{// ENDesp_err_terresp_ota_end(ota_handle);if(err!ESP_OK){printf(OTA end failed: %s\n,esp_err_to_name(err));break;}erresp_ota_set_boot_partition(ota_partition);if(err!ESP_OK){printf(Set boot partition failed\n);break;}printf(OTA complete, rebooting...\n);vTaskDelay(pdMS_TO_TICKS(100));esp_restart();break;}}}Python 端 OTA 工具importusb.coreimportusb.utilimportstruct,timeclassEsp32UsbOta:def__init__(self):# ESP32-S3 USB VID:PIDself.devusb.core.find(idVendor0x303A,idProduct0x4001)ifnotself.dev:raiseRuntimeError(ESP32-S3 USB not found)self.dev.set_configuration()cfgself.dev.get_active_configuration()intfcfg[(0,0)]# CDC接口self.ep_outusb.util.endpoint_descriptor(usb.util.find_descriptor(intf,custom_matchlambdae:usb.util.endpoint_direction(e.bEndpointAddress)usb.util.ENDPOINT_OUTande.bmAttributes2))self.ep_inusb.util.endpoint_descriptor(usb.util.find_descriptor(intf,custom_matchlambdae:usb.util.endpoint_direction(e.bEndpointAddress)usb.util.ENDPOINT_INande.bmAttributes2))defota_upgrade(self,firmware_path):withopen(firmware_path,rb)asf:fwf.read()print(fFirmware size:{len(fw)}bytes)# STARTself.send_packet(b\x01struct.pack(I,len(fw)))assertself.wait_ack(),Start failed# DATAoffset0chunk_size4096whileoffsetlen(fw):chunkfw[offset:offsetchunk_size]self.send_packet(b\x02chunk)assertself.wait_ack(),fWrite failed at{offset}offsetlen(chunk)print(f\r{offset}/{len(fw)}({offset/len(fw)*100:.1f}%),end)# ENDself.send_packet(b\x03)print(\nOTA complete!)defsend_packet(self,data):self.ep_out.write(data)defwait_ack(self):try:dataself.ep_in.read(1,timeout5000)returndata[0]0x00except:returnFalseif__name____main__:otaEsp32UsbOta()ota.ota_upgrade(firmware.bin)五、同时做 CDC HID 设备ESP32-S3 可以同时做 CDC 串口和 HID 键盘/鼠标#includetinyusb.h#includeclass/hid/hid_device.h// HID描述符staticconsttusb_desc_device_tdevice_descriptor{.bLengthsizeof(tusb_desc_device_t),.bDescriptorTypeTUSB_DESC_DEVICE,.bcdUSB0x0200,.bDeviceClass0xEF,// Multi-interface.bDeviceSubClass0x02,.bDeviceProtocol0x01,.bMaxPacketSize064,.idVendor0x303A,.idProduct0x4001,.bcdDevice0x0100,.iManufacturer1,.iProduct2,.iSerialNumber3,.bNumConfigurations1,};// HID Report Descriptorstaticconstuint8_thid_report[]{TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)),};// 配置描述符CDC HIDstaticconstuint8_tconfig_desc[]{TUD_CONFIG_DESCRIPTOR(1,3,0,TUD_CONFIG_DESC_LENTUD_CDC_DESC_LENTUD_HID_DESC_LEN,100,TUSB_DESC_CONFIGURATION_POWER_BUS_POWERED),TUD_CDC_DESCRIPTOR(0,4,0x81,8,0x02,64,64),TUD_HID_DESCRIPTOR(1,0x03,0x80|0x81,64,hid_report,1,100),};六、实测数据ESP32-S3 240MHzUSB Full Speed (12Mbps)指标硬件UART 921600USB CDC最大吞吐921.6kbps~8Mbps日志延迟1ms1-2msCPU占用1MB传输15%5%发送缓冲128B64B/帧接收延迟1ms1-2msUSB CDC 的优势是吞吐量高理论 8Mbps劣势是延迟固定 1msUSB 帧间隔。七、踩坑清单USB 引脚ESP32-S3 的 USB D-/D 在 GPIO19/GPIO20不能改引脚这两个引脚不能做其他用途USB Full SpeedESP32-S3 原生 USB 是 Full Speed12Mbps。需要 High Speed480Mbps得用 USB-OTG 外设TinyUSB 版本ESP-IDF 5.x 内置 TinyUSB 组件。旧版需要手动集成CDC 枚举时间设备插入后 Windows 枚举需要 2-3 秒。Linux/macOS 快些。枚举前数据会丢DTR 信号Windows 的 PuTTY/串口助手打开端口时 DTR 拉高。可以检测 DTR 判断是否有人看USB 供电USB 线供电能力 500mAUSB2.0。ESP32-S3 传感器功耗超 500mA 要外接电源USB CDC JTAGESP32-S3 的 USB 可以同时做 CDC 和 JTAG 调试USB Serial JTAG。menuconfig 选模式printf 线程安全多线程同时 printf 到 CDC 会乱码。用 mutex 保护或用 ESP_LOG自带锁USB 断连USB 线拔了再插CDC 设备会被重新枚举。应用层要处理重连USB 驱动Windows 10/11 自带 CDC 驱动。Windows 7 需要手动安装驱动
返回列表