7. 系统架构

感谢您阅读到目前为止完成的教程。它可能永远也不会完成。无论如何,我想快速讨论一下系统架构。

首先,OpenMV Cam 基于 STM32 微控制器架构,因为 MicroPython pyboard 基于 STM32 微控制器架构。如果项目是使用其他系统启动的,情况可能会完全不同。

接下来,我们选择不在原始 OpenMV Cam 中使用 DRAM,因为这样会使系统在低产量下变得过于昂贵。SDR DRAM(STM32 所支持的类型)在低制造量下并不是最便宜的,而且大大增加了板级设计的复杂性(例如,你需要做 8 层 PCB 来布线路径)。随着我们为 OpenMV Cam 配备更快的主处理器,SDR DRAM 的速度也未能跟上内部 RAM 的速度。例如,在 STM32H7 上,内部 RAM 的带宽为 3.2GB/s,而最大 SDR RAM 带宽为 666MB/s,即使我们使用 8 层板设计,并采用 32 位 DRAM 总线,且为 DRAM 提供 50 个以上的 I/O 引脚。

因此,由于我们建立在 STM32 架构上,并且目前受限于使用昂贵且速度较慢的 SDR DRAM,我们尚未将其添加为内部 SRAM,因为我们的内部 SRAM 速度要快得多。随着生产量的增加和技术的改进,希望我们能够在保持 OpenMV Cam 简单易用的同时拥有更多内存。

7.1. 内存架构

鉴于上述内存架构的限制,我们将所有代码都设计为在 STM32 微控制器的内存中运行。然而,STM32 并没有一个大型的连续内存映射。它为不同的使用场景提供了不同的 RAM 区段。

首先,有一个包含全局变量、堆和栈的 RAM 段。堆和全局变量的大小是固定的,因此只有栈会增长和收缩。出于性能原因,堆/栈冲突并不会被不断检查,因此不要在 OpenMV Cam 上使用递归函数。

至于堆,它的大小是固定的,不会朝着栈增长,并由 MicroPython 的垃圾回收器管理。MicroPython 自动释放堆中未使用的块。然而,MicroPython 堆的设计不允许它像 PC 上的堆一样任意大(例如,在兆字节范围内)。因此,即使我们有 DRAM,也很难利用 MicroPython 的堆。

接下来,有一个更大的内存段用于帧缓冲区以存储图像。当调用 sensor.snapshot() 等函数时,新图像将存储在帧缓冲区的底部。然后,帧缓冲区中的任何未使用空间都可以用作从帧缓冲区的顶部向下构建的“帧缓冲区堆栈”。这种内存架构设计是使我们的许多计算机视觉方法能够执行而无需在 MicroPython 堆中分配大型数据结构的原因。

话虽如此,帧缓冲区堆栈仍然是一个堆栈,不支持随机分配和释放。幸运的是,大多数计算机视觉算法具有非常可预测的内存分配。对于那些不具有的(例如,AprilTags),我们在需要时在帧缓冲区堆栈内分配一个临时堆(同样是为了避免片段化 MicroPython 堆)。

最后,视觉算法通过在 MicroPython 堆中分配对象来返回它们的结果(通常很小)。然后,结果可以由 MicroPython 轻松进行垃圾回收,而帧缓冲区堆栈在任何计算机视觉算法执行完毕后完全清除。

现在,虽然这很好用,但意味着您只能在 RAM 中有一个大图像。由于 MicroPython 堆经过优化以存储小对象,将大型 100KB 图像存储在其中是没有意义的。为了使更多图像适合 RAM,我们允许使用帧缓冲区堆栈存储次要图像,使用 sensor.alloc_extra_fb()。通过在帧缓冲区堆栈上分配次要帧缓冲区,您现在可以在 RAM 中拥有两个或更多图像,但代价是减少用于更复杂算法(如 AprilTags)的内存空间。

因此,这就是内存架构。而且…我们允许图像存储在帧缓冲区、堆和帧缓冲区堆栈中。是的,我们的代码相当复杂,以处理所有这些,如果能把所有东西都放在一个大的 DRAM 中会很好,但现在您知道为什么情况不是这样。