USB/reset/download #
UART0 和 USB CDC-CAM #
各 ESP32 开发版一般有两个 USB 接口:
- 一个标记为 UART 的 USB 接口,实际连接的是板载的 USB-UART-Bridge 芯片,该芯片再连接到 ESP32 芯片 的 =UART0 串口= (非 USB 接口)。ESP32 支持通过 UART0 串口烧录固件和打印应用 log;
- 一个标记为 USB 的 USB 接口,该接口实际连接 ESP32 芯片的 USB-Serial-JTAG controller(默认)或
USB-OTG controller。两者都实现了 USB-CDC-ACM function,连接到 PC 后都会被识别到串口设备:
- Linux:/dev/ttyACM*
- Windows:COM
- MacOS: /dev/cu*
对于 Mac 查看 USB-OTG 对应的串口和 JTAG 接口:
zj@a:~/docs$ system_profiler SPUSBDataType | grep -A 11 "USB JTAG"
USB JTAG/serial debug unit:
Product ID: 0x1001
Vendor ID: 0x303a
Version: 1.01
Serial Number: 3C:84:27:04:FE:18
Speed: Up to 12 Mb/s
Manufacturer: Espressif
Location ID: 0x02100000 / 2
Current Available (mA): 500
Current Required (mA): 500
Extra Operating Current (mA): 0
对应的设备名称为 /dev/tty.usbmodem* 或 /dev/cu.usbmodem*:
zj@a:~/docs$ ls -l /dev/*.usbmodem*
crw-rw-rw- 1 root 9, 11 5 10 21:38 /dev/cu.usbmodem2101
crw-rw-rw- 1 root 9, 10 5 10 20:16 /dev/tty.usbmodem2101
#+ATTR_HTML: :width 400 :align center [[file:static/images/debug/2024-05-10_21-38-25_screenshot.png]]
zj@a:~/docs$ espflash board-info
[2024-05-10T13:38:06Z INFO ] Detected 2 serial ports
[2024-05-10T13:38:06Z INFO ] Ports which match a known common dev board are highlighted
[2024-05-10T13:38:06Z INFO ] Please select a port
[2024-05-10T13:38:19Z INFO ] Serial port: '/dev/cu.usbmodem2101'
[2024-05-10T13:38:19Z INFO ] Connecting...
[2024-05-10T13:38:21Z INFO ] Using flash stub
Chip type: esp32s3 (revision v0.2)
Crystal frequency: 40 MHz
Flash size: 16MB
Features: WiFi, BLE
MAC address: 3c:84:27:04:fe:18
# 使用 probe-rs 工具
zj@a:~/docs$ probe-rs info
Probing target via JTAG
WARN probe_rs::probe::espusbjtag: More than one TAP detected, defaulting to tap0
No DAP interface was found on the connected probe. ARM-specific information cannot be printed.
Error while reading RISC-V info: Connected target is not a RISC-V device.
Xtensa Chip:
IDCODE: 00120034e5
Version: 1
Part: 8195
Manufacturer: 626 (Tensilica)
Probing target via SWD
Error identifying target using protocol SWD: Probe does not support SWD
zj@a:~/docs$
如果烧写了 USB-OTG 应用,则它的 USB Host/Device Stack 代码在启动时自动将 ESP32 内置的 USB Phy 连接 到 USB-OTG Controller, 从而不会被 PC 识别为 USB-Serial-JTAG Controller。这时需要按住 BOOT 按钮的同 时按 RST 按钮,这时开发板重启并进入 download 模式,内部的 USB PHY 将重新连接 USB-Serial-JTAG Controller。
strapping pins #
开发板一般有一个单独的 RST 按钮,日常高电平,连接 CPU EN 引脚,按下后将 EN 引脚置为低电平,触发 CPU reset。
CPU 上电或 reset 时,CPU 先读取如下引脚的电平来决定启动模式等,reset 结束后,这些引脚恢复正常 IO 功能:
- Chip boot mode:GPIO0, GPIO46;
- VDD_SPI voltage: GPIO45; 《- flash memory 的 voltage
- ROM messages printing: GPIO46;
- JTAG signal source: GPIO3;
各接口的默认情况:
- GPIO0 默认内部 pull-up:默认为高电平。
- GPIO45/GPIO46 默认内部 pull-down:默认为低电平。
- GPIO3 为 floating,电平未知。
2.6.1 Chip Boot Mode Control #
=GPIO0 and GPIO46= control the boot mode after the reset is released. See Table 2-11 Chip Boot Mode Control.
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-09_10-57-01_screenshot.png]]
开发板中 GPIO46 引脚是悬空的,所以默认为内部 pull-down 的低电平。这样主要取决于 GPIO0 引脚的情况:
- GPIO0 在开发版中连接 BOOT 按钮,可以人工控制电平。
- GPIO0 引脚是在 reset 时才会读取的,所以需要和 RST 按钮一起按下。
当 reset 时按住 BOOT 按钮,就将 GPIO0 置为低电平,这时进入 Joint Download Boot Mode:
- USB Download Boot:
- USB-Serial-JTAG Download Boot
- USB-OTG Download Boot
- UART Download Boot
对于 Joint Download Boot Mode,ESP32 支持 =同时从= USB 或 UART0 接口下载固件数据。
- ESP 内部只有一个 USB PHY,而有两个 Controller:USB-Serial-JTAG Controller 和 USB-OTG Controller;
- 该 USB PHY =只能连接到一个 Controller= , =默认连接到 USB-Serial-JTAG Controller= ;
- 后续可以通过软件 =设置寄存器或 eFuse 的方式= 来修改 PHY 连接到的 Controller 类型;
比如,如果要开发 USB-OTG 应用,芯片烧写了 USB Stack 代码(Host 或 Device 模式均可),则在 =USB Host/Device Stack 的初始化代码= 会通过设置寄存器的方式将 PHY 连接到 OTG Controller。这时如果要通过 USB 烧录固件,则需要 =手动按住开发版上的 reset+boot 按钮= ,触发 CPU 复位重启和置为 Boot download mode,这时 USB PHY 重新连接了默认了 USB-Serial-JTAG Controller。
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_22-45-45_screenshot.png]]
As the ESP32-S3 only has a single internal PHY, at first programming you may need to decide how that is going to be used in the intended application by burning eFuses to affect the initial USB configuration.
This affects ROM download mode as well: while =both= USB-OTG as well as the USB Serial/JTAG controller allows =serial programming, only USB-OTG supports the DFU protocol and only the USB Serial/JTAG controller supports JTAG debugging over USB= .
Even when not using USB, eFuse configuration is required when an external JTAG adapter will be used. Table 33-6 indicates which eFuse to burn to get a certain boot-up configuration. Note that this is mostly relevant for the configuration =in download mode= and the bootloader as the configuration can be =altered at runtime= as soon as user code is running.
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-09_00-21-45_screenshot.png]]
After the user program is running, it can =modify the initial configuration by setting registers=. Specifically, RTC_CNTL_SW_HW_USB_PHY_SEL can be used to =have software override the effect of EFUSE_USB_PHY_SEL=: if this bit is set, the USB PHY selection logic will use the value of the RTC_CNTL_SW_USB_PHY_SEL bit in place of that of EFUSE_USB_PHY_SEL.
As shown in 33-3, by default (phy_sel = 0), ESP32-S3 USB Serial/JTAG Controller is connected to internal PHY and USB-OTG is connected to external PHY. =However, when USB-OTG Download mode is enabled, the chip initializes the IO pad connected to the external PHY in ROM when starts up=. The status of each IO pad after initialization is as follows.
In SPI Boot mode, =the ROM bootloader= loads and executes the program from SPI flash to boot the system.
In Joint Download Boot mode, users can download binary files into flash using =UART0 or USB= interface. It is also possible to download binary files into SRAM and execute it from SRAM.
In addition to SPI Boot and Joint Download Boot modes, ESP32-S3 also supports =SPI Download Boot= mode. For details, please see ESP32-S3 Technical Reference Manual > Chapter Chip Boot Control.
2.6.3 ROM Messages Printing Control #
使用 GPIO46 控制,开发版一般悬空,使用默认内部下拉的低电平。
During boot process the messages by the ROM code can be printed to:
- =(Default) UART and USB Serial/JTAG controller.= (同时发送到 UART 和 USB)
- USB Serial/JTAG controller.
- UART.
The ROM messages printing to UART or USB Serial/JTAG controller can be respectively disabled by =configuring registers and eFuse=. For detailed information, please refer to ESP32-S3 Technical Reference Manual > Chapter Chip Boot Control.
2.6.4 JTAG Signal Source Control #
ESP32 内部的 JTAG Controller 的 signal 来源有两种方式:
- USB Serial/JTAG Controller;
- ESP32 单独提供的 JTAG 引脚,如 MTDI,MTCK, MTMS, and MTDO
The strapping pin =GPIO3= can be used to control the source of JTAG signals during the early boot process. This pin does not have any internal pull resistors and the strapping value must be controlled by the external circuit that cannot be in a high impedance state.
As Table 2-13 shows, GPIO3 is used in combination with =EFUSE_DIS_PAD_JTAG, EFUSE_DIS_USB_JTAG, and EFUSE_STRAP_JTAG_SEL= .
GPIO3 开发板一般悬空,内部没有 pull up/down,电平未知。默认是从 USB Serial/JTAG Controller 获得 JTAG signal。
受三个 eFuse + GPIO3 共同控制:
- 三个 eFuse 都为 0(默认) 时,不管 GPIO3 为何电平,JTAG Controller 都从 USB Serial/JTAG Controller 获得 JTAG 信号;《– 默认行为
- 当三个 eFuse 为 0/0/1 时,取决于 GPIO3 电平:
- 0 : JTAG pins MTDI,MTCK, MTMS, and MTDO
- 1 : USB Serial/JTAG Controller
- 其他情况,只会根据 eFuse0/eFuse1 的值来决定是哪一个 source。
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_23-58-35_screenshot.png]]
reset 和 download mode #
开发板上提供了两个轻触开关来控制 CHIP_PU(也称EN/RST)和 GPIO0 电平(未按时高电平,按下时将引脚置为 低电平):
- SW1 BOOT 开关:连接 GPIO0 引脚,为低电平时将芯片置为 =download 模式= ,否则就是 =正常的= 从 FLASH 加载 代码来执行的模式;
- SW2 REST 开关:连接 CHIP_PU(EN/REST 引脚),为低电平时 reset/restart 芯片;
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-09_09-36-13_screenshot.png]]
单独按 BOOT 开关没有效果,需要在按住 RST 开关的同时来按 BOOT 开关:
- 如果没有按 BOOT 开关,reset 复位后是 =正常的启动流程,即从 FLASH 中加载代码= ;
- 如果按了 BOOT 开关,reset 复位后进入 =download 模式,从 UART0/USB 接收固件代码= ;
- ESP32 同时从 UART0 或 USB OTG 接口接收固件代码,具体看烧写器连接的是哪一个接口。
- 使用 UART0 接口时,一般需要额外的 UART-USB-Bridge 芯片(因为当前电脑一般不直接带 UART 的 COM 硬件接口)。
- 使用 USB OTG 则直接使用 ESP32 内置的 USB Controller,不需要额外的硬件;
- 如果使用 USB OTG 接口,则称为 [[https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/dfu.html][DFU( Device Firmware Upgrade)]]操作;
- ESP32 同时从 UART0 或 USB OTG 接口接收固件代码,具体看烧写器连接的是哪一个接口。
除了按物理开关外,也可以通过软件编程来将芯片复位和置为 download 模式:
-
USB-to-UART Bridge 芯片:带有 DTR 和 RTS 引脚(高电平有效),分别经过三极管转换为低电平有效后,连 接芯片的 DTR -> GPIO0 和 RTS -> CHIP_PU 引脚,实现软件控制;
https://dl.espressif.com/dl/schematics/SCH_ESP32-S3-DevKitC-1_V1.1_20221130.pdf
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-09_09-33-00_screenshot.png]]
-
USB 接口(默认连接的是 USB-Serial-JTAG Controller):该接口虚拟的 USB-CDC-ACM 串口设备也提供了虚 拟的 RST/DTR line,实现软件控制;
esptool.py 等 flash 烧写和调试软件,通过软件设置串口的 DTR 和 RST 参数组合,将芯片 reset 和设置 download 模式,download 结束后再 reset 芯片来启动正常的从 flash 加载应用的流程。
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-09_00-17-23_screenshot.png]]
Application Startup Flow #
https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/startup.html
This note explains various steps which happen before app_main function of an ESP-IDF application is called.
The high level view of startup process is as follows:
- =First Stage Bootloader in ROM= loads second-stage bootloader image to RAM (IRAM & DRAM) from flash offset 0x0.
- =Second Stage Bootloader= loads partition table and main app image from flash. Main app incorporates both RAM segments and read-only segments mapped via flash cache.
- =Application Startup executes=. At this point the second CPU and RTOS scheduler are started.
First Stage Bootloader in ROM 是 ESP32 芯片出厂内置在 ROM 中的代码,而且是只读不能修改的。
- CPU 复位后执行位于 ROM 中的 First Stage Bootloader。
Second Stage Bootloader 位于可编程 Flash 中(offset 0x0),是开发者可以修改和自定义的,使用分区表 来描述 Flash 中的内容。
First Stage Bootloader
After SoC reset, =PRO CPU= will start running immediately, executing =reset vector code=, while =APP CPU= will be held in reset. During startup process, PRO CPU does all the initialization. APP CPU reset is de-asserted in the =call_start_cpu0 function of application startup code=. Reset vector code is located in =the mask ROM of the ESP32-S3 chip and cannot be modified=.
Startup code called from the reset vector determines =the boot mode= by checking GPIO_STRAP_REG register for bootstrap pin states. Depending on the reset reason, the following takes place:
Second stage bootloader binary image is loaded from =the start of flash at offset 0x0=.
Second Stage Bootloader
In ESP-IDF, the binary image which resides at =offset 0x0 in flash(用户可编程的 Flash,而非 ESP32 内置的 ROM)= is the second stage bootloader. Second stage bootloader source code is available in =components/bootloader directory of ESP-IDF=. Second stage bootloader is used in ESP-IDF to add flexibility to =flash layout (using partition tables)=, and allow for various flows associated with flash encryption, secure boot, and over-the-air updates (OTA) to take place.
When the first stage bootloader is finished checking and loading the second stage bootloader, it jumps to the second stage bootloader entry point found in =the binary image header=.
Second stage bootloader =reads the partition table found by default at offset 0x8000= (configurable value). See partition tables documentation for more information. The bootloader finds =factory and OTA app partitions=. If OTA app partitions are found in the partition table, the bootloader consults the =otadata partition= to determine which one should be booted. See Over The Air Updates (OTA) for more information.
For a full description of the configuration options available for the ESP-IDF bootloader, see Bootloader.
For the selected partition, second stage bootloader =reads the binary image from flash= one segment at a time:
- For segments with load addresses in internal IRAM (Instruction RAM) or DRAM (Data RAM), the contents are copied from flash to the load address.
- For segments which have load addresses in DROM (Data Stored in flash) or IROM (Code Executed from flash) regions, the flash MMU is configured to provide the correct mapping from the flash to the load address.
Once all segments are processed - meaning code is loaded and flash MMU is set up, second stage bootloader verifies the integrity of the application and then =jumps to the application entry point= found in =the binary image header=.
Application Startup
Application startup covers everything that happens after the app starts executing and before the app_main function starts running inside the main task. This is split into three stages:
Port initialization of hardware and basic C runtime environment.
System initialization of software services and FreeRTOS.
Running the main task and calling app_main.
bootloader #
这里的 bootloader 指的是位于 Flash 0x0 位置的 second stage bootloader。
- 第一个阶段的 bootload 位于 ROM 中,开发者不能修改和控制。
=Bootloader is located at the address 0x1000 in the flash.= 所以 Flash 前 4KB 没有使用。
而缺省情况下分区表在 flash 中的偏移地址是 CONFIG_PARTITION_TABLE_OFFSET value 0x8000, 所以 Bootloader 的 =可用空间是 0x7000 大小= 。
esp-rs 或 esp-idf 的 std 应用都会构建出:
- 应用本身 myespv4;
- 分区表文件 partition-table.bin;
- 缺省 bootloader 文件 bootloader.bin; 这三个文件都会被烧写到 flash 中。
zj@a:~/code/esp32/std$ ls -l target/xtensa-esp32s3-espidf/debug/myespv4
-rwxr-xr-x 1 alizj 12M 5 8 15:54 target/xtensa-esp32s3-espidf/debug/myespv4*
zj@a:~/code/esp32/std$ ls -l target/xtensa-esp32s3-espidf/debug/*.bin
-rw-r--r-- 1 alizj 21K 5 8 15:52 target/xtensa-esp32s3-espidf/debug/bootloader.bin
-rw-r--r-- 1 alizj 3.0K 5 8 15:54 target/xtensa-esp32s3-espidf/debug/partition-table.bin
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/bootloader.html
The ESP-IDF Software Bootloader performs the following functions:
Minimal initial configuration of internal modules;
Initialize Flash Encryption and/or Secure features, if configured;
Select the application partition to boot, based on the partition table and ota_data (if any);
Load this image to RAM (IRAM & DRAM) and transfer management to the image that was just loaded.
=Bootloader is located at the address 0x1000 in the flash.=
For a full description of the startup process including the ESP-IDF bootloader, see Application Startup Flow.
Bootloader Compatibility
It is recommended to update to newer versions of ESP-IDF: when they are released. The OTA (over the air) update process can flash new apps in the field but cannot flash a new bootloader. For this reason, the bootloader supports booting apps built from newer versions of ESP-IDF.
The bootloader does not support booting apps from older versions of ESP-IDF. When updating ESP-IDF manually on an existing product that might need to downgrade the app to an older version, keep using the older ESP-IDF bootloader binary as well.
Note
If testing an OTA update for an existing product in production, always test it using the same ESP-IDF bootloader binary that is deployed in production.
SPI Flash Configuration
Each ESP-IDF application or bootloader =.bin file contains a header with CONFIG_ESPTOOLPY_FLASHMODE, CONFIG_ESPTOOLPY_FLASHFREQ, CONFIG_ESPTOOLPY_FLASHSIZE embedded in it=. These are used to configure the SPI flash during boot.
The First Stage Bootloader in ROM reads =the Second Stage Bootloader header information= from flash and uses this information to load the rest of the Second Stage Bootloader from flash. However, at this time the system clock speed is lower than configured and not all flash modes are supported. When the Second Stage Bootloader then runs, it will reconfigure the flash using values read from the currently selected app binary’s header (and NOT from the Second Stage Bootloader header). This allows an OTA update to change the SPI flash settings in use.
Bootloaders prior to ESP-IDF V4.0 used the bootloader’s own header to configure the SPI flash, meaning these values could not be changed in an update. To maintain compatibility with older bootloaders, the app re-initializes the flash settings during app startup using the configuration found in the app header.
Log Level
The default bootloader =log level is “Info”=. By setting the CONFIG_BOOTLOADER_LOG_LEVEL option, it is possible to increase or decrease this level. This log level is separate from the log level used in the app (see Logging library).
Reducing bootloader log verbosity can improve the overall project boot time by a small amount.
Bootloader Size
When enabling additional bootloader functions, including Flash Encryption or Secure Boot, and especially if setting a high CONFIG_BOOTLOADER_LOG_LEVEL level, then it is important to =monitor the bootloader .bin file’s size=.
When using the default CONFIG_PARTITION_TABLE_OFFSET value 0x8000, =the size limit is 0x8000 bytes.=
If the bootloader binary is too large, then the bootloader build will fail with an error “Bootloader binary size [..] is too large for partition table offset”. If the bootloader binary is flashed anyhow then the ESP32 will fail to boot - errors will be logged about either invalid partition table or invalid bootloader checksum.
Options to work around this are:
Set bootloader compiler optimization back to "Size" if it has been changed from this default value.
Reduce bootloader log level. Setting log level to Warning, Error or None all significantly reduce the final binary size (but may make it harder to debug).
Set CONFIG_PARTITION_TABLE_OFFSET to a higher value than 0x8000, to place the partition table later in the flash. This increases the space available for the bootloader. If the partition table CSV file contains explicit partition offsets, they will need changing so no partition has an offset lower than CONFIG_PARTITION_TABLE_OFFSET + 0x1000. (This includes the default partition CSV files supplied with ESP-IDF.)
When Secure Boot V2 is enabled, there is also an absolute binary size =limit of 48 KB (0xC000 bytes)= (excluding the 4 KB signature), because the bootloader is first loaded into a fixed size buffer for verification.
开发 second stage bootloader:
The current bootloader implementation allows a project to =extend it or modify it=. There are two ways of doing it: by =implementing hooks or by overriding it=. Both ways are presented in =custom_bootloader= folder in ESP-IDF examples:
- bootloader_hooks which presents how to connect some hooks to the bootloader initialization
- bootloader_override which presents how to override the bootloader implementation
In the bootloader space, you cannot use the drivers and functions from other components. If necessary, then the required functionality should be placed in the project’s bootloader_components directory (note that this will increase its size).
If the bootloader grows too large then it can collide with the partition table, which is flashed at offset 0x8000 by default. Increase the partition table offset value to place the partition table later in the flash. This increases the space available for the bootloader.
custom_bootloader: https://github.com/espressif/esp-idf/tree/master/examples/custom_bootloader
https://github.com/espressif/esp-idf/tree/master/components/bootloader
esp-idf/components/bootloader/subproject/main/bootloader_hooks.h
#ifndef BOOTLOADER_HOOKS_H
#define BOOTLOADER_HOOKS_H
/**
* @file The 2nd stage bootloader can be overriden or completed by an application.
* The functions declared here are weak, and thus, are meant to be defined by a user
* project, if required.
* Please check `custom_bootloader` ESP-IDF examples for more details about this feature.
*/
/**
* @brief Function executed *before* the second stage bootloader initialization,
* if provided.
*/
void __attribute__((weak)) bootloader_before_init(void);
/**
* @brief Function executed *after* the second stage bootloader initialization,
* if provided.
*/
void __attribute__((weak)) bootloader_after_init(void);
#endif // BOOTLOADER_HOOKS_H
components/bootloader/subproject/main/bootloader_start.c
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include "esp_log.h"
#include "esp_rom_sys.h"
#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"
#include "bootloader_hooks.h"
static const char *TAG = "boot";
static int select_partition_number(bootloader_state_t *bs);
static int selected_boot_partition(const bootloader_state_t *bs);
/*
* We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
* The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
* We do have a stack, so we can do the initialization in C.
*/
void __attribute__((noreturn)) call_start_cpu0(void)
{
// (0. Call the before-init hook, if available)
if (bootloader_before_init) {
bootloader_before_init();
}
// 1. Hardware initialization
if (bootloader_init() != ESP_OK) {
bootloader_reset();
}
// (1.1 Call the after-init hook, if available)
if (bootloader_after_init) {
bootloader_after_init();
}
#ifdef CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP
// If this boot is a wake up from the deep sleep then go to the short way,
// try to load the application which worked before deep sleep.
// It skips a lot of checks due to it was done before (while first boot).
bootloader_utility_load_boot_image_from_deep_sleep();
// If it is not successful try to load an application as usual.
#endif
// 2. Select the number of boot partition
bootloader_state_t bs = {0};
int boot_index = select_partition_number(&bs);
if (boot_index == INVALID_INDEX) {
bootloader_reset();
}
// 3. Load the app image for booting
bootloader_utility_load_boot_image(&bs, boot_index);
}
// Select the number of boot partition
static int select_partition_number(bootloader_state_t *bs)
{
// 1. Load partition table
if (!bootloader_utility_load_partition_table(bs)) {
ESP_LOGE(TAG, "load partition table error!");
return INVALID_INDEX;
}
// 2. Select the number of boot partition
return selected_boot_partition(bs);
}
/*
* Selects a boot partition.
* The conditions for switching to another firmware are checked.
*/
static int selected_boot_partition(const bootloader_state_t *bs)
{
int boot_index = bootloader_utility_get_selected_boot_partition(bs);
if (boot_index == INVALID_INDEX) {
return boot_index; // Unrecoverable failure (not due to corrupt ota data or bad partition contents)
}
if (esp_rom_get_reset_reason(0) != RESET_REASON_CORE_DEEP_SLEEP) {
// Factory firmware.
#ifdef CONFIG_BOOTLOADER_FACTORY_RESET
bool reset_level = false;
#if CONFIG_BOOTLOADER_FACTORY_RESET_PIN_HIGH
reset_level = true;
#endif
if (bootloader_common_check_long_hold_gpio_level(CONFIG_BOOTLOADER_NUM_PIN_FACTORY_RESET, CONFIG_BOOTLOADER_HOLD_TIME_GPIO, reset_level) == GPIO_LONG_HOLD) {
ESP_LOGI(TAG, "Detect a condition of the factory reset");
bool ota_data_erase = false;
#ifdef CONFIG_BOOTLOADER_OTA_DATA_ERASE
ota_data_erase = true;
#endif
const char *list_erase = CONFIG_BOOTLOADER_DATA_FACTORY_RESET;
ESP_LOGI(TAG, "Data partitions to erase: %s", list_erase);
if (bootloader_common_erase_part_type_data(list_erase, ota_data_erase) == false) {
ESP_LOGE(TAG, "Not all partitions were erased");
}
#ifdef CONFIG_BOOTLOADER_RESERVE_RTC_MEM
bootloader_common_set_rtc_retain_mem_factory_reset_state();
#endif
return bootloader_utility_get_selected_boot_partition(bs);
}
#endif // CONFIG_BOOTLOADER_FACTORY_RESET
// TEST firmware.
#ifdef CONFIG_BOOTLOADER_APP_TEST
bool app_test_level = false;
#if CONFIG_BOOTLOADER_APP_TEST_PIN_HIGH
app_test_level = true;
#endif
if (bootloader_common_check_long_hold_gpio_level(CONFIG_BOOTLOADER_NUM_PIN_APP_TEST, CONFIG_BOOTLOADER_HOLD_TIME_GPIO, app_test_level) == GPIO_LONG_HOLD) {
ESP_LOGI(TAG, "Detect a boot condition of the test firmware");
if (bs->test.offset != 0) {
boot_index = TEST_APP_INDEX;
return boot_index;
} else {
ESP_LOGE(TAG, "Test firmware is not found in partition table");
return INVALID_INDEX;
}
}
#endif // CONFIG_BOOTLOADER_APP_TEST
// Customer implementation.
// if (gpio_pin_1 == true && ...){
// boot_index = required_boot_partition;
// } ...
}
return boot_index;
}
// Return global reent struct if any newlib functions are linked to bootloader
struct _reent *__getreent(void)
{
return _GLOBAL_REENT;
}
开发 bootloader 的支持库: https://github.com/espressif/esp-idf/tree/master/components/bootloader_support
esp bootloader plus: https://github.com/espressif/esp-bootloader-plus/blob/master/README_CN.md esp bootloader plus 是乐鑫基于 ESP-IDF 的 custom bootloader 推出的 =增强版 bootloader= ,支持在 bootloader 阶段 =对压缩或差分 + 压缩的固件进行 解压缩或解压缩 + 反差分,来升级原有固件= 。 下表总结了适配 esp bootloader plus 的乐鑫芯片以及其对应的 ESP-IDF 版本:
Chip ESP-IDF Release/v4.4 ESP-IDF Master ESP32-C3 alt text alt text ESP32-C2 N/A alt text
使用 esp bootloader plus 可以轻松支持下述三种 OTA 更新方式:
- 全量更新:服务器将完整的 new_app.bin 发送到设备端,设备端直接应用 new_app.bin 完成固件更新。
- 压缩更新:服务器将压缩后的 new_app.bin,即 compressed_app.bin 发送到设备端,设备端应用解压后得到的 new_app.bin 完成固件更新。
- 差分压缩更新:服务器使用设备端正在运行的 origin_app.bin 和 需要更新的 new_app.bin 计算出一个补 丁文件 patch.bin,然后将补丁文件发送到设备端;设备端使用补丁,执行反差分,恢复出 new_app.bin, 并应用 new_app.bin 完成固件更新。
注:每种更新方式向下兼容。压缩更新兼容全量更新;差分压缩更新兼容压缩更新和全量更新。
USB Controller #
参考文档:
A =USB host= can establish a connection with =USB devices= through a USB interface, =enabling functions= such as data transfer and power supply.
USB-IF (USB Implementers Forum) defines =USB Device Class standards=, with common device classes such as
- HID (Human Interface Device);
- MSC (Mass Storage Class)
- CDC (Communication Device Class),
- UAC: Audio,
- UVC:Video
- 。。。
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB_Stream/2024-05-08_20-06-53_screenshot.png]]
ESP32 USB 包含两个 Controller:USB-Serial-JTAG Controller 和 USB OTG Full-Speed Controller。
=USB-Serial-JTAG Controller=: A dedicated USB controller with both =USB Serial and USB JTAG= capabilities. It supports firmware download, log printing, CDC transmission, and JTAG debugging through the USB interface. Secondary development such as modifying USB functions or descriptors is not supported.
- 默认启用的是 USB-Serial-JTAG Controller 而非 USB OTG Full-Speed Controller;
- 连接到 PC 后会被识别为一个 USB 串口 + 一个 JTAG 接口;COM* for Windows, /dev/ttyACM* for Linux,
/dev/cu* for MacOS;
- 对于 JTAG 调试,不需要额外的调试器硬件,直接使用 OpenOCD 或 probe-rs 即可调试。
- USB-Serial-JTAG Controller Introduction: https://docs.espressif.com/projects/esp-iot-solution/en/latest/usb/usb_overview/usb_serial_jtag.html
=USB OTG Full-Speed Controller=: refers to a controller that simultaneously supports =USB-OTG, USB Host, and USB Device modes=, with the capability for mode negotiation and switching. It supports two speeds: =Full-speed (12Mbps) and Low-speed (1.5Mbps)=, and is compatible with both USB 1.1 and USB 2.0 protocols. Starting from ESP-IDF version 4.4, it includes =USB Host and USB Device= protocol stacks, as well as various =device class drivers=, supporting user secondary development.
- USB-OTG Peripheral Introduction: https://docs.espressif.com/projects/esp-iot-solution/en/latest/usb/usb_overview/usb_otg.html
只有 USB OTG 才能使用 USB 接口进行 DFU 更新。
只有 USB JTAG 才能进行 JTAG 调试。
USB OTG Host #
USB-OTG 默认实现了 CDC function,所以可以被 PC 识别为串口设备,可以用于 logging, console access, and firmware downloads. The PC will detect a new serial port device, listed as COM* on Windows, /dev/ttyACM* on Linux, and /dev/cu* on MacOS.
ESP32-S2/S3 的 USB-OTG 还实现了 DFU(Device Firmware Upgrade) function,可以实现从 USB 存储设备中获 取firmware 文件然后升级。
USB-OTG 还实现了 USB Host func,USB Host drivers for HID, MSC, CDC, UVC, and other device classes. 这样可以将其他 USB 设备,如 USB 摄像头 UVC,USB 麦克风 UAC 等直接连接到 ESP32 的 USB 接口, 来供 ESP32 上允许的应用来使用。
USB-OTG 还实现了 USB Device func,同时还支持 TinyUSB Stack,这样开发者可以将 ESP32 作为一个 MSC 或其 他自定义 device 来开发使用.
目前只有 P4 支持 High-speed 480 Mbit/s (60 MB/s) USB 2.0 接口,其他设备都是 USB 1.0 接口的 12Mbps。 #+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_20-21-52_screenshot.png]]
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_20-22-07_screenshot.png]]
参考:https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/usb_host.html
USB Hosts 是将 ESP32 作为一个 USB 主控,其他 USB 设备可以连接到 ESP32,ESP32 主控应用来发现和读取 这些 USB 设备的数据。
=The USB Host Library= (hereinafter referred to as the =Host Library=) is the lowest layer of the USB Host stack that exposes a public facing API. In most cases, applications that require USB Host functionality do not need to interface with the Host Library directly. Instead, most applications =use the API provided by a host class driver= that is implemented on top of the Host Library.
However, you may want to use the Host Library directly for some of (but not limited to) the following reasons:
- Implementation of =a custom host class driver=
- Usage of lower level USB Host API
Diagram of the Key Entities of USB Host Functionality
#+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_23-23-21_screenshot.png]]
The diagram above shows the key entities that are involved when implementing USB Host functionality. These entities are:
- The Host Library
- Clients of the Host Library
- Devices
- Host Library Daemon Task
USB Hosts 应用举例:https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host
- 下面是 USB Host Stack 提供的使用 Host Library APIs 实现的各种 host class drivers 例子;
- 使用的 USB-OTG Contrller。
- CDC-ACM:A host class driver for the =Communication Device Class (Abstract Control Model)= is
distributed as a managed component via the ESP-IDF Component Registry.
- The peripherals/usb/host/cdc/cdc_acm_host example uses the =CDC-ACM host driver component to communicate with CDC-ACM devices= .
- The peripherals/usb/host/cdc/cdc_acm_vcp example shows how can you extend the CDC-ACM host driver to interface =Virtual COM Port devices=.
- The CDC-ACM driver is also used in esp_modem examples, where it is used for =communication with cellular modems= .
- MSC:A host class driver for the =Mass Storage Class (Bulk-Only Transport)= is deployed to ESP-IDF
Component Registry.
- The peripherals/usb/host/msc example demonstrates the usage of =the MSC host driver to read and write to a USB flash drive= .
- HID:A host class driver for the =HID (Human interface device)= is distributed as a managed
component via the ESP-IDF Component Registry.
- The peripherals/usb/host/hid example demonstrates the possibility to =receive reports from a USB HID device with several interfaces= .
- UVC:A host class driver for the =USB Video Device Class= is distributed as a managed component via
the ESP-IDF Component Registry.
- The peripherals/usb/host/uvc example demonstrates the usage of =the UVC host driver to receive a video stream from a USB camera= and optionally forward that stream over Wi-Fi.
USB OTG Device #
参考:https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/usb_device.html
USB Device 是将 ESP32 编程为一个 USB 设备,连接到其他 USB Host 设备,如 PC。
The ESP-IDF =USB Device Stack= (hereinafter referred to as the =Device Stack=) enables USB Device support on ESP32-S3. By using the Device Stack, ESP32-S3 can =be programmed with any well defined USB device functions= (e.g., keyboard, mouse, camera), =a custom function= (aka vendor-specific class), or =a combination= of those functions (aka a composite device).
The Device Stack is built around the =TinyUSB stack=, but extends TinyUSB with some minor features and modifications for better integration with ESP-IDF. The Device stack is distributed as a managed component via the ESP-IDF Component Registry.
Features:
- Multiple supported device classes (CDC, HID, MIDI, MSC)
- Composite devices
- Vendor specific classes
- Maximum of 6 endpoints
- 5 IN/OUT endpoints
- 1 IN endpoints
- VBUS monitoring for self-powered devices
USB Device 应用举例:https://github.com/espressif/esp-idf/tree/d4cd437e/examples/peripherals/usb/device
- peripherals/usb/device/tusb_console :How to set up ESP32-S3 chip to get log output via Serial Device connection
- peripherals/usb/device/tusb_serial_device :How to set up ESP32-S3 chip to work as a USB Serial Device
- peripherals/usb/device/tusb_midi : How to set up ESP32-S3 chip to work as a USB MIDI Device
- peripherals/usb/device/tusb_hid : How to set up ESP32-S3 chip to work as a USB Human Interface Device
- peripherals/usb/device/tusb_msc :How to set up ESP32-S3 chip to work as a USB Mass Storage Device
- peripherals/usb/device/tusb_composite_msc_serialdevice:How to set up ESP32-S3 chip to work as a Composite USB Device (MSC + CDC)
USB Device:embassy_usb #
embassy_usb 可以用来实现各种类型的 USB Device:
Native async.
Fully lock-free: endpoints are separate objects that can be used independently without needing a central mutex. If the driver supports it, they can even be used from different priority levels.
Suspend/resume, remote wakeup.
USB composite devices.
Ergonomic descriptor builder.
Ready-to-use implementations for a few USB classes (note you can still implement any class yourself outside the crate).
Serial ports (CDC ACM)
Ethernet (CDC NCM)
Human Interface Devices (HID)
MIDI
类似的还有 usb-device 项目,提供如下能力:
usbd-hid Crates.io - HID class
usbd-human-device-interface Crates.io - HID class
usbd-serial Crates.io - CDC-ACM serial port class
usbd-storage Crates.io - (Experimental) Mass storage port class
usbd-dfu Crates.io - Device Firmware Upgrade class
usbd-picotool-reset Crates.io - picotool-reset class
usbd-midi Crates.io - MIDI class
USB JTAG Controller #
Espressif has ported OpenOCD to support the ESP32-S3 processor and the multi-core FreeRTOS (which is the foundation of most ESP32-S3 apps). Additionally, some extra tools have been written to provide extra features that OpenOCD does not support natively.
This document provides a guide to installing OpenOCD for ESP32-S3 and debugging using GDB under Linux, Windows and macOS
JTAG & OpenOCD 调试原理:
- xtensa-esp32s3-elf-gdb debugger
- OpenOCD on chip debugger,
- and the JTAG adapter connected to ESP32-S3 target. #+ATTR_HTML: :width 400 :align center [[file:static/images/USB/2024-05-08_23-28-53_screenshot.png]]
ESP32 内置的 USB-Serial-JTAG Controller 同时提供了 JTAG + 串口,所以可以通过一根 USB 线来同时使用这 两个功能:
- 串口:对应的设备是 COM* for Windows, /dev/ttyACM* for Linux, /dev/cu* for MacOS;
- JTAG 接口;
缺省情况下,ESP32-S3 JTAG 功能是连接到 USB_SERIAL_JTAG Controller 的,使用 USB_SERIAL_JTAG 输入使用 的是 ESP32 芯片的 GPIO19/20 D-/D+ 引脚。而当不使用 EPS32 内置的 USB_SERIAL_JTAG Controller,而想使用 自己外置的 JTAG 硬件调试器时,需要直接使用 ESP32 提供的 JTAG 引脚 GPIO39-GPIO42。而且需要使用 espefuse.py 工具来 burning eFuse 来实现。
- Burning =DIS_USB_JTAG eFuse= will =permanently disable= the connection between USB_SERIAL_JTAG and the JTAG port of the ESP32-S3. =JTAG interface can then be connected to GPIO39-GPIO42=. Note that =USB CDC functionality= of USB_SERIAL_JTAG will still be usable, i.e., flashing and monitoring over USB CDC will still work.
- Burning =STRAP_JTAG_SEL eFuse= will enable selection of JTAG interface by =a strapping pin=, GPIO3. If the strapping pin is low =when ESP32-S3 is reset=, JTAG interface will use GPIO39-GPIO42. If the strapping pin is high, USB_SERIAL_JTAG will be used as the JTAG interface.
USB UVC/UAC Stream #
usb_stream 是基于 ESP32-S2/ESP32-S3 的 =USB UVC + UAC= 主机驱动程序,支持从 USB 设备读取/写入/控制多 媒体流。例如最多同时支持 1 路摄像头 + 1 路麦克风 + 1 路扬声器播放器数据流。
- 需要将 ESP32 作为 USB Host 模式来使用。
特性:
- 支持通过 UVC Stream 接口获取视频流,支持批量和同步两种传输模式
- 支持通过 UAC Stream 接口获取麦克风数据流,发送播放器数据流
- 支持通过 UAC Control 接口控制麦克风音量、静音等特性
- 支持自动解析设备配置描述符
- 支持对数据流暂停和恢复
USB UVC 功能:获得 JPEG 图片格式视频流;
- 摄像头必须兼容 USB1.1 全速(Fullspeed)模式
- 摄像头需要自带 MJPEG 压缩
- 用户可通过 uvc_streaming_config() 函数手动指定相机接口、传输模式和图像帧参数
- 如使用同步传输摄像头,需要支持设置接口 MPS(Max Packet Size) 为 512
- 同步传输模式,图像数据流 USB 传输总带宽应小于 4 Mbps (500 KB/s)
- 批量传输模式,图像数据流 USB 传输总带宽应小于 8.8 Mbps (1100 KB/s)
USB UAC 功能:支持 speaker 和 mic,一般通过 I2S 接口收发数据,前者是 PCM 编码数字音频播放,后者是读 取 PCM 编码格式的麦克风输入。
- 音频功能必须兼容 UAC 1.0 协议
- 用户需要通过 uac_streaming_config() 函数手动指定 spk/mic 采样率,位宽参数
注:I2S 接口是数字音频信号的传输协议(不一定是物理接口),而 PCM 是数字音频的编码格式,可以结果 DAC 直接转换为模拟信号。
USB UVC + UAC 功能
- UVC 和 UAC 功能可以单独启用,例如仅配置 UAC 来驱动一个 USB 耳机,或者仅配置 UVC 来驱动一个 USB 摄像头
- 如需同时启用 UVC + UAC,该驱动程序目前仅支持具有摄像头和音频接口的复合设备(Composite Device), 不支持同时连接两个单独的设备
uvc_config_t uvc_config = {
.frame_width = 320, // mjpeg width pixel, for example 320
.frame_height = 240, // mjpeg height pixel, for example 240
.frame_interval = FPS2INTERVAL(15), // frame interval (100µs units), such as 15fps
.xfer_buffer_size = 32 * 1024, // single frame image size, need to be determined according to actual testing, 320 * 240 generally less than 35KB
.xfer_buffer_a = pointer_buffer_a, // the internal transfer buffer
.xfer_buffer_b = pointer_buffer_b, // the internal transfer buffer
.frame_buffer_size = 32 * 1024, // single frame image size, need to determine according to actual test
.frame_buffer = pointer_frame_buffer, // the image frame buffer
.frame_cb = &camera_frame_cb, //camera callback, can block in here
.frame_cb_arg = NULL, // camera callback args
}
uac_config_t uac_config = {
.mic_bit_resolution = 16, //microphone resolution, bits
.mic_samples_frequence = 16000, //microphone frequence, Hz
.spk_bit_resolution = 16, //speaker resolution, bits
.spk_samples_frequence = 16000, //speaker frequence, Hz
.spk_buf_size = 16000, //size of speaker send buffer, should be a multiple of spk_ep_mps
.mic_buf_size = 0, //mic receive buffer size, 0 if not use, else should be a multiple of mic_min_bytes
.mic_cb = &mic_frame_cb, //mic callback, can not block in here
.mic_cb_arg = NULL, //mic callback args
};
- 用户可通过 uvc_config_t 配置摄像头分辨率、帧率参数。通过 uac_config_t 配置音频采样率、位宽等参 数。参数说明如下:
- 使用 uvc_streaming_config() 配置 UVC 驱动,如果设备同时支持音频,可使用 uac_streaming_config() 配置 UAC 驱动
- 使用 usb_streaming_start() 打开数据流,之后驱动将响应设备连接和协议协商
- 之后,主机将根据用户参数配置,匹配已连接设备的描述符,如果设备无法满足配置要求,驱动将以警告提示
- 如果设备满足用户配置要求,主机将持续接收 IN 数据流(UVC 和 UAC 麦克风),并在新帧准备就绪后调用
用户的回调
- 接收到新的 MJPEG 图像后,将触发 UVC 回调函数。用户可在回调函数中阻塞,因为它在独立的任务上下 文中工作
- 接收到 mic_min_bytes 字节数据后,将触发 mic 回调。但是这里的回调一定不能以任何方式阻塞,否则 会影响下一帧的接收。如果需要对 mic 进行阻塞操作,可以通过 uac_mic_streaming_read 轮询方式代 替回调方式
- 发送扬声器数据时,用户可通过 uac_spk_streaming_write() 将数据写入内部 ringbuffer,主机将在 USB 空闲时从中获取数据并发送 OUT 数据
- 使用 usb_streaming_control() 控制流挂起/恢复。如果 UAC 支持特性单元,可通过其分别控制麦克风和播 放器的音量和静音
- 使用 usb_streaming_stop() 停止 USB 流,USB 资源将被完全释放。
示例:
-
[[https://github.com/espressif/esp-iot-solution/tree/ce4c75dc/examples/usb/host/usb_camera_mic_spk][usb/host/usb_camera_mic_spk]] This example demonstrates how to use usb_stream component with an ESP device. Example does the following steps:
-
Config a UVC function with specified frame resolution and frame rate, register frame callback
-
Config a UAC function with one microphone and one speaker stream, register mic frame callback
-
Start the USB streaming
-
In image frame callback, if ENABLE_UVC_WIFI_XFER is set to 1, the real-time image can be fetched through ESP32Sx’s Wi-Fi softAP (ssid: ESP32S3-UVC, http: 192.168.4.1), else will just print the image message
-
In mic callback, if ENABLE_UAC_MIC_SPK_LOOPBACK is set to 1, the mic data will be write back to usb speaker, else will just print mic data message
-
For speaker, if ENABLE_UAC_MIC_SPK_LOOPBACK is set to 0, the default sound will be played back
-
[[https://github.com/espressif/esp-iot-solution/tree/ce4c75dc/examples/usb/host/usb_camera_lcd_display][usb/host/usb_camera_lcd_display]] This routine demonstrates how to use the usb_stream component to get an image from a USB camera and display it dynamically on the RGB screen.
-
Pressing the boot button can switch the display resolution.
-
For better performance, please use ESP-IDF release/v5.0 or above versions.
-
Currently only images with a resolution smaller than the screen resolution are displayed When the image width is equal to the screen width, the refresh rate is at its highest.
使用 PSRAM 120M DDR 来提供高速 RGB LCD fps 显示。
- Displaying 800*480 images on ESP32-S3-LCD-EV-Board with psram 120M and
800480 screen can reach 16FPS - Displaying 480*320 images on ESP32-S3-LCD-Ev-Board with psram 120M and
480480 screen can reach 29FPS
使用 esp_jpeg 将 jpeg 解码为 LCD 可以显示的 RGB565 raw picture 格式。
- 在 LCD 屏幕底部显示 FPS;
// https://github.com/espressif/esp-iot-solution/blob/ce4c75dc133e72a566eaf0827073d15f570a41cc/examples/usb/host/usb_camera_lcd_display/main/main.c
static void _display_task(void *arg)
{
uint16_t *lcd_buffer = NULL;
int64_t count_start_time = 0;
int frame_count = 0;
int fps = 0;
int x_start = 0;
int y_start = 0;
int width = 0;
int height = 0;
int x_jump = 0;
int y_jump = 0;
while (!if_ppbuffer_init) {
vTaskDelay(1);
}
while (1) { // 持续显示从 camera 获得并解码的 JPEG 图片,显示 FPS
if (ppbuffer_get_read_buf(ppbuffer_handle, (void *)&lcd_buffer) == ESP_OK) {
if (current_width == BSP_LCD_H_RES && current_height <= BSP_LCD_V_RES) {
x_start = 0;
y_start = (BSP_LCD_V_RES - current_height) / 2;
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, current_width, current_height, cur_frame_buf);
} else if (current_width < BSP_LCD_H_RES && current_height < BSP_LCD_V_RES) {
x_start = (BSP_LCD_H_RES - current_width) / 2;
y_start = (BSP_LCD_V_RES - current_height) / 2;
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 1, 1, cur_frame_buf);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + current_width, y_start + current_height, lcd_buffer);
} else {
/* This section is for refreshing images with a resolution larger than the screen, which is currently not enabled */
if (current_width < BSP_LCD_H_RES) {
width = current_width;
x_start = (BSP_LCD_H_RES - current_width) / 2;
x_jump = 0;
} else {
width = BSP_LCD_H_RES;
x_start = 0;
x_jump = (current_width - BSP_LCD_H_RES) / 2;
}
if (current_height < BSP_LCD_V_RES) {
height = current_height;
y_start = (BSP_LCD_V_RES - current_height) / 2;
y_jump = 0;
} else {
height = BSP_LCD_V_RES;
y_start = 0;
y_jump = (current_height - BSP_LCD_V_RES) / 2;
}
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 1, 1, cur_frame_buf);
for (int i = y_start; i < height; i++) {
esp_lcd_panel_draw_bitmap(panel_handle, x_start, i, x_start + width, i + 1, &lcd_buffer[(y_jump + i)*current_width + x_jump]);
}
}
ppbuffer_set_read_done(ppbuffer_handle);
if (count_start_time == 0) {
count_start_time = esp_timer_get_time();
}
if (++frame_count == 20) {
frame_count = 0;
fps = 20 * 1000000 / (esp_timer_get_time() - count_start_time);
count_start_time = esp_timer_get_time();
ESP_LOGI(TAG, "camera fps: %d %d*%d", fps, current_width, current_height);
}
esp_painter_draw_string_format(painter, x_start, y_start, NULL, COLOR_BRUSH_DEFAULT, "FPS: %d %d*%d", fps, current_width, current_height);
cur_frame_buf = (cur_frame_buf == rgb_frame_buf1) ? rgb_frame_buf2 : rgb_frame_buf1;
}
vTaskDelay(1);
}
}
- [[https://github.com/espressif/esp-iot-solution/tree/ce4c75dc/examples/usb/host/usb_audio_player][usb/host/usb_audio_player]] This example demonstrates how to use usb_stream component to handle an USB Headset device.
- The usb_stream driver currently support 48 KHz 16 bit dual channel at most due to the limitation
of the USB FIFO strategy, if you want to play other format MP3 file, please convert it to
44.1KHz/48 KHz 16 bit dual channel first.
- 将 mp3 解码为 LPCM 格式,使用的是 chmorgan/esp-audio-player 软件 mp3 解码库;
同时支持扬声器和麦克风的,基于 UAC 协议的的 USB 耳机:
// https://github.com/espressif/esp-box/blob/master/examples/usb_headset/main/src/usb_headset.c
static void usb_headset_spk(void *pvParam)
{
while (1) {
SYSVIEW_SPK_WAIT_EVENT_START();
if (s_spk_active == false) {
ulTaskNotifyTake(pdFAIL, portMAX_DELAY);
continue;
}
// clear the notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (s_spk_buf_len == 0) {
continue;
}
// playback the data from the ring buffer chunk by chunk
SYSVIEW_SPK_WAIT_EVENT_END();
SYSVIEW_SPK_SEND_EVENT_START();
size_t bytes_written = 0;
bsp_i2s_write(s_spk_buf, s_spk_buf_len, &bytes_written, 0); // 使用 I2S 写 PCM 数字音频信号
for (int i = 0; i < bytes_written / 2; i ++) {
rb_write(s_spk_buf + i, 2);
}
s_spk_buf_len = 0;
SYSVIEW_SPK_SEND_EVENT_END();
}
}
static void usb_headset_mic(void *pvParam)
{
while (1) {
if (s_mic_active == false) {
ulTaskNotifyTake(pdFAIL, portMAX_DELAY);
continue;
}
// clear the notification
ulTaskNotifyTake(pdTRUE, 0);
// read data from the microphone chunk by chunk
SYSVIEW_MIC_READ_EVENT_START();
size_t bytes_read = 0;
size_t bytes_require = s_mic_bytes_ms * (CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_MS - 1);
esp_err_t ret = bsp_i2s_read(s_mic_write_buf, bytes_require, &bytes_read, 0); // 使用 I2S 从设备读取麦克风 PCM 信号
if (ret != ESP_OK) {
ESP_LOGD(TAG, "Failed to read data from I2S, ret = %d", ret);
SYSVIEW_MIC_READ_EVENT_END();
continue;
}
// swap the buffer
int16_t *tmp_buf = s_mic_read_buf;
UAC_ENTER_CRITICAL();
s_mic_read_buf = s_mic_write_buf;
s_mic_read_buf_len = bytes_read;
s_mic_write_buf = tmp_buf;
UAC_EXIT_CRITICAL();
SYSVIEW_MIC_READ_EVENT_END();
}
}