ESP32 存储系统及Flash,进而考虑HTTP升级文件
1、背景
由于做的下载文件速度过慢,而OTA Demo的下载速度很快,应该有必要了解两者的差距。
1、OTA只有一个Get请求,而我的1K接1K的请求,速度就慢了
2、两片Flash的问题。
1.1 资料
ESP32技术参考手册
ESP32系列芯片技术规格书
2、ESP32 存储系统
2.1 存储系统简介
地址0x4000_0000以下的部分属于数据总线的地址范围;
地址0x4000_0000~0x4FFF_FFFF部分属于指令总线的地址范围;
地址0x5000_0000及以上的部分是数据总线和指令总线共用的地址范围。
存储系统分为片上存储和片外存储。
其中片上存储包括:
• 448 KB 的 ROM,用于程序启动和内核功能调用
• 520 KB 片上 SRAM,用于数据和指令存储
• RTC 快速存储器,为 8 KB 的 SRAM,可以在 Deep-sleep 模式下 RTC 启动时用于数据存储以及被主 CPU
访问
• RTC 慢速存储器,为 8 KB 的 SRAM,可以在 Deep-sleep 模式下被协处理器访问
• 1 Kbit 的 eFuse,其中 256 bit 为系统专用(MAC 地址和芯片设置) ; 其余 768 bit 保留给用户程序, 这些程
序包括 flash 加密和芯片 ID
• 嵌入式 flash ESP32-D2WD带有16Mbit,40MHz的嵌入式flash,与GPIO16,GPIO17,SD_CMD,SD_CLK,SD_DATA_0和SD_DATA_1连接。
ESP32支持多个外部QSPI flash和静态随机存储器SRAM。
外部 flash 可以同时映射到 CPU 指令和只读数据空间。外部 flash 最大可支持 16 MB。
外部 SRAM 可映射到 CPU 数据空间。外部 SRAM 最大可支持 8 MB。一次最多可映射 4 MB。虽然ESP32能够支持多种类型的RAM芯片,但ESP32_SDK目前支持ESP_PSRAM32、ESP_PSRAM64 这是1.8V器件。
在芯片启动后,用户程序可以MAP外部SRAM或flash到CPU地址空间。
下面QSPI接口下的对Flash和SRAM的并行支持。
外部SRAM包含在存储器映射中,并且在某些限定内,可以用与内部数据RAM相同的方式来使用。乐鑫的WROVER系列模组就包括了ESP 1.8v Flash和集成在模块内的ESP-PSRAM。
2.2 软件支持的外部RAM(External RAM)
ESP-IDF完全支持在应用程序中使用外部RAM,在启动时初始化外部RAM,提供了多种方式来配置处理外部RAM。
Initialize SPI RAM when booting the ESP32,即在ESP32boot时初始化SPI RAM。
方式1、整合RAM到ESP32内存映射。这是一个外部RAM的基本选项。外部RAM指向地址空间0x3F800000(字节访问)。外部RAM的区域大小是SPI RAM大小(最大4MB)。通过指针指向外部RAM来放置数据。
方式2、初始化RAM并将其添加到功能分配器。这就允许程序使用heap_caps_malloc(size,MALLOC_CAP_SPIRAM)专门分配一块外部RAM。可以使用该内存,然后使用正常的free()来释放。映射到0x3F800000.
方式3、初始化 RAM,将其添加到功能分配器,并将内存添加到可由 malloc()
返回的 RAM 池中。 这允许任何应用程序使用外部 RAM 而无需重写代码以使用 heap_caps_malloc
。这是默认。
方式4、允许在外部RAM放置BSS段,这段地址空间起始于0x3F800000,用于lwip、net80211、libpp和bluedroid ESP-IDF库存储初始化为零的数据(bss段)。通过在静态声明中应用EXT_RAM_ATTR 宏(未初始化为0值)从内部BSS段移到外部RAM。这有效减少BSS段使用的内部静态内存。
2.3 外部Flash
与SPI flash配合使用的底层ROM功能没有与附加到SPI外围设备(SPI0除外)的Flash芯片一起工作的规定。
ESP-IDF提供了使用Flash的功能函数,但是一般来说,尽量避免使用原始的SPI flash功能,推荐使用特定分区的功能。因此需要配置分区表。
2.3.1 分区表
2.3.1.1、概念和组成
ESP32的flash可以包含多个应用程序以及多种不同类型的数据(例如校正数据、文件系统数据、参数存储器数据),因此,需要引入分区表的概念。
分区表帮助用户在实际产品开发过程中对Flash分区定制才能更好地满足产品需求。
分区表一般在flash中的默认偏移地址为0x8000处烧写,大小为C00(最多可以保存95条分区表条目)。分区表数据后还保存着该表的MD5校验和,校验分区表的完整性。使能安全启动,还存有签名信息。
分区表组成
Name标签 | 不超过16个字符 | |
Type类型 | 类型有两种app(0)或者data(1),也可以使用其他数据(0x40~0xFE)作为自定义分区类型 | 1 |
SubType子类型 | 8bit,具体和Type有关;Type==app时,SubType可以指定为factory(0)、ota_0(0x10)....ota_15(0x1F)或者test(0x20)--预留app子类型,用于工厂测试过程 ESP-IDF目前不支持; Type==data,SubType字段可以指定为0ta(0)-OTA数据分区、phy(1)—PHY初始化信息、nvs(2)-非易失性存储或nvs_keys(4)-NVS秘钥分区,至少4096字节。 |
|
Offset偏移量 | 指定偏移地址,一般紧跟前一个分区开始,app分区必须0x10000(64K)对齐,用","时gen_esp32part.py工具会自动计算一个满足对齐要求的偏移地址,没有对齐,工具会报错 | |
Size | 支持K和M的倍数单位,默认字节 | |
flag | 应支持encrypted标志,在启动Flash加密后,该分区会被加密。app分区始终被加密。 |
分区表例子
NVS和PHY分区是必不可少的。
2.3.1.2、分区表类型----使用make partition_table命令来打印分区表摘要
内置分区表:
Single factory app,no OTA
# Espressif ESP32 Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,2M,
Factory app, two OTA definitions
比自定义分区表CSV图的结构多factory分区。factory分区可有也可无。
自定义分区表
就该CSV文件可以根据需求,描述任意数量的分区信息。分区表的offset可以为空,gen_esp32part.py工具会从分区表位置的后面自动计算并填充该分区的偏移地址,并确保每个分区的偏移地址正确对齐。
2.3.1.3、生成二进制分区表和MD5校验和
CSV->bin工具: partition_table/gen_esp32part.py工具
方法:在make menuconfig指定分区表CSV文件的名称,执行make partition_table.
3、思考HTTP流下载文件
当应用程序需要打开连接和以active方式控制数据的读取。用了数据流的API,就不能使用esp_http_client_perform接口,但在使用之前先用esp_http_client_init()接口来获得句柄。
esp_http_client_init()-----创建和生成句柄;
esp_http_client_open()-------用write_len参数打开http连接,需要读时write_len=0;写头字符串;
esp_http_client_fetch_headers()-----读HTTP Server 应答头,返回服务器的content-length和调用esp_http_client_get_status_code() 获得连接HTTP状态
esp_http_client_read()------从HTTP数据流中读取数据
esp_http_client_close-------关闭连接
esp_http_client_cleanup()-----释放资源
例程:protocols/esp_http_client
static void http_perform_as_stream_reader()
{
char *buffer = malloc(MAX_HTTP_RECV_BUFFER + 1);
if (buffer == NULL) {
ESP_LOGE(TAG, "Cannot malloc http receive buffer");
return;
}
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
.event_handler = _http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err;
if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
free(buffer);
return;
}
int content_length = esp_http_client_fetch_headers(client);
int total_read_len = 0, read_len;
if (total_read_len < content_length && content_length <= MAX_HTTP_RECV_BUFFER) {
read_len = esp_http_client_read(client, buffer, content_length);
if (read_len <= 0) {
ESP_LOGE(TAG, "Error read data");
}
buffer[read_len] = 0;
ESP_LOGD(TAG, "read_len = %d", read_len);
}
ESP_LOGI(TAG, "HTTP Stream reader Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
esp_http_client_close(client);
esp_http_client_cleanup(client);
free(buffer);
}