[esp8266]RAM不足问题,导致重启

RAM不足

问题描述

在esp8266编程过程中,它拥有DRAM80KB,但是使用时,想要申请一块固定的全局变量BUffer时,只能使用到8K
再往上就会照成重启问题,明明只已经使用了50%。

问题分析

  1. 实际上RAM只能使用50%
  2. String类属于不定长,在函数中使用String,当全局已经使用90%情况下,无法为String申请更多空间。

解决过程

flash与RAM

​ 实际上参与工作的硬件有w25q32和esp8266内部的ram区域。而flash其中一部分作为ROM,也就是存放代码。ram又被分为两部分:IRAM与DRAM。

​ 那么也就是说代码应该被烧录到flash中。而代码被分为许多个section,常见的如:

  • .bss
  • .text
  • .rodata
  • .data
  • .irom0.text

​ 那么esp8266究竟的falsh与ram在代码运行究竟时怎样工作的?

探究工作过程

​ 我这里使用的是一个esp8266 arduino库,开发工具为vscode+platformio来开发。芯片为esp-12s。

  • falsh大小:4M.

  • RAM:96K

​ 我记得在操作系统的段页系统一节中,有cache一概念,它是介于cpu与RAM之间的,在指令流水出现结构冒险 时,通常解决办法是使用两个cache:icache与dcache。意思是指令cahce与数据cache。这里的IRAM与DRAM应该也是为了防止结构冒险吧。那么可以得出,IRAM是用来存放指令的,而DRAM用来存放数据的。

​ 问题又来了IRAM存放怎么样的指令?在一篇文章ESP32 程序的内存模型可以得到:

IRAM实际上是执行代码,即.text段

​ 从上面大致可以推出IRAM会将flash(w25q32)代码载入,并且执行。而IRAM又分为iram与icache,iram是真正的载入.text段的区域。那么icache的作用是?

​ 官方给出的解释是:

后 32 KB 被映射作为 iCache,放在 SPI Flash 中的,加了ICACHE_FLASH_ATTR的代码会被从 SPI Flash 自动动态加载到 iCache。

​ 这一段并没有讲明白映射大小,映射的位置,什么时候会自动加载到cache中。且代码相对较`大,不可能完全载入到32K iram中。

	推测是32K iram会载入一部分指令给cpu使用,而icache则不断的预测代码位置,提前调入代码,就像虚拟内存的交换一样,在iram中未找到的指令,会去icache中寻找,当icahe也没有,则是产生缺失中断,将falsh中的代码调入icache,这部分是自动完成的。

该网页内容证实推断大致正确:https://blog.csdn.net/weixin_30657541/article/details/102172971

​ 在前面一章flash的分布已经讲过icache映射在falsh的位置。这里再回忆一遍(文件eagle.flash.4m3m.ld):

/* Flash Split for 4M chips */
/* sketch @0x40200000 (~1019KB) (1044464B) */
/* empty  @0x402FEFF0 (~4KB) (4112B) */
/* spiffs @0x40300000 (~3048KB) (3121152B) */
/* eeprom @0x405FB000 (4KB) */
/* rfcal  @0x405FC000 (4KB) */
/* wifi   @0x405FD000 (12KB) */

MEMORY
{
  dport0_0_seg :                        org = 0x3FF00000, len = 0x10
  dram0_0_seg :                         org = 0x3FFE8000, len = 0x14000
  iram1_0_seg :                         org = 0x40100000, len = 0x8000
  irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
}

PROVIDE ( _FS_start = 0x40300000 );
PROVIDE ( _FS_end = 0x405FA000 );
PROVIDE ( _FS_page = 0x100 );
PROVIDE ( _FS_block = 0x2000 );
PROVIDE ( _EEPROM_start = 0x405fb000 );
/* The following symbols are DEPRECATED and will be REMOVED in a future release */
PROVIDE ( _SPIFFS_start = 0x40300000 );
PROVIDE ( _SPIFFS_end = 0x405FA000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );

INCLUDE "local.eagle.app.v6.common.ld"

这里的0x402xxxxx是esp8266定义的falsh map地址,而非硬件地址。
真实硬件地址应该是irom0_0_seg-0x40200000,这个是在flash(w25q32)的地址。

​ irom0_0_seg自然是rom的代码位置,它在esp8266 arduino flash分布图中应该是:

|--------------|-------|---------------|--|--|--|--|--|
^              ^       ^               ^     ^
Sketch    OTA update   File system   EEPROM  WiFi config (SDK)

​ 在sketch位置。在arduino上sketch实际上是本意就是代码的意思。

​ 现在有这样一个问题:

	arduino中定义sketch是从0x40200000-0x402FEFEF,而irom0_0_seg是从0x40201010-0x40300000。明明sketch是代码的意思为什么与irom0_0_seg会有偏差(0x1010B)?

IRAM原理结论(大写IRAM与iram区别在于:IRAM包含iram)

​ 实际上flash中的0x1010-0x100000位置是irom0位置,ubuntu下使用readelf -S ./test/firmware.elf(firmware.elf是编译后的烧录文件)查看确实irom0是在40201010也没找到小于该地址的数据。

​ 接着我想,那么从0x40200000-0x40201010或者flash物理地址0-0x1010究竟有什么,使用16进制查看器,发现确实在firmware.bin 0-0x1010存在数据。那么该部分数据是什么?

​ 想要去使用objdump,发现无法识别,那么为什么*.bin文件无法识别,我向看看elf是如何转换成bin文件的,在Vscode编译,发现无法看到编译命令,从网上了解到objcopy可以将代码转换为bin。尝试arm-none-eabi-objcopy.exe -O binary .pio\build\esp12e\firmware.elf ./boot.bin还是无法识别文件。那找找该工程的编译器,在.vscode/launch.json中发现:

"toolchainBinDir": "C:/Users/PX_Lenovo/.platformio/packages/toolchain-xtensa/bin"

​ 好家伙,编译器不是gcc,看了下xtensa这个是esp系列处理器的型号,那这个就简单了,将上面指示的工具链位置加入系统路径,重启vscode,在vs(实际是powershell执行的)的命令行中执行:xtensa-lx106-elf-objcopy.exe -O binary .pio\build\esp12e\firmware.elf ./app.bin。结果发现app.binfirmware.bin不一样。

​ 试过很多办法,都没办法找到任何相关信息,可能其产生是因为编译器自动产生的,作为一个文件的头部或者索引表

​ 现在想要知道该文件怎么过来的,先得知道是怎么样得命令产生了firmware.bin文件,想着加多调试信息,所以在platformio.ini加入:

build_flags = 
	.....
	-DCORE_DEBUG_LEVEL=5

​ 编译一次,发现打印了如下关键信息:

Creating BIN file ".pio\build\esp12e\firmware.bin" using "C:\Users\PX_Lenovo\.platformio\packages\framework-arduinoespressif8266\bootloaders\eboot\eboot.elf" and ".pio\build\esp12e\firmware.elf"

​ 里面指出在firmware.bin=eboot.elf+firmware.elf所产生得。

	irom0在代码中的表现是:irom0_0_seg,.irom0.text。而它所处的falsh位置是0x0001010-0x300000这一段位置。在cpu内存映射是0x40201010-0x40300000。32K的iram(iram1_0_seg)会调入部分irom0的代码进入其中,而有32K的icache(irom0_0_seg)作为虚拟内存映射到irom0上,增加读取速度。

DRAM结论

​ 除去.text与.irom0.text就只剩下:.bss,.rodata,data三个字段。根据官方:

	DRAM 空间为 96 KB: 对于 Non-OS_SDK,前 80 KB 用来存放 .data/.bss/.rodata/heap,heap 区的大小取决于 .data/.bss/.rodata 的大小;还有 16 KB 给 ROM code 使用。 对于 RTOS_SDK,96 KB 用来存放 .data/.bss/.rodata/heap,heap 区的大小取决于 .data/.bss/.rodata 的大小。

​ 也就是说内存剩余空间就是heap得了,这里不知道有没有stack。heap是动态大小,这样就会带来一些问题:某些空间比较申请全局变量,那么就是.data字段了或者.bss字段。这个会压缩heap大小,且编译器无法识别如下错误:

	以80K为例,当全局申请了20K得变量,但是某个函数中申请了70K得buffer,这样会使得跑入该函数时导致数据错乱。

缓解RAM不足办法

总之:尽量不要申请大得全局变量。

其他

官方问题合集里面有RAM的结构讲解。

.bss.data.text.段讲解

readelf用法

一些命令

arm-none-eabi-nm -n -t d -S --size-sort .pio\build\esp12e\firmware.elf
arm-none-eabi-objdump.exe -t .pio\build\esp12e\firmware.elf > objdump.txt
arm-none-eabi-objcopy.exe -O binary ./firmware.elf boot.bin
arm-none-eabi-objcopy.exe -O binary .pio\build\esp12e\firmware.elf ./boot.bin

xtensa-lx106-elf-objcopy.exe -O binary .pio\build\esp12e\firmware.elf ./app.bin
xtensa-lx106-elf-objdump.exe -t .pio\build\esp12e\firmware.elf > objdump.txt
xtensa-lx106-elf-objdump.exe -t .pio\build\esp12e\firmware.bin > bin.txt
xtensa-lx106-elf-objdump.exe -h -b binary -m arm boot.bin
xtensa-lx106-elf-readelf.exe -h .pio\build\esp12e\firmware.elf > elfinfo.txt
posted @ 2022-04-06 16:45  邪恶法师  阅读(1858)  评论(0编辑  收藏  举报