ESP32-S3 通过USB Serial更新固件

本文主要介绍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)
  • RTC_CNTL_USB_PAD_PULL_OVERRIDE 置 1 允许软件控制内部 USB 接收器的 USB D+/D- 上拉和下拉电阻。(R/W)
  • RTC_CNTL_USB_DP_PULLDOWN 置 1 使能 USB+ 下拉电阻。本字段仅在RTC_CNTL_USB_PAD_PULL_OVERRIDE 配置时生效。(R/W)
  • RTC_CNTL_USB_DM_PULLDOWN 置 1 使能 USB- 下拉电阻。本字段仅在RTC_CNTL_USB_PAD_PULL_OVERRIDE 配置时生效。(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的固件。

 

usb 1-4: new full-speed USB device number 119 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  // 以上是USB CDC-ACM模式枚举信息
usb 1-4: USB disconnect, device number 119 // ESP32-S3收到'U'命令,USB切换到USB Serial/JTAG控制器,触发USB复位
usb 1-4: new full-speed USB device number 120 using xhci_hcd  //以下是USB Serial/JTAG控制器模式枚举信息
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

 

posted @ 2024-01-25 15:52  闹闹爸爸  阅读(701)  评论(0编辑  收藏  举报