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

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

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

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

文章目录

  • 预备理论
    • Loader
    • SLIP
    • Request Packets & Response Packets
      • Request Packets
        • Command
        • Data
        • Checksum
      • Response Packets
        • Status Bytes
          • Error
    • Data Frame
  • 固件下载
    • 同步
    • 获取芯片信息(可选)
      • 确定芯片类型
      • 读取 MAC 地址
    • 下载 text.bin
    • 下载 data.bin
    • 修改波特率(可选)
    • 下载固件
  • 协议进阶
    • Stub Loader
      • 编译 Stub Loader
    • 压缩下载

预备理论

  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 中,功能有限。
  2. Stub Loader: 由 ROM Loader 加载到 RAM 中,功能可扩展(Espressif 推荐此方式)。

SLIP

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

Hex Value Abbreviation Description
0xC0 END Frame End
0xDB ESC Frame Escape
0xDC ESC_END Transposed Frame End
0xDD ESC_ESC Transposed 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 与 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

Byte Name Comment
0 Direction Always 0x00 for requests
1 Command Command identifier
2-3 Size Length of Data field, in bytes
4-7 Checksum Simple checksum of part of the Data field (only used for some commands)
8-n Data Variable length data payload (0-65535 bytes, as indicated by Size field)

Command

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

Data

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

Byte Name Comment
0-3 Data to write length Little endian 32-bit word
4-7 Sequence number Little endian 32-bit word. The sequence number is 0 based
8-15 0 Two words of all zero, unused
16- Data to write Length 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 ofdata 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

Byte Name Comment
0 Direction Always 0x01 for responses
1 Command Same value as Command identifier in the request packet that trigged the response
2-3 Size Size of Data field. At least the length of the Status Bytes (2 or 4 bytes)
4-7 Value Response value used by READ_REG command. Zero otherwise
8-n Data Variable length data payload. Length indicated by Size field

Status Bytes

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

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

  1. ESP8266 ROM Loader
  2. ESP8266 Stub Loader
  3. ESP32 Stub Loader
Byte Name Comment
Size-2 Status Status flag. success (0) or failure(1)
Size-1 Error if Status is 1, this indicates the type of error

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

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

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

ROM Loader:

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 域的格式:

假设现在 PC 下只想发送一包数据给 Stub Loader,原始数据为 0x01-0xC0-0x02-0xDB-0xC0-0x03-0x04-0x05,那么原始数据在发送给 Stub Loader 之前,需要满足以下三条规则:

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

Request Packet

Response Packet

固件下载

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

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

同步

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

典型的 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 命令来读取芯片内部的寄存器来获取芯片信息。

对于 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

    该命令在 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 = 0x1000dram : 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

    该命令在 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

  2. MEM_DATA

  3. 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 命令用来修改波特率。


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

下载固件

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

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

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

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

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


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

  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.py 以 base64 编码并以压缩的方式提供 (以 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 的源码可以在 Espressif 的 ESP-IDF 中找到。

编译 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 无关的代码逻辑全部注释掉。

编译完成后,在 build 目录下会自动生成一个 stub_flasher_8266.elf 文件。可以通过以下命令来解析 elf 文件中的段信息。

readelf -l stub_flasher_8266.elf


从输出信息可知,该 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


从输出信息可知,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;}```解压缩的简单示例如下:```ctinfl_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_ENABLEprintf("compressed in bytes : %lu, uncompressed out bytes : %lu\n", in_bytes, out_bytes);
#endiferr = 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
  2. FLASH_DEFL_DATA
  3. SPI_FLASH_MD5 (可选)
  4. FLASH_DEFL_END

Espressif 玩转 固件下载相关推荐

  1. YDOOK:ESP8266: 乐鑫官方AT固件下载

    YDOOK:ESP8266: 乐鑫官方 AT固件下载 © YDOOK JY Lin 文章目录 YDOOK:ESP8266: 乐鑫官方 AT固件下载 © YDOOK JY Lin 1. URL: htt ...

  2. ESP32模组AT固件下载与通讯测试

    准备: 硬件:ESP-WROOM-32.USB下载线.USB转TTL 软件: 1)Flash下载工具 下载地址:工具 | 乐鑫科技 (espressif.com) 2)下载固件包,我选择的最新版本V2 ...

  3. YDOOK:ESP8266: 官方AT固件下载 WiFi 开发固件下载

    YDOOK:ESP8266: 官方AT固件下载 WiFi 开发固件下载 © YDOOK JY Lin 文章目录 YDOOK:ESP8266: 官方AT固件下载 WiFi 开发固件下载 © YDOOK ...

  4. OpenMV(二)--IDE安装与固件下载

    IDE安装与固件下载 1. IDE安装 2. 固件下载 2.1 DFuSe安装 2.2 固件下载 1. IDE安装 OpenMV具有独有的IDE,而且可以跨平台使用,支持Win, Mac OS, Li ...

  5. EC600 QuecPython开发环境搭建、固件下载,最方便的OpenCPU物联网4G通信解决方案

    EC600 QuecPython 官方资源汇总 开发环境搭建 1.安装windows驱动 2.验证模组的固件版本 3.烧录QuecPython固件 小试QuecPython 1.查看系统信息 2.点亮 ...

  6. 2021最新外卖霸王餐小程序、H5、微信公众号版外系统源码|霸王餐美团/饿了么系统 粉丝裂变玩源码下载

    2021年了,你还在用淘宝客吗?赶紧跟上互联网的大势吧,外卖cps就是cps人群趋势! 个人.个体.企业均可使用 外卖霸王餐小程序.H5.微信公众号版外系统源码|霸王餐美团/饿了么系统 粉丝裂变玩 2 ...

  7. java固件包_iOS13 各版本固件下载地址以及更新方法

    2019-06-04 7907 iOS13本次更新: iOS13 developer beta①Face id 解锁速度增加30%②App打开速度快三倍③黑暗模式(dark mode)④Applemu ...

  8. 涂鸦三明治开发套件开箱及固件下载

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 人工智能编程入门博客 开箱 涂鸦三明治开发套件包括:涂鸦三明治红外遥控功能板.涂鸦三明治 Wi-Fi ...

  9. miui android 7.1,小米MIUI7.1稳定版固件下载 MIUI7.1稳定版完整刷机包下载

    MIUI7.1稳定版终于来了!1月5日,在今天锤子科技正式宣布"不给力"之后,小米MIUI7.1稳定版正式推送.下面小编给大家带来小米miui7.1稳定版刷机包下载地址,感兴趣的用 ...

最新文章

  1. Linux磁盘管理(实验)
  2. 2018年4月份,阿里最新的java程序员面试题目
  3. 问答平台元老Yahoo Answers宣布将永久关闭,网友:爷青结
  4. CentOS 初体验十八:grep命令使用
  5. 维护网络安全要攻防兼备
  6. 重写Oracle的wm_concat函数,自定义分隔符、排序
  7. 【JQuery】JQuery学习笔记
  8. 三级菜单页面布局_三级菜单的最快导航布局
  9. grpc 流式传输_编写下载服务器。 第一部分:始终流式传输,永远不要完全保留在内存中...
  10. 作业四 | 个人项目-小学四则运算 “软件”之初版
  11. HTML修改价格文字,HTML打折计算价格实现原理与脚本代码
  12. 点击button标签会导致提交form
  13. python 读取excel失败 可以转换成csv文件
  14. 【优化求解】基于matlab GUI模拟退火算法求解全局最大值最小值问题【含Matlab源码 1242期】
  15. 使用WinRAR 进行解压war文件。
  16. UML建模工具最近更新(-2022年4月)共12款:Papyrus、StarUML、Software Ideas Modeler
  17. linux nas共享存储6,NAS(网络附属存储)技术
  18. vb可以开发用c语言,c语言和vb语言的区别是什么?_后端开发
  19. ubuntu 18.04 pycharm生成快捷方式 ,亲测有效!!
  20. Java | Java模拟实现扑克牌洗牌、发牌过程

热门文章

  1. 鸿蒙麒麟食华为的什么系统,你好,鸿蒙!华为自研系统真的来了,还有麒麟、朱雀.........
  2. 倍福PLC和C#通过ADS通信传输int类型变量
  3. 如何查找下载外文文献,超强外文文献检索网站排名
  4. 编译原理知识点总结——从NFA到DFA的转化
  5. 用户行为分析的基本概览和常用名词解释
  6. 【Linux | 系统编程】Linux系统编程(文件、进程线程、进程间通信)
  7. windows文本转语音调用
  8. ct扫描方式有哪些_医学影像技术(医学高级):CT必看题库知识点(考试必看) - 考试题库...
  9. shp2sdo的下载及使用说明
  10. 华为Atlas200DK的环境部署与运行demo(人脸识别)