今天来撸一下 Espressif 的 Serial 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
- 压缩下载
预备理论
- Loader
- SLIP
- Request Packets & Response Packets
Loader
Loader 可以被理解为一段代码,负责接收 UART 的数据(这些数据其实就是 Request Packets),执行特定的动作并返回动作的执行结果(这些结果其实就是 Response Packets)。
大家有没有理解上面这句话呢?其实说白了就是一句话的事,我们假设所有的 UART 数据均为 PC 发送,那么 PC 和芯片之间就是一问一答
的机制。PC 叫芯片做什么,芯片回答"完成"或者“没完成”就完事。
目前 Espressif 芯片支持两种 Loader:
- ROM Loader: 固定在 ROM 中,功能有限。
- 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 协议规定处理方式如下:
- 每个 SLIP 包以 0xC0 (END) 开头,以 0xC0 (END) 结尾
- 除 SLIP 包头包尾外,payload 中遇到 0xC0 (END),转换成 0xDB (ESC), 0xDC (ESC_END)
- payload 中遇到 0xDB (ESC),转换成 0xDB (ESC), 0xDD (ESC_ESC)
SLIP 与 Serial Protocol 的关系?
SLIP 是 Serial Protocol 在 UART 层面的数据组织方式。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:
- ESP8266 ROM Loader
- ESP8266 Stub Loader
- 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:
- 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 之前,需要满足以下三条规则:
- SLIP 协议对特殊字节的处理方式
- Data 域格式
- Request Packets 格式
Request Packet
Response Packet
固件下载
整个下载过程可分为 6 个步骤:
- 同步
- 获取芯片信息(可选)
- 下载 text.bin
- 下载 data.bin
- 修改波特率(可选)
- 下载固件
同步
当芯片处于 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 命令来读取芯片内部的寄存器来获取芯片信息。
对于 ESP8266 和 ESP8285,可以读取地址为 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, 0x3FF00054 和 0x3FF0005C 的寄存器来确定 MAC 地址。
- ESP32 可以分别读取地址为 0x3FF5A004, 和 0x3FF5A008 的寄存器来确定 MAC 地址。
- ESP32C3 可以分别读取地址为 0x60008844, 和 0x60008848 的寄存器来确定 MAC 地址。
具体的实现方式可以参考 ESP-IDF 中的 esp_efuse_mac_get_default 接口或参考 esptool.py 文件。
下载 text.bin
对于一段可以运行的程序来说,最基本应包含三个段:
- text 段:用来存放代码
- data 段:用来存放初始化过的全局变量和静态变量
- bss 段:用来存放未初始化的全局变量和静态变量
所以,如果想要让 Stub Loader 跑起来的话,最起码要将 text.bin 和 data.bin 通过 ROM Loader 加载到 RAM 中。text.bin 会被 ROM Loader 加载到 IRAM 中,data.bin 会被 ROM Loader 加载到 DRAM 中。对于如何获取 text.bin 和 data.bin ,可参考 Stub Loader。
下载 text.bin 需要通过以下两条命令:
-
MEM_BEGIN
该命令在 Request_Packets 中的 Data 域中需要指定 4 个参数:- total size
- number of data packets
- data size in one packet
- memory offset
假设 text.bin 的大小为 3624 字节,设置的 data size in one packet 为 1024 字节,那么 number of data packets 则为 3624 / 1024 = 4。data 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"
-
MEM_DATA
该命令在 Request_Packets 中的 Data 域中需要指定 4 个参数:- data size
- sequencu number
- 0
- 0
这里要思考一个问题:
text.bin 的大小为 3624 字节,设置的 data size in one packet
为 1024 字节,那么 number of data packets
则为 3624 / 1024 = 4,那么 text.bin 的实际大小即位 4 * 1024 = 4096 字节,远远大于 3624 字节的实际大小。使用 MEM_DATA 发送最后一包数据时 data size
应该设置为 1024 还是 3624 - 1024 * 3 = 552 字节呢?
自己在实验过程中测试了 1024 和 552 这两种方案,发现其实是都可以的。
- 设置为 1024 时,多出的数据均要以 0xFF 作为填充 (文档中推荐此方式)
- 设置为 552 也即实际的剩余长度 (esptool 采用此种方式)
下载 data.bin
下载 data.bin 需要通过以下三条命令:
-
MEM_BEGIN
-
MEM_DATA
-
MEM_END
该命令在 Request_Packets 中的 Data 域中需要指定 2 个参数:- execute flag, 一般为 0 即可
- 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 中。
- FLASH_BEGIN
- FLASH_DATA
- SPI_FLASH_MD5 (可选)
- FLASH_END
这里以 hello_world demo 产生的固件为例,介绍下怎样使用上述四条命令。
- bootloader.bin (起始地址为 0x00000000,大小为 9984)
- partition-table.bin (起始地址为 0x00008000,大小为 3072)
- hello_world.bin (起始地址为 0x00010000,大小为 168720)
上述命令在使用时需要注意以下几点:
-
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;
-
FLASH_BEGIN 命令不需要考虑擦除 FLASH 的问题,擦除工作在 Stub Loader 接收到 FLASH_BEGIN 命令后会根据
size to erase
参数自动进行 4 KB 对齐,然后擦除这一部分空间。 -
FLASH_END 命令可指定
run to user code
来运行下载的固件。
协议进阶
该部分是对 Serial Protocol 自己所作的扩展。如果只想了解固件是如何下载的,这部分略过即可。
该部分会从以下两部分展开进行讲述:
- Stub Loader
- 压缩下载
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 对键值对:
- text: text.bin
- text_start: 0x4010D000
- entry: 0x4010D004
- data: data.bin
- 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
的大小为 0x12fa8
。 Segment 01
即 text 段的物理起始地址为 0x4010D0000
,大小为 0x20C7
。
由于 bss 段和 data 段同处于 Segment 00 中,我只对 data 段感兴趣。所以可以通过 readelf 命令继续对 Segment 00
段分析:
readelf -S stub_flasher_8266.elf
从输出信息可知,text 段处于 elf 文件中偏移地址为 0x13000
处,对应地址为 0x4010D0000
,大小为 0x20C7
。data 段处于 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;};
压缩下载需要通过以下四条命令。
- FLASH_DEFL_BEGIN
- FLASH_DEFL_DATA
- SPI_FLASH_MD5 (可选)
- FLASH_DEFL_END