ESP8266架构探索运行的起始

3次阅读

共计 2793 个字符,预计需要花费 7 分钟才能阅读完成。

都 2020 年了,想必大家对 ESP8266 都很熟了,没用过至少也听说过。所以抛开丰富有趣的各种应用,下面来说一说很少人讨论的 ESP8266 的架构。

架构

ESP8266 集成了增强版的超低功耗 Tensilica’s Xtensa L106 32-bit 内核处理 器。为什么说增强版呢,因为 Tensilica 处理器的特点,就是工程师可以对内核 IP 做定制化,乐鑫购买 IP 后,继续进行优化,形成了增强版。
该处理器使用RISC 指令集,CPU 时钟速度最⾼可达 160MHz,芯⽚内置了存储控制器,包含 ROM 和 SRAM。但这个 ROM 呢,固化一些底层代码,用户是修改不了的,用户程序必须由外部 Flash 存储,理论上最大可支持 16MB 的存储。所以这就是为什么买 ESP8266 模块时,都要看用的多大的 Flash,使用 SDK 开发方式的话,从编译到下载都要选择正确的 Flash。

代码和内存的存储分配

熟悉计算机原理的都知道,计算机的本质就是计算,通过执行不同的 指令 ,操作不同的 数据 来得到不同的数据结果。指令在代码中可以认为是不同的语句,数据就是不同的变量。在单片机中,一般指令存在 ROM 中,数据变量存在 RAM 中。有这两个概念后,再看看 ESP8266 这款芯片。
ESP8266 片上有固化的 ROM,需要用户外置 Flash 来存储程序。在物理上有 64KB 的 iRAM,96KB 的 dRAM。
其中:

iRAM:instruction RAM,用来存放指令,位于 0x40100000 开始的 64KB 空间。
dRAM:data RAM,用来存放数据,位于 0x3FFE8000 开始的空间。

所以 ESP8266 一共有这么几个存储的地方:片上 ROM、片上 iRAM、片上 dRAM,片外 Flash。

存储区 作用
片上 ROM 固化芯片底层代码,用户无法改动
片上 iRAM 存放指令,用于执行程序
片上 dRAM 存放数据
片外 Flash 存放用户代码、用户数据等

知道 iRAM 后,就知道我们 Flash 的地位并不怎样。代码并不是直接在 Flash 上取指令运行的,而是加载到 iRAM 中来运行的。

代码开始的地方

ESP8266 中所有代码都是加载到 iRAM 后运行的,只是有的提前加载(IRAM 段),有的用时加载(IROM 段)。IROM 段使用部分称为 Cache,IRAM 段为真 iRAM。
ESP8266 有个特殊的 Cache 概念。在计算机系统中,Cache 是为了解决 CPU 读内存速度过慢而设置的。Cache 比内存的读取速度更快,但比寄存器读取速度慢。通常 Cache 只是小空间存储,用来缓存 CPU 读取过的内存数据。ESP8266 中的 Cache 使用了 iRAM,iRAM 共有 64k,分了一半(32k)给 Cache。ESP8266 的底层启动后,会将对应 Flash 的空间映射到 Cache 空间。
用户代码是存在外置 Flash 的,那么芯片启动后,IRAM 段加载到 iRAM 中,底层代码(前面说的固化 ROM 内)也会将 Flash 空间映射到 Cache 中。因为 Cache 的空间远小于 Flash,所以使用了特殊的映射技术可以将最大 1M+1M 的 Flash 映射到 32K 的 cache 中。而哪些代码需要加载到 iRAM,哪些还是放在 Cache 中运行,则是在链接时,通过指定不同的段来区分。
写过代码的朋友就知道了,在 NONOS SDK 中,需要 ICACHE_FLASH_ATTR 来定义函数,以使代码不占用 iRAM 空间。在 RTOS SDK 中,使用 IRAM_ATTR 定义函数,将需要频繁运行,讲究效率的函数放到 iRAM 中。这个便是 iRAM 和 iROM 的区别引起的。
好了,到了这里,我们就知道代码编译生成二进制文件,烧录到 Flash 后,是怎么由 ESP8266 执行起来的了。别走开,下面有彩蛋。

ROM 的底层

ROM 的底层是看不见的,但是既然我们能用到,那就有它的痕迹
https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld

PROVIDE (ets\_memcmp = 0x400018d4);
PROVIDE (ets\_memcpy = 0x400018b4);
PROVIDE (ets\_memmove = 0x400018c4);
PROVIDE (ets\_memset = 0x400018a4);
PROVIDE (ets\_post = 0x40000e24);
PROVIDE (ets\_printf = 0x400024cc);
PROVIDE (ets\_putc = 0x40002be8);
PROVIDE (ets\_rtc\_int\_register = 0x40002a40);

这份 ld 文件里,就描述了底层函数在 ROM 中的地址,也告诉我们,到底提供了多少功能。
但 NONOS SDK 官方也已经停止更新了,在 RTOS SDK 中,底层函数作用也逐渐在淡化,像操作外设的都改成了在用户代码里编写的形式。

NONOS SDK 默认链接到 iROM 中

不知道现在还有多少人在使用 NONOS SDK 做开发,既然这篇文章说到了代码存放,那就说一下吧。
在 NONOS SDK 中,用户代码编译链接后,会默认放到 iRAM 中,所以需要用户手动增加 ICACHE_FLASH_ATTR 来修饰。但问题是 iRAM 空间只有 32k,是非常有限的。
如果都是自己从 0 开始写的代码,那也没什么。但是如果是要引用别人的库,或者是自己写的代码要封装成库给其他地方使用,这时就很麻烦了,因为其他平台都不用这样定义。那么有没有解决办法呢,有。
在 SDK 中找到./ld/eagle.app.v6.ld 文件。
.irom0.text : ALIGN(4) 段下做如下修改即可。

  .irom0.text : ALIGN(4)
  {_irom0_text_start = ABSOLUTE(.);

    *libuser.a:(.literal.* .text.*)   /* 增加此行 */
    *libat.a:(.literal.* .text.*)
    *libcrypto.a:(.literal.* .text.*)
    
    ... ...
    
    _irom0_text_end = ABSOLUTE(.);
  } >irom0_0_seg :irom0_0_phdr

用户代码编译后,会生成 libuser.a 文件,所以这个文件里都是用户代码。新增语句就是将用户代码中类常量和代码指令都放到 iROM 中。

End

关于 ESP8266 架构和代码如何开始运行的东西就到这里了。彩蛋之后,当然是下集预告啦。下次可能会根据开源的 Bootloader 代码,来跟着解析一遍哦。
阵容如下,有没有点期待呢。

rboot:https://github.com/raburton/rboot
zboot:https://github.com/zorxx/zboot
esp-bootloader:https://github.com/tuanpmt/esp-bootloader

正文完
 0