numworks移植记录:3.ESP32-S3 硬件资源分析及 NumWorks 原版运行需求

2 阅读6分钟

ESP32-S3 硬件资源分析及 NumWorks 原版运行需求

在开始移植工作之前,我们需要对目标硬件平台(ESP32-S3)和待移植软件(NumWorks Epsilon)的资源需求做一个全面的分析。这就像搬家前先测量新家的空间尺寸,再清点家具的尺寸,确保能装得下、摆得齐。

1. 你的硬件平台:ESP32-S3(带 8MB PSRAM)

你选用的是带 外置 8MB PSRAM 版本的 ESP32-S3 模组(如 ESP32-S3-WROOM-1-N8R8 或类似型号)。这类模组在性能和存储上有以下关键参数:

硬件组件规格参数说明
CPUXtensa® 双核 32 位 LX7 处理器,最高主频 240 MHz性能远超原版 NumWorks 采用的 Cortex-M4/M7(通常主频 100 MHz 左右)
片上 SRAM512 KB内部高速 RAM,CPU 可直接访问,零等待
PSRAM8 MB(通过 SPI 扩展)这是你的核心优势!容量巨大,可存放大型对象或作为内存池
Flash8 MB(或 16 MB)用于存放固件代码、字库、持久化数据
ROM384 KB存放引导程序和部分底层库
RTC SRAM16 KB低功耗模式下保持数据
外设丰富度SPI、I2C、UART、I2S、LCD 接口、Camera 接口、USB OTG 等为驱动屏幕、触摸、音频等提供了充分支持

小结:你手上的 ESP32-S3 是一颗性能强大、内存资源相当充裕的芯片,特别是 8MB PSRAM 的存在,为运行 NumWorks 这样复杂的图形化应用程序提供了充足的空间。

2. NumWorks 原版运行需求分析

NumWorks 原版硬件(N0100/N0110 型号)使用的是 STM32F4/F7 系列芯片,其资源相对“紧张”。了解原版需求,有助于我们评估移植的难度和适配策略。

资源类型原版硬件(N0100/N0110)对应软件需求
RAM仅 256 KB- 存放运行时变量、栈、堆 - Poincaré 数学引擎的内存池(通常几十 KB) - MicroPython 运行时(原始堆约 32 KB ,Omega 分支改为 100 KB ) - 帧缓冲区(可能占几十 KB)
FlashN0100: 1 MB(已接近占满) N0110: >1 MB(外挂 NOR Flash)- 存放固件代码(Epsilon 约 900 KB ) - Omega 分支(带 KhiCAS)约 3-4 MB - 字库、图标等资源文件
CPUCortex-M4/M7 @ ~100 MHz- 处理表达式解析、绘图、MicroPython 解释执行
存储内部 NOR Flash(支持 XIP,~100 MHz 访问)- 代码就地执行(XIP),无需加载到 RAM

原版内存使用特点

  • 内存池(Pool):Poincaré 引擎通常会在 .bss 段中静态分配一个较大的内存池(例如 Poincare::Pool),用于存放表达式树节点。这个池的大小是编译时固定的,在 N0110 上可能是几十到一百多 KB。
  • 栈(Stack):任务栈通常不大(几 KB 到十几 KB)。
  • 堆(Heap):用于动态分配(如 MicroPython 对象),原版因 RAM 限制,堆非常小。

3. 资源匹配度评估

资源项ESP32-S3(你的硬件)NumWorks 需求评估结论
CPU 性能240 MHz 双核~100 MHz 单核远超需求,甚至可以跑更高帧率或开启更多调试
SRAM512 KB256 KB(总 RAM)足够,但需注意布局:内部 SRAM 可用于栈、关键变量、中断处理
PSRAM8 MB无(原版无外部 RAM)极其充裕!可以把 Poincaré 内存池、MicroPython 堆、甚至帧缓冲区都放进去
Flash8 MB 起1~4 MB充裕,可轻松容纳 Epsilon 甚至 Omega 固件
外设驱动ESP-IDF 提供 LCD/Camera/Touch 等驱动需适配 Ion HAL有基础,需自己实现硬件抽象层

关键结论:你的硬件资源 全面超越 原版需求,不存在“跑不起来”的硬伤。最大的挑战在于如何合理分配这些资源,特别是利用好 PSRAM 这个大容量但访问速度稍慢的“仓库”。

4. 移植中需要特别关注的资源问题

虽然资源充裕,但有几个与原版设计紧密相关的点需要特别注意:

4.1 内存池(Poincare::Pool)的放置

原版代码中,Poincaré 引擎通常会在链接脚本中分配一个静态内存池,类似:

ld

_poincare_pool_start = .;
KEEP(*(.bss.$poincare_pool))
_poincare_pool_end = .;

在 ESP32-S3 上,你有两个选择:

  • 放在 SRAM 中:访问速度快,但会占用宝贵的 512 KB SRAM(可能够用,因为原版池子不大)。
  • 放在 PSRAM 中:容量巨大,但访问速度稍慢(通常在 40-80 MHz 且有时序开销)。由于 Poincaré 引擎需要频繁创建、销毁表达式节点,放在 PSRAM 可能会略微降低计算速度,但通常可接受。

建议:将池子放在 PSRAM 中,把 SRAM 留给栈、中断向量表、以及性能关键的变量。这需要在链接脚本中调整段位置,将 .bss.$poincare_pool 指向 PSRAM 区域。

4.2 MicroPython 堆

原版 MicroPython 堆很小,但你可以充分利用 PSRAM,将堆扩展到 几百 KB 甚至几 MB,让 Python 代码可以处理更大规模的数据。这需要在 MicroPython 移植层修改堆的起始地址和大小。

4.3 帧缓冲区(Frame Buffer)

如果使用双缓冲或需要离屏渲染,帧缓冲区可能会占不少内存(例如 320×240 像素 RGB565 格式约 150 KB)。同样可以放在 PSRAM 中,避免挤占 SRAM。

4.4 栈大小

ESP32-S3 的双核特性:你的代码目前应该运行在 PRO_CPU(协议 CPU)上,另一个 APP_CPU 默认空闲。需要注意栈大小设置,防止递归调用过深导致栈溢出。可以将主栈设置在 SRAM 中。

4.5 对齐和访问速度

你之前遇到过的 -2 变成 4294967294 的符号扩展问题,本质上是因为在 64 位环境下误读了 32 位有符号数。PSRAM 是 32 位宽,访问时需要注意:

  • 确保结构体对齐(__attribute__((aligned(4)))ALIGN(4))。
  • 从 PSRAM 读取数据时,如果跨边界访问,可能会触发异常或性能下降。大多数时候编译器会自动处理,但涉及手动指针操作时要小心。

4.6 链接脚本调整

原版链接脚本假设所有段都放在连续的内存区域(SRAM)。在 ESP32-S3 上,你需要为 PSRAM 单独定义一个内存区域,并将部分段(如 .bss.$poincare_pool.heap 等)定向到 PSRAM。

参考 ESP-IDF 的链接脚本模板,可以这样定义:

ld

MEMORY
{
  /* 内部 SRAM */
  SRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 0x40000  /* 256 KB?实际看芯片 */

  /* 外部 PSRAM(如果启用) */
  PSRAM (rw) : ORIGIN = 0x3F800000, LENGTH = 0x800000  /* 8 MB */
}

SECTIONS
{
  /* ... 其他段 ... */

  /* Poincare 池放入 PSRAM */
  .poincare_pool (NOLOAD) : ALIGN(4)
  {
    _poincare_pool_start = .;
    *(.bss.$poincare_pool)
    _poincare_pool_end = .;
  } > PSRAM

  /* MicroPython 堆放入 PSRAM */
  .micropython_heap (NOLOAD) : ALIGN(4)
  {
    _micropython_heap_start = .;
    . = . + 512K;  /* 分配 512 KB */
    _micropython_heap_end = .;
  } > PSRAM

  /* 栈、关键变量仍放在 SRAM */
  .bss (NOLOAD) : ALIGN(4)
  {
    /* ... 普通 .bss 变量 ... */
  } > SRAM
}

5. 内存布局参考图

下面是一个建议的内存布局方案,可供你在编写链接脚本时参考:

text

+------------------------+ 0x3FC88000 (SRAM 起始)
| 中断向量表 / 启动代码   |
+------------------------+
| 栈(通常向下增长)       |
+------------------------+
| .bss(普通变量)         |
+------------------------+
| .data(已初始化变量)    |
+------------------------+ SRAM 结束

+------------------------+ 0x3F800000 (PSRAM 起始)
| Poincaré 内存池         |  (例如 256 KB)
+------------------------+
| MicroPython 堆          |  (例如 512 KB)
+------------------------+
| 帧缓冲区 / 其他大块数据  |  (剩余空间)
+------------------------+ PSRAM 结束 (约 8 MB)

6. 下一步行动建议

  1. 确定 PSRAM 是否启用:在 ESP-IDF 配置中(idf.py menuconfig -> Component config -> ESP32S3-specific -> Support for external, SPI-connected RAM),确保启用了 PSRAM,并选择适当的配置(如 Octal SPI PSRAM)。
  2. 编写或修改链接脚本:基于 ESP-IDF 默认的 esp32s3.ld,增加 PSRAM 内存区域定义,并将大段(如 Poincaré 池)定向到 PSRAM。
  3. 验证内存布局:编译后查看 .map 文件,确认关键段是否被放在了预期位置。
  4. 在 C++ 代码中获取段地址:使用 extern char _poincare_pool_start[]; 等声明,在初始化时计算池的大小,并传递给 Poincaré 引擎。

下一篇文章,我们将深入 Ion 硬件抽象层的移植,从点亮屏幕和读取按键开始,让 NumWorks 真正在你的 ESP32-S3 上“跑起来”。敬请期待!