Espressif 玩转 固件下载

今天来撸一下 EspressifSerial Protocol。虽然 Espressif 已经提供了 esptool 工具用于固件下载,但架不住还是有将下载功能集成到自己工具中的需求呀。

对于 Serial ProtocolEspressif 已经提供了比较完善的文档。但个人认为文档写的过于繁琐,没有提炼出精华。因为对于我们来说,只想了解固件是如何下载即可,文档中竟然连最基本的下载流程图都木有。没办法,就只能自己动手丰衣足食了~~~

该文档只是专注于如何下载固件,对于 esptool 提供的其它功能,均可以在 Serial Protocol 中找到对应的实现方式。如果还有不明白的地方,建议阅读 esptool.py 源码。

最后,esptool 可以通过 --trace 选项将下载过程中的 Request Packets & Response Packets 打印出来,结合文档效果加倍哦!

预备理论

  1. Loader
  2. SLIP
  3. Request Packets & Response Packets

Loader

Loader 可以被理解为一段代码,负责接收 UART 的数据(这些数据其实就是 Request Packets),执行特定的动作并返回动作的执行结果(这些结果其实就是 Response Packets)。
大家有没有理解上面这句话呢?其实说白了就是一句话的事,我们假设所有的 UART 数据均为 PC 发送,那么 PC 和芯片之间就是一问一答的机制。PC 叫芯片做什么,芯片回答"完成"或者“没完成”就完事。
目前 Espressif 芯片支持两种 Loader:

  1. ROM Loader: 固定在 ROM 中,功能有限。
    ROM Loader
  2. Stub Loader: 由 ROM Loader 加载到 RAM 中,功能可扩展(Espressif 推荐此方式)。
    Stub Loader

SLIP

SLIP 全称为 Serial Line Internet Protocol (串行线路网际协议),是在串行通信线路上支持 TCP/IP 的一种点对点式的链路层通信协议。该协议仅仅是在串行线路上对 IP 数据包进行了简单的封装。对于该协议,官方的理解到此就可以了。
SLIP 协议中存在几个特殊字节:

Hex ValueAbbreviationDescription
0xC0ENDFrame End
0xDBESCFrame Escape
0xDCESC_ENDTransposed Frame End
0xDDESC_ESCTransposed Frame Escape

对于这些特殊字节,SLIP 协议规定处理方式如下:

  1. 每个 SLIP 包以 0xC0 (END) 开头,以 0xC0 (END) 结尾
  2. 除 SLIP 包头包尾外,payload 中遇到 0xC0 (END),转换成 0xDB (ESC), 0xDC (ESC_END)
  3. payload 中遇到 0xDB (ESC),转换成 0xDB (ESC), 0xDD (ESC_ESC)
    SLIP

SLIP 与 Serial Protocol 的关系?
SLIPSerial ProtocolUART 层面的数据组织方式。PC 发送的 Request Packet 被封装成逻辑上的 IP Datagram
比如 PC 下发的 Request Packet 为 0x01-0xDB-0x49-0xC0-0x15,那么最终在 UART 上传输的字节流为 0xC0-0x01-0xDD-0xDB-0x49-0xDC-0xDB-0x15-0xC0。

Request Packets & Response Packets

PC 下发的有效数据(Request Packets)被封装到 IP Datagram 放入 SLIP 包中。而这些有效数据(Request Packets)又被 Serial Protocol 定义成固定的格式。

Request Packets

ByteNameComment
0DirectionAlways 0x00 for requests
1CommandCommand identifier
2-3SizeLength of Data field, in bytes
4-7ChecksumSimple checksum of part of the Data field (only used for some commands)
8-nDataVariable length data payload (0-65535 bytes, as indicated by Size field)

Request Packets

Command

command 为 PC 下发给 Loader,让 Loader 执行特定动作的命令。
ROM Loader 和 Stub Loader 支持的命令集不同,命令的更多细节可参考 Espressif 官方的文档 Command Packet
在这里插入图片描述

Data

Request Packets 中的 Data 域仅仅适用于 *_DATA 命令。Data 域有其自有的格式:

ByteNameComment
0-3Data to write lengthLittle endian 32-bit word
4-7Sequence numberLittle endian 32-bit word. The sequence number is 0 based
8-150Two words of all zero, unused
16-Data to writeLength given at beginning of payload
Checksum

Checksum 仅仅在 *_DATA 命令中有效,因为它只计算 Request Packets 中的 Data 域中的 Data to write
Checksum 的简单算法可参考源文件 stub_flasher.c

/* esptool protcol "checksum" is XOR of 0xef and each byte of
   data payload. */
static uint8_t calculate_checksum(uint8_t *buf, int length)
{
  uint8_t res = 0xef;
  for(int i = 0; i < length; i++) {
    res ^= buf[i];
  }
  return res;
}

从算法可以看出,Checksum 只是对数据做了个简单的校验,不足以确保数据的有效性。所以 Espressif 在文档 Serial Protocol 中推荐使用 SPI_FLASH_MD5 命令对固件做 MD5 校验。

Response Packets

ByteNameComment
0DirectionAlways 0x01 for responses
1CommandSame value as Command identifier in the request packet that trigged the response
2-3SizeSize of Data field. At least the length of the Status Bytes (2 or 4 bytes)
4-7ValueResponse value used by READ_REG command. Zero otherwise
8-nDataVariable length data payload. Length indicated by Size field

Response Packets

Status Bytes

Response Packets 中 Data 域中最后 2 字节或者 4 字节代表状态码。用于指示对应的 Request Packets 动作执行的结果。

2 字节状态码适用以下 Loader:

  1. ESP8266 ROM Loader
  2. ESP8266 Stub Loader
  3. ESP32 Stub Loader
ByteNameComment
Size-2StatusStatus flag. success (0) or failure(1)
Size-1Errorif Status is 1, this indicates the type of error

4 字节状态码适用以下 Loader:

  1. ESP32 ROM Loader
ByteNameComment
Size-4StatusStatus flag. success (0) or failure(1)
Size-3Errorif Status is 1, this indicates the type of error
Size-2Reserved
Size-1Reserved
Error

对于 Status Bytes 中的 Error 域所表示的错误原因,ROM Loader 和 Stub Loader 中有各自的意思。

ROM Loader:
ROM Loader Errors
Stub Loader:
Stub Loader 中的错误原因定义在源文件 stub_flasher.h 中。

/* Error codes */
typedef enum {
  ESP_OK = 0,

  ESP_BAD_DATA_LEN = 0xC0,
  ESP_BAD_DATA_CHECKSUM = 0xC1,
  ESP_BAD_BLOCKSIZE = 0xC2,
  ESP_INVALID_COMMAND = 0xC3,
  ESP_FAILED_SPI_OP = 0xC4,
  ESP_FAILED_SPI_UNLOCK = 0xC5,
  ESP_NOT_IN_FLASH_MODE = 0xC6,
  ESP_INFLATE_ERROR = 0xC7,
  ESP_NOT_ENOUGH_DATA = 0xC8,
  ESP_TOO_MUCH_DATA = 0xC9,

  ESP_CMD_NOT_IMPLEMENTED = 0xFF,
} esp_command_error;

Data Frame

上面说了这么多枯燥无味的理论知识,相信能看到这里的读者估计已经云里雾里了吧!说实话,我写到这里都有点迷糊了。没关系,下面我们通过一个例子来加深下理解。
FLASH_DATA 命令为例,来梳理下 PC 发送给 Stub Loader 的 Request Packets 及 Stub Loader 回复的 Response Packets。
先来看一下 FLASH_DATA 命令 Data 域的格式:
FLASH_DATA
假设现在 PC 下只想发送一包数据给 Stub Loader,原始数据为 0x01-0xC0-0x02-0xDB-0xC0-0x03-0x04-0x05,那么原始数据在发送给 Stub Loader 之前,需要满足以下三条规则:

  1. SLIP 协议对特殊字节的处理方式
  2. Data 域格式
  3. Request Packets 格式

Request Packet
Request Packet
Response Packet
Response Packet

固件下载

整个下载过程可分为 6 个步骤:

  1. 同步
  2. 获取芯片信息(可选)
  3. 下载 text.bin
  4. 下载 data.bin
  5. 修改波特率(可选)
  6. 下载固件

同步

当芯片处于 UART Bootloader 模式时,PC 下发的首条命令必须为同步命令 (Sync)
Sync 命令包含了一个 36 字节的 Data 域,用来检测配置的串口波特率。
Sync
典型的 Sync 命令的 Request Packet 如下:

0xC0 0x00 0x08 0x24 0x00 0x00 0x00 0x00 0x00 0x07 0x07 0x12 0x20 0x55 0x55 0x55 0x55 0x55 0x55 0x55
0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55
0x55 0x55 0x55 0x55 0x55 0xC0

典型的 Sync 命令的 Response Packet 如下 (2字节状态码):

0xC0 0x01 0x08 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xC0

典型的 Sync 命令的 Response Packet 如下 (4字节状态码):

0xC0 0x01 0x08 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xC0

获取芯片信息(可选)

确定芯片类型

PC 可以下发 READ_REG 命令来读取芯片内部的寄存器来获取芯片信息。
READ_REG
对于 ESP8266ESP8285,可以读取地址为 0x3FF00050 的寄存器来进行分辨。

Request Packet

0xC0 0x00 0x0A 0x04 0x00 0x00 0x00 0x00 0x00 0x50 0x00 0xF0 0x3F 0xC0

Response Packet

0xC0 0x01 0x0A 0x02 0x00 0x00 0x00 0x14 0x39 0x00 0x00 0xC0

如何分辨方法如下:

if ((addr & (1 << 4)) != 0) {
    printf("is ESP8285!\n");
} else {
    printf("is ESP8266!\n");
}

对于 ESP32 ,可以读取地址为 0x3FF5A00C 的寄存器来进行分辨。

 uint32_t addr = 0;

 char *type[] = {"ESP32-D0WDQ6", "ESP32-D0WD", "ESP32-D2WD", "ESP32-U4WDH", "ESP32-PICO-D4", "ESP32-PICO-V3-02"};

 err = loader_read_reg_cmd(fd, 0x3ff5a00c, &addr);
 if (err != ESP_LOADER_SUCCESS) {
     printf("Cannot read chip reg.\n");
     return;
 }

 uint8_t pkg_version = (addr >> 9) & 0x07;
 pkg_version += ((addr >> 2) & 0x1) << 3;

 if (pkg_version >= sizeof(type)/sizeof(type[0])) {
     printf("unknown ESP32!\n");
 } else {
     printf("is %s!\n", type[pkg_version]);
 }

对于 ESP32C3 ,可以读取地址为 0x60008850 的寄存器来进行分辨。

uint32_t addr = 0;

char *type[] = {"ESP32-C3"};

err = loader_read_reg_cmd(fd, 0x60008850, &addr);
if (err != ESP_LOADER_SUCCESS) {
    printf("Cannot read chip reg.\n");
    return;
}

uint8_t pkg_version = (addr >> 21) & 0x07;
if (pkg_version >= sizeof(type)/sizeof(type[0])) {
    printf("unknown ESP32-C3!\n");
} else {
    printf("is %s!\n", type[pkg_version]);
}

读取 MAC 地址

  • ESP8266 可以分别读取地址为 0x3FF00050, 0x3FF000540x3FF0005C 的寄存器来确定 MAC 地址。
  • ESP32 可以分别读取地址为 0x3FF5A004, 和 0x3FF5A008 的寄存器来确定 MAC 地址。
  • ESP32C3 可以分别读取地址为 0x60008844, 和 0x60008848 的寄存器来确定 MAC 地址。

具体的实现方式可以参考 ESP-IDF 中的 esp_efuse_mac_get_default 接口或参考 esptool.py 文件。

下载 text.bin

对于一段可以运行的程序来说,最基本应包含三个段:

  1. text 段:用来存放代码
  2. data 段:用来存放初始化过的全局变量和静态变量
  3. bss 段:用来存放未初始化的全局变量和静态变量

所以,如果想要让 Stub Loader 跑起来的话,最起码要将 text.bindata.bin 通过 ROM Loader 加载到 RAM 中。text.bin 会被 ROM Loader 加载到 IRAM 中,data.bin 会被 ROM Loader 加载到 DRAM 中。对于如何获取 text.bindata.bin ,可参考 Stub Loader。

下载 text.bin 需要通过以下两条命令:

  1. MEM_BEGIN
    MEM_BEGIN
    该命令在 Request_Packets 中的 Data 域中需要指定 4 个参数:

    1. total size
    2. number of data packets
    3. data size in one packet
    4. memory offset

    假设 text.bin 的大小为 3624 字节,设置的 data size in one packet1024 字节,那么 number of data packets 则为 3624 / 1024 = 4data size in one packet 一般设置为 4 的倍数即可。memory offset 一般跟链接文件 ld 有关。例如,ESP32 的 Stbu Loader 的链接文件 stub_32.ld 中定义如下:

    MEMORY {
      iram : org = 0x400BE000, len = 0x1000
      dram : org = 0x3ffcc000, len = 0x14000
    }
    
    ENTRY(stub_main)
    
    SECTIONS {
      .text : ALIGN(4) {
        *(.literal)
        *(.text .text.*)
      } > iram
    
      .bss : ALIGN(4) {
         _bss_start = ABSOLUTE(.);
        *(.bss)
    	_bss_end = ABSOLUTE(.);
      } > dram
    
      .data : ALIGN(4) {
        *(.data)
        *(.rodata .rodata.*)
      } > dram
    }
    
    INCLUDE "rom_32.ld"
    
  2. MEM_DATA
    MEM_DATA
    该命令在 Request_Packets 中的 Data 域中需要指定 4 个参数:

    1. data size
    2. sequencu number
    3. 0
    4. 0

这里要思考一个问题:

text.bin 的大小为 3624 字节,设置的 data size in one packet1024 字节,那么 number of data packets 则为 3624 / 1024 = 4,那么 text.bin 的实际大小即位 4 * 1024 = 4096 字节,远远大于 3624 字节的实际大小。使用 MEM_DATA 发送最后一包数据时 data size 应该设置为 1024 还是 3624 - 1024 * 3 = 552 字节呢?

自己在实验过程中测试了 1024552 这两种方案,发现其实是都可以的。

  1. 设置为 1024 时,多出的数据均要以 0xFF 作为填充 (文档中推荐此方式)
  2. 设置为 552 也即实际的剩余长度 (esptool 采用此种方式)

下载 data.bin

下载 data.bin 需要通过以下三条命令:

  1. MEM_BEGIN
    MEM_BEGIN

  2. MEM_DATA
    MEM_DATA

  3. MEM_END
    MEM_END
    该命令在 Request_Packets 中的 Data 域中需要指定 2 个参数:

    1. execute flag, 一般为 0 即可
    2. entry point address

    MEM_END 命令下发之后,芯片除了会回复对应的 response 之外,如果 Stub Loader 成功运行,则会主动发送一个 SLIP 包,payload 为 OHAI (0x4F 0x48 0x41 0x49)。该 SLIP 包是唯一一个芯片主动发送的包

    0xC0 0x4F 0x48 0x41 0x49 0xC0
    

修改波特率(可选)

CHANGE_BAUDRATE 命令用来修改波特率。

CHANGE_BAUDRATE
该命令执行完成之后,PC 在接收到 response 之后可延迟一段时间(主要是给芯片一些时间来完成波特率的切换操作)在切换到新的波特率进行通信。

下载固件

下载固件可以分为普通下载压缩下载两种方式(esptool 默认采用压缩下载方式)。这里只介绍普通下载方式以方便理解。

普通下载方式需要通过以下四条命令。下载的固件将被 Stub Loader 写入到 FLASH 中。

  1. FLASH_BEGIN
    FLASH_BEGIN
  2. FLASH_DATA
    FLASH_DATA
  3. SPI_FLASH_MD5 (可选)
    SPI_FLASH_MD5
  4. FLASH_END
    FLASH_END

这里以 hello_world demo 产生的固件为例,介绍下怎样使用上述四条命令。

  1. bootloader.bin (起始地址为 0x00000000,大小为 9984)
  2. partition-table.bin (起始地址为 0x00008000,大小为 3072)
  3. hello_world.bin (起始地址为 0x00010000,大小为 168720)

Download
上述命令在使用时需要注意以下几点:

  1. FLASH_BEGIN 命令在 ROM Loader 和 Stub Loader 中参数的定义是不一样的,不要被文档上的描述误导,文档上描述的是对应 ROM Loader 中的定义。在 Stub Loader 中参数定义如下(可以参考 stub_flasher.c):

    • size to erase: total size
    • number of data packets: ignore
    • data size in one packet: ignore
    • flash offset: offset
        case ESP_FLASH_BEGIN:
          /* parameters (interpreted differently to ROM flasher):
             0 - erase_size (used as total size to write)
             1 - num_blocks (ignored)
             2 - block_size (should be MAX_WRITE_BLOCK, relies on num_blocks * block_size >= erase_size)
             3 - offset (used as-is)
           */
            if (command->data_len == 16 && data_words[2] > MAX_WRITE_BLOCK) {
                error = ESP_BAD_BLOCKSIZE;
            } else {
                error = verify_data_len(command, 16) || handle_flash_begin(data_words[0], data_words[3]);
            }
          break;
    
  2. FLASH_BEGIN 命令不需要考虑擦除 FLASH 的问题,擦除工作在 Stub Loader 接收到 FLASH_BEGIN 命令后会根据 size to erase 参数自动进行 4 KB 对齐,然后擦除这一部分空间。

  3. FLASH_END 命令可指定 run to user code 来运行下载的固件。

协议进阶

该部分是对 Serial Protocol 自己所作的扩展。如果只想了解固件是如何下载的,这部分略过即可。

该部分会从以下两部分展开进行讲述:

  1. Stub Loader
  2. 压缩下载

Stub Loader

Stub Loader 由 text 段和 data 段组成。 esptool.pybase64 编码并以压缩的方式提供 (以 ESP8266 为例,可参考 ESP8266ROM.STUB_CODE,其余芯片参考对应 STUB_CODE 即可)。

ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b"""
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\
5dwuzr3/OyU1/H+n+L8kk1ilxnz4f3giyVw=\
""")))

ESP8266ROM.STUB_CODE 解压缩之后,实际就是一个 python 中的字典,包含了 5 对键值对:

  1. text: text.bin
  2. text_start: 0x4010D000
  3. entry: 0x4010D004
  4. data: data.bin
  5. data_start: 0x3FFFACA8

如果有兴趣想自己查看 ESP8266ROM.STUB_CODE 的内容,可以参考如下 python 代码,该段代码会自动将 text 段和 data 段的内容以二进制的形式分别保存到 text.bin 和 data.bin 中。

if __name__ == '__main__':

    for key in ESP8266ROM_STUB_CODE.keys():
        print(f"{key}:")
        if key == 'text':
            hex_bytes = binascii.hexlify(ESP8266ROM_STUB_CODE[key])
            print(hex_bytes)

            file_write = open('text.bin', 'wb')

            file_write.write(ESP8266ROM_STUB_CODE[key])

            file_write.close()
        elif key == 'data':
            hex_bytes = binascii.hexlify(ESP8266ROM_STUB_CODE[key])
            print(hex_bytes)

            file_write = open('data.bin', 'wb')

            file_write.write(ESP8266ROM_STUB_CODE[key])

            file_write.close()
        else:
            print(hex(ESP8266ROM_STUB_CODE[key]))

    sys.exit(0)

Stub Loader 的源码可以在 EspressifESP-IDF 中找到。
esptool_code

编译 Stub Loader

ESP8266 为例,使用 ESP8266_RTOS_SDK release/v3.4 来编译 Stub Loader。这里要特别说明一下哈,我是使用 ESP-IDF v4.4 中的 esptool_py 组件来编译 Stub Loader。因为我查看 ESP-IDF v4.4 中组件 esptool_py 中的 Makefile 的逻辑,发现是可以同时生成运行在 Espressif 各种 chips 上的不同 Stub Loader 的。

我只关注可以运行在 ESP8266 上的 Stub Loader,所以将 Makefile 上和 ESP8266 无关的代码逻辑全部注释掉。
ESP8266_STUB_LOADER_MAKE
编译完成后,在 build 目录下会自动生成一个 stub_flasher_8266.elf 文件。可以通过以下命令来解析 elf 文件中的段信息。

readelf -l stub_flasher_8266.elf

READELF_SEGMENTS
从输出信息可知,该 elf 文件仅仅包含了 bss 段,data 段和 text 段。bss 段和 data 段处于 Segment 00 中。text 段单独处于 Segment 01 中。Segment 00 的大小为 0x12fa8Segment 01text 段的物理起始地址为 0x4010D0000,大小为 0x20C7

由于 bss 段和 data 段同处于 Segment 00 中,我只对 data 段感兴趣。所以可以通过 readelf 命令继续对 Segment 00 段分析:

readelf -S stub_flasher_8266.elf

READELF_SECTIONS
从输出信息可知,text 段处于 elf 文件中偏移地址为 0x13000 处,对应地址为 0x4010D0000,大小为 0x20C7data 段处于 elf 文件中偏移地址为 0x12CA8,对应地址为 0x3FFFFACA8,大小为 0x300

压缩下载

esptool 默认采用压缩下载方式。采用 Deflate 算法对固件数据进行压缩与解压缩。该算法同时使用 LZ77 算法和哈夫曼编码,是一个无损数据的压缩算法。

Deflate 压缩与解压缩的源代码可以在自由、通用的压缩库 zlib 上找到。C 源代码可参考 miniz。用法很简单,直接参考 miniz 的 example 即可。

压缩的简单示例如下:

    tdefl_compressor deflator;
    mz_uint comp_flags = TDEFL_WRITE_ZLIB_HEADER | MZ_BEST_COMPRESSION;
    tdefl_status status = tdefl_init(&deflator, NULL, NULL, comp_flags);
    if (status != TDEFL_STATUS_OKAY) {
        printf("tdefl_init() failed.\n");
        free(bin_uncompressed);
        free(bin_compressed);
        return ESP_LOADER_ERROR_FAIL;
    }

    size_t uncompressed_in_bytes = load_bin_size;
    size_t compressed_out_bytes = uncompressed_in_bytes;
    status = tdefl_compress(&deflator, bin_uncompressed, &uncompressed_in_bytes, bin_compressed, &compressed_out_bytes, TDEFL_FINISH);
    if (status != TDEFL_STATUS_DONE) {
        printf("tdefl_compress() failed %d.\n", status);
        free(bin_uncompressed);
        free(bin_compressed);
        return ESP_LOADER_ERROR_FAIL;
    }```

解压缩的简单示例如下:

```c
    tinfl_decompressor inflator;
    tinfl_init(&inflator);

    uint8_t *bin_data = bin_compressed;
    uint32_t compressed_size = compressed_out_bytes;
    while(compressed_size > 0) {
        memset(payload_flash, 0x0, sizeof(payload_flash));

        ssize_t load_to_read = READ_BIN_MIN(compressed_size, sizeof(payload_flash));
        memcpy(payload_flash, bin_data, load_to_read);

        size_t in_bytes = load_to_read;
        size_t out_bytes = bin_cal_checksum + sizeof(bin_cal_checksum) - next_out;
        mz_uint uncomp_flags = (compressed_size <= load_to_read ? 0 : TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_PARSE_ZLIB_HEADER;
        status = tinfl_decompress(&inflator, 
                                payload_flash, 
                                &in_bytes, 
                                bin_cal_checksum,
                                next_out,
                                &out_bytes, 
                                uncomp_flags);

#ifdef DEBUG_ENABLE
        printf("compressed in bytes : %lu, uncompressed out bytes : %lu\n", in_bytes, out_bytes);
#endif

        err = esp_loader_flash_compressed_write(fd, bin_data, in_bytes, bin_cal_checksum, out_bytes);
        printf("stub loader err=%d\n",err);
        if (err != ESP_LOADER_SUCCESS) {
            printf("Packet could not be written.\n");
            free(bin_uncompressed);
            free(bin_compressed);
            return err;
        }

        compressed_size -= load_to_read;
        bin_data += load_to_read;
    };

压缩下载需要通过以下四条命令。

  1. FLASH_DEFL_BEGIN
    FLASH_DEFL_BEGIN
  2. FLASH_DEFL_DATA
    FLASH_DEFL_DATA
  3. SPI_FLASH_MD5 (可选)
    SPI_FLASH_MD5
  4. FLASH_DEFL_END
    FLASH_DEFL_END
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式工程狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值