【问题标题】:How to store text file on embedded systems flash memory and read from it如何在嵌入式系统闪存上存储文本文件并从中读取
【发布时间】:2023-08-17 11:00:02
【问题描述】:

我正在尝试执行以下操作:将文本文件 (7kB) 存储在 STEVAL-MKI109V2(与 freeRTOS 一起运行)板的闪存中,并读取此文本文件并在设备本身上对其进行一些计算。我有两个问题:

1) 存储文本文件 只需将文本文件添加到我的 keil 项目就足够了吗?编译后可以访问吗?

2) 访问数据 这就是我到现在为止失败的地方。起初我尝试使用 stdio.h 中的 fopen() 但我在编译时遇到了一些错误。我发现我的项目是使用 microLib 编译的,它似乎不包含文件 I/O。使用标准 C 库编译后它是成功的,但是一旦我到达代码中的 fopen 部分,系统就会崩溃。

现在我不知道是因为找不到文本文件,还是无法在嵌入式系统上使用 fopen()。除了 FLASH_Unlock(); 之外,我没有在 STM 文档或论坛中找到更多信息。函数,不过好像是用来写的。

我是否需要以另一种方式存储我的文本文件并通过内存地址而不是文件名来访问?我很困惑,在网上找不到任何信息。

提前感谢您的帮助!

【问题讨论】:

    标签: c++ embedded keil freertos


    【解决方案1】:

    如果您只想将文件的内容作为字符字符串,您可以将文件转换为 C 源代码,例如使用一个小的 Python 程序(或任何其他语言,我只使用 Python,因为在 Python 中比在 C 或 C++ 中更简单)。只需创建类似的内容:

    const char my_text[] = {
    
        ... here goes the text
    
    };
    

    最简单的,用"拥抱每一行。

    然后将该文件添加到项目中(您需要在使用它的地方进行 extern 声明)或 #include 并声明 static(感谢 @clifford)。

    【讨论】:

    • 谢谢,这个简单的想法帮助了我的问题:)
    • 根据我在 ARM RealView(Keil 的 ARM-MDK 中使用的编译器,自从提到 MicroLib 以来我认为这是有问题的工具链)中的经验(尽管它可能特定于 C++),my_text[]将位于 RAM 中,并在 ROM 中使用 const 初始化程序,因此浪费了大量 RAM。我已经编辑了您的答案以将其声明为static,这具有直接在闪存中定位它的效果。希望你不要反对。
    • @Clifford:谢谢,我忘了提static。但是,我回滚并编辑了最后一句话,因为我实际上描述了两种方式:具有全局范围的外部结构和#included(实际上我忘记了static)。
    • @Clifford:我认为编译器处理类似于.data 的 const 充其量是损坏的。对于甚至会增加可靠性问题的 Cortex-M,作为保护区域,至少需要另一个 MPU 区域。确定这不是链接描述文件和启动代码中的错误吗?对于 gcc,我可以告诉你它有效。但是分配给* const 的复合文字存在一些问题。当我为外部结构使用特殊部分时,我怀疑这有问题并且没有跟踪该跟踪(我只是使用单独的 const 结构而不是文字并将其地址分配给第二个结构中的指针)。
    • @Unfixable:数组毕竟只是一个数组。由于被 const 初始化为您存储的字符的大小,您可以获得它的大小。由于代码是自动生成的,您还可以创建更复杂的结构,或者在每行的开头添加一个索引数组(char * 的数组将起到相同的作用,但如果您想在一次 - 例如通过像以太网这样的块接口发送。请注意,当连接字符串时,C 确实将 NUL 添加到每个字符串。你首先必须明确什么你真的想做,那怎么做
    【解决方案2】:

    如果你只是想在你的应用程序中嵌入一个资源,那么实现一个文件系统就太过分了,你应该使用@Olaf的方法。

    但是,如果您想要可以独立于您的应用程序进行编程的数据;那么您可以简单地保留必要数量的片上闪存页面,并通过 JTAG 单独对这些页面进行编程,或者添加对加载和编程闪存页面到您的应用程序的支持。或者为了获得更大的灵活性,您可以添加一个使用保留数量的闪存页面的文件系统 - 这还需要您向应用程序添加一种下载和写入数据的方法。

    STM32F1xx 上的所有闪存页大小相同,因此无论使用低内存页还是高内存页都没有关系,但使用高页更简单,因为代码开始的复位向量在低内存中。要保留页面(防止编译器将代码放入其中),您只需减少项目内存映射选项中的默认高位地址(我假设您使用的是 Keil ARM-MDK/uVision,因为您提到了 MicroLib)。

    Keil 的 MicroLib 或其全功能库都支持 I/O 流,但由于无法提前知道目标的 I/O 能力,因此需要所谓的retargetting。在最简单的情况下,这通常仅针对 stdout/stdin 流实现,但您可以为任何 I/O 设备实现文件描述符 - 但是要执行文件 I/O,您需要一个文件系统,例如 ELM FatFsYaffs 用于您仍然需要实现访问闪存的低级驱动程序。如果您使用文件系统库;您实际上不必通过重定向来挂钩 stdio;您可以直接访问该库 - 我提到了重定向,因为您似乎对 stdio 的工作原理有些松散。

    STM32F1xx 上闪存编程的详细信息在主要参考资料的separate manual 中,而 STM32 标准外设库包含支持编程的低级函数。在这里,您会发现 ST 文档中其他地方没有明确说明的严重问题;当您对闪存进行编程或擦除时,它会将地址和数据总线锁定到整个闪存 - 因为通常处理器也从中获取指令,所以整个内核会在操作,可能长达 40 毫秒(在 STM32F2xx 上更糟糕的是 800 毫秒!);因此写入闪存页面可能会弄乱时间关键操作。

    如果你想在这样的设备上使用文件系统;您最好使用 SPI 端口与 SD 卡进行通信,或者使用片外非易失性存储器。

    【讨论】:

    • 哇,感谢您的详尽解释。作为 3 周前才开始使用嵌入式系统的人,我很感激任何这样的信息!还有很多东西要学。
    • @Unfixable:好的;要记住的关键是闪存是字可编程/页可擦除的;要更改数据,您必须读取-修改-擦除-写入整个页面。您显然需要确保这些页面不用于可能正在执行的代码。处理写入过程中可能出现的电源故障是一个更复杂的问题。
    • @Unfixable:请注意,可单独擦除的单元通常称为“扇区”。此外,一些闪存架构进一步将每个扇区划分为“页”作为编程单元。您必须阅读参考手册和相应的应用说明。