本文主要介绍ESP32-S3在ubuntu20.04下通过ESP32-S3的USB Serial更新固件的方法以及遇到的问题的解决方法。
现在市面上ESP32-S3的开发板,大多都是ESP32-S3搭配一个USB-To-Serial桥芯片(CP210X、CH340等)来使用,硬件上再设计一个自动烧录电路(如下所示),以达到通过串口实现固件更新的目的。
在实际的产品中,使用USB-To-Serial桥芯片不仅增加成本,也浪费PCB上面积。ESP32-S3是支持USB OTG的,可以使用这个USB口来进行固件更新吗?
当然可以!
除 USB OTG 之外,ESP32-S3 还内置一个 USB 串行/JTAG 控制器。当只使用内部收发器时,USB OTG 和 USB 串行/JTAG 外设共用这个收发器。默认情况下,内部收发器与USB 串行/JTAG 外设相连。当RTC_CNTL_SW_HW_USB_PHY_SEL_CFG 为 0 时,eFuse 中的 EFUSE_USB_PHY_SEL 位决定内部收发器与哪个外设相连。若该位为 0,内部收发器与 USB 串行/JTAG 外设相连;若该位为 1,内部收发器与 USB OTG 外设相连。当 RTC_CNTL_SW_HW_USB_PHY_SEL_CFG 为 1 时,由 RTC_CNTL_SW_USB_PHY_SEL_CFG控制内部收发器与哪个外设相连(与 EFUSE_USB_PHY_SEL 位的使用方式相同)。
CDC-ACM 接口遵循标准 USB CDC-ACM 类别进行虚拟串口通信,包含一个虚拟中断端点(不会发送任何事件,无使用需求)以及一个批量输入端点 (Bulk IN) 和批量输出端点 (Bulk OUT) 进行数据接收和发送。这些端点一次可以处理最高 64 字节的数据包,实现高吞吐量。CDC-ACM 为标准的 USB 设备类型,主机一般无需任何特殊安装程序就能正常工作,也就是说,当一个 USB 调试设备正确连接至主机时,操作系统应能在片刻后显示新的串口信息。 除了通用的通信之外,CDC-ACM 接口还可以复位 ESP32-S3 并选择使其进入下载模式,从而烧录新的固件。这一功能可通过设置虚拟串口的 RTS 和 DTR 线来实现。 当我们的程序没有操作USB,完全按照eFuse的配置让USB工作以后,在Linux主机下看到的设备枚举信息如下:usb 1-4: new full-speed USB device number 114 using xhci_hcd usb 1-4: New USB device found, idVendor=303a, idProduct=1001, bcdDevice= 1.01 usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-4: Product: USB JTAG/serial debug unit usb 1-4: Manufacturer: Espressif usb 1-4: SerialNumber: 48:27:E2:E1:E5:30 cdc_acm 1-4:1.0: ttyACM1: USB ACM device
这时我们使用esptool来更新固件是可以的,命令如下:
esptool --chip esp32s3 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 8MB 0x10000 test_firmware.bin
注意:ubuntu20.04使用apt下载的esptool版本太低,不支持ESP32-S3的固件更新,需要到https://github.com/espressif/esptool下载最新的esptool。
但是当我们实现了ESP32-S3的USB Serial功能以后,就不能再使用esptool自动更新固件了,会一直打印connecting......消息。需要先通过BOOT和Reset引脚让ESP32-S3进入下载模式,才能使用esptool更新固件成功。
USB Serial代码demo如下(基于Arduino IDE):
#if ARDUINO_USB_MODE #warning This sketch should be used when USB is in OTG mode void setup(){} void loop(){} #else #include "USB.h" #if ARDUINO_USB_CDC_ON_BOOT #define HWSerial Serial0 #define USBSerial Serial #else #define HWSerial Serial USBCDC USBSerial; #endif static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data){ if(event_base == ARDUINO_USB_EVENTS){ arduino_usb_event_data_t * data = (arduino_usb_event_data_t*)event_data; switch (event_id){ case ARDUINO_USB_STARTED_EVENT: HWSerial.println("USB PLUGGED"); break; case ARDUINO_USB_STOPPED_EVENT: //HWSerial.println("USB UNPLUGGED"); break; case ARDUINO_USB_SUSPEND_EVENT: HWSerial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en); break; case ARDUINO_USB_RESUME_EVENT: HWSerial.println("USB RESUMED"); break; default: break; } } else if(event_base == ARDUINO_USB_CDC_EVENTS){ arduino_usb_cdc_event_data_t * data = (arduino_usb_cdc_event_data_t*)event_data; switch (event_id){ case ARDUINO_USB_CDC_CONNECTED_EVENT: HWSerial.println("CDC CONNECTED"); break; case ARDUINO_USB_CDC_DISCONNECTED_EVENT: HWSerial.println("CDC DISCONNECTED"); break; case ARDUINO_USB_CDC_LINE_STATE_EVENT: HWSerial.printf("CDC LINE STATE: dtr: %u, rts: %u\n", data->line_state.dtr, data->line_state.rts); break; case ARDUINO_USB_CDC_LINE_CODING_EVENT: HWSerial.printf("CDC LINE CODING: bit_rate: %u, data_bits: %u, stop_bits: %u, parity: %u\n", data->line_coding.bit_rate, data->line_coding.data_bits, data->line_coding.stop_bits, data->line_coding.parity); break; case ARDUINO_USB_CDC_RX_EVENT: HWSerial.printf("CDC RX [%u]:", data->rx.len); { uint8_t buf[data->rx.len]; size_t len = USBSerial.read(buf, data->rx.len); USBSerial.write(buf, data->rx.len); //write back to usb host HWSerial.write(buf, len); } HWSerial.println(); break; case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT: HWSerial.printf("CDC RX Overflow of %d bytes", data->rx_overflow.dropped_bytes); break; default: break; } } } void setup() { HWSerial.begin(115200); HWSerial.setDebugOutput(true); USB.onEvent(usbEventCallback); USBSerial.onEvent(usbEventCallback); USBSerial.begin(); USB.begin(); } void loop() { while(HWSerial.available()){ size_t l = HWSerial.available(); uint8_t b[l]; l = HWSerial.read(b, l); USBSerial.write(b, l); } } #endif /* ARDUINO_USB_MODE */
Arduino IDE里Tools->Board我们选择的是“ESP32S3 Dev Module”,编译下载完之后,把USB接到ubuntu20.04主机上,枚举信息如下:
usb 1-4: new full-speed USB device number 104 using xhci_hcd usb 1-4: New USB device found, idVendor=303a, idProduct=1001, bcdDevice= 1.00 usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-4: Product: ESP32S3_DEV usb 1-4: Manufacturer: Espressif Systems usb 1-4: SerialNumber: 4827E2E1E530 cdc_acm 1-4:1.0: ttyACM1: USB ACM device
解决办法
根据esp32-s3_technical_reference_manual_cn.pdf,有如下信息:
RTC_CNTL_SW_USB_PHY_SEL 置 1 允许 USB OTG 使用内部 USB 接收器。清 0 允许USB-Serial-JTAG 使用内部 USB 接收器。本字段仅在 RTC_CNTL_SW_HW_USB_PHY_SEL 时生效。(R/W) RTC_CNTL_SW_HW_USB_PHY_SEL 置 1 软件分配内部 USB 接收器的使用(RTC_CNTL_SW_USB_PHY_SEL);清 0 硬件分配内部 USB 接收器的使用。(R/W) 所以,当我们想通过ubuntu20.04主机更新ESP32-S3的固件时,先通过CDC-ACM设备发送一个命令给ESP32-S3,ESP32-S3收到这个命令后,从当前USB的工作模式(CDC-ACM切换到USB Serial/JTAG模式),然后就可以用esptool直接进行固件更新了,省去了按BOOT和Reset引脚进入ESP32-S3固件下载模式的步骤。 完整代码如下:
#if ARDUINO_USB_MODE #warning This sketch should be used when USB is in OTG mode void setup(){} void loop(){} #else #include "USB.h" #if ARDUINO_USB_CDC_ON_BOOT #define HWSerial Serial0 #define USBSerial Serial #else #define HWSerial Serial USBCDC USBSerial; #endif #define DR_REG_RTCCNTL_BASE 0x60008000 #define RTC_CNTL_USB_CONF_REG (DR_REG_RTCCNTL_BASE + 0x120) //write value to register #define REG_WRITE(_r, _v) do { \ (*(volatile uint32_t *)(_r)) = (_v); \ } while(0) //read value from register #define REG_READ(_r) ({ \ (*(volatile uint32_t *)(_r)); \ }) void switch_to_download_mode(uint32_t ms) {// 切换到USB Serial/JTAG控制器
REG_WRITE(RTC_CNTL_USB_CONF_REG, 0x0); // 拉低D+和D-持续100ms,触发USB复位重新枚举 REG_WRITE(RTC_CNTL_USB_CONF_REG, 0xa8); delay(ms); REG_WRITE(RTC_CNTL_USB_CONF_REG, 0x0); } static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data){ if(event_base == ARDUINO_USB_EVENTS){ arduino_usb_event_data_t * data = (arduino_usb_event_data_t*)event_data; switch (event_id){ case ARDUINO_USB_STARTED_EVENT: HWSerial.println("USB PLUGGED"); break; case ARDUINO_USB_STOPPED_EVENT: //HWSerial.println("USB UNPLUGGED"); break; case ARDUINO_USB_SUSPEND_EVENT: HWSerial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en); break; case ARDUINO_USB_RESUME_EVENT: HWSerial.println("USB RESUMED"); break; default: break; } } else if(event_base == ARDUINO_USB_CDC_EVENTS){ arduino_usb_cdc_event_data_t * data = (arduino_usb_cdc_event_data_t*)event_data; switch (event_id){ case ARDUINO_USB_CDC_CONNECTED_EVENT: HWSerial.println("CDC CONNECTED"); break; case ARDUINO_USB_CDC_DISCONNECTED_EVENT: HWSerial.println("CDC DISCONNECTED"); break; case ARDUINO_USB_CDC_LINE_STATE_EVENT: HWSerial.printf("CDC LINE STATE: dtr: %u, rts: %u\n", data->line_state.dtr, data->line_state.rts); break; case ARDUINO_USB_CDC_LINE_CODING_EVENT: HWSerial.printf("CDC LINE CODING: bit_rate: %u, data_bits: %u, stop_bits: %u, parity: %u\n", data->line_coding.bit_rate, data->line_coding.data_bits, data->line_coding.stop_bits, data->line_coding.parity); break; case ARDUINO_USB_CDC_RX_EVENT: HWSerial.printf("CDC RX [%u]:", data->rx.len); { uint8_t buf[data->rx.len]; size_t len = USBSerial.read(buf, data->rx.len); USBSerial.write(buf, data->rx.len); //write back to usb host HWSerial.write(buf, len); if (buf[0] == 'U') { //如果收到'U'字符,切换到USB Serial/JTAG模式 switch_to_download_mode(100); } } HWSerial.println(); break; case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT: HWSerial.printf("CDC RX Overflow of %d bytes", data->rx_overflow.dropped_bytes); break; default: break; } } } void setup() { HWSerial.begin(115200); HWSerial.setDebugOutput(true); USB.onEvent(usbEventCallback); USBSerial.onEvent(usbEventCallback); USBSerial.begin(); USB.begin(); } void loop() { while(HWSerial.available()){ size_t l = HWSerial.available(); uint8_t b[l]; l = HWSerial.read(b, l); USBSerial.write(b, l); } } #endif /* ARDUINO_USB_MODE */
如上述代码所示,当ESP32-S3 USB 收到’U‘命令后,把USB切换到USB Serial/JTAG控制器,然后拉低D+和D-触发USB复位,重新枚举后即可直接使用esptool通过主机下CDC-ACM设备更新ESP32-S3的固件。
标签:HWSerial,USB,CDC,S3,ESP32,ARDUINO,data,event From: https://www.cnblogs.com/wanglouxiaozi/p/17987323