使用I2C EEPROM作为Arduino上的文件系统

ChangeCode
转载
发布时间: 2025-06-14 15:04:09 | 阅读数 0收藏数 0评论数 0
封面
这是一篇关于使用EEPROM作为Arduino数据记录器存储的简短教程。虽然SD卡是常见的数据记录方式,但它们存在内存占用大、兼容性问题以及需要占用SPI总线的问题。相比之下,I2C EEPROM模块提供了一个低成本的替代方案,仅需两个引脚且有多种容量选择(如4kB、32kB和64kB)。

准备工作:

材料:

  1. 该库和测试程序适用于任何Arduino设备,无论是3.3V还是5V系统。它已经在多个平台如Arduino、ESP、Raspberry Pi Pico上进行了测试。
  2. 需要一个小的面包板和一些导线来连接EEPROM。
  3. EEPROM可作为模块、芯片或集成在实时时钟中提供。最常见的模块要么是图片右侧带有可配置I2C地址和写保护功能的那种,要么是中间固定地址的那种。你可以使用这个库与这些设备中的任何一个。
  4. 你需要Arduino IDE。我使用的是1.8.15版本。此外,你需要从以下链接下载库:
  5. https://github.com/slviajero/EepromFS
  6. 并将其整合到你的Arduino库中。
1

连接EEPROM

大多数模块都在电路板上印有针形图案。将Arduino的SDA脚(在UNO上为A4)与模块的SDA脚和SCL脚(在UNO上的A5)连接。也连接电源。几乎所有的eeprom都可以在5V的电压下运行。有一些模型的最小电压降为1.7V。这些是24FCxxx类型,xxx以位表示EEPROM大小。相当便宜的24Cxxx需要在2.7V到5V之间。

这些模块的I2C地址在0x50到0x57之间。在上述可配置模块上,包括写保护WP在内的所有跳线都应该打开。这样,该地址就会被设置为0x50。

EEPROM芯片也可以直接连接,使地址和WP引脚打开。不需要I2C上拉电阻。将地址引脚拉到5V将改变I2C地址。拉出WP将激活写入保护。SDA连接到针脚5,SCL连接到模块的针脚6。接通针脚8(VCC)和针脚4(GND)。

时钟芯片,根据不同的时钟模型,也有不同的地址。

非常常见的HW-084型号有0x57作为其4kB EEPROM的默认地址。这些模块都是矩形的。稍小的二次“小RTC”模块使用0x50。这两个模块都可以通过焊接跨接连接来进行配置。

2

下载软件

下载并安装EEPROM文件系统库

https://github.com/slviajero/EepromFS

.在Arduino IDE中打开库的测试程序示例/测试。此程序将运行EEPROM文件系统的一系列测试。在本教程中,它将被用来解释该库的特性。

在测试程序开始时

#include <EepromFS.h

#include <Wire.h>

EepromFS EFS (0x50, 32768);

将创建一个EepromFS对象。第一个参数是I2C地址,第二个参数是EEPROM的大小。将这些值设置为您的硬件配置。

请上传并运行该测试程序。在输出中,所有的“错误状态”行都应该显示结果0,即,如果EEPROM连接正确,则没有错误。输出的前几行应该是这样的:

-----------------------------------------

文件系统测试

-----------------------------------------

交配32个槽

格式化时间= 171

518字节更新

更新= 24000

错误状态:0

-----------------------------------------

已安装文件系统

4中的32个插槽

错误状态:0

-----------------------------------------

3

产品的性能和局限性

EepromFS将整个EEPROM空间划分为一个固定长度的等量插槽。在上面的示例中,它创建了32个大约1kB大小的插槽。一个文件可以和插槽的大小一样大,但不能更大。即使在其他插槽中有空间,文件也不能超过一个插槽。这使得文件系统非常简单,因为没有文件分配表或任何其他复杂的目录机制。

文件名可以为12个字符长。同时也可以使用典型的MSDOS 8.3文件名。文件名长度是EepromFS库的一个参数,可以更改为小于28的任何其他值。

只能同时打开两个文件。一个可以打开供读,另一个可以用于写入和附加。

有一个包含16个字节的读/写缓冲区。缓冲区长度可以更改为小于30的任何其他值。

对文件名和缓冲区长度的限制不是来自EepromFS库,而是来自底层的Arduino Wire库。一个I2C事务只能传输32个字节,包括该寻址。这将将有效负载的大小限制为30个字节。EEPROM通常可以处理64或128字节的较大的块大小。要使用这一点,将需要一个具有更大缓冲区大小的有线库。

测试程序可以下载

https://github.com/slviajero/EepromFS/blob/main/examples/baudrate/baudrate.ino

4

它是如何工作的?

如果您只对库本身及其api感兴趣,则可以跳过此段落。这是关于EEPROM计时和I2C的附加信息。

eeprom是按块组织的。每个方块都可以单独编写。这些块在32到128字节之间。编写一个块是一项耗时的活动,并且需要一个适当的延迟。

EEPROM可以一次接受一个块来进行读取,然后需要时间来消化这些信息。因此,最好在RAM中缓冲数据并一次传输一个块。不幸的是,Arduino I2C库将传输块的大小限制为32个字节。这是由参数设置的

#定义BUFFER_LENGTH 32

在电线线。可以通过编辑该库来更改该参数。这将允许更大的I2C传输尺寸。请注意,twi.h也需要更改,因为参数也出现在那里,并且不会自动调整。

EepromFS库接受标准线和缓冲区16个字节的限制。

库中的读取代码是这样看起来的

Wire.beginTransmission(eepromaddr);
unsigned int pa=p*EFS_PAGESIZE;
Wire.write((int)pa/256);
Wire.write((int)pa%256);
Wire.endTransmission();
Wire.requestFrom((int)eepromaddr, (int)EFS_PAGESIZE);
int dc=0;
while( !Wire.available() && dc++ < 1000) delay(0);
if (Wire.available() == EFS_PAGESIZE) {
for(uint8_t i=0; i<EFS_PAGESIZE; i++)
pagebuffer[i]=(uint8_t) Wire.read();
pagenumber=p;
pagechanged=false;
} else {
ferror|=1;
return 0;
}

eeprom希望该地址在事务开始时以两个字节的形式发送。这发生在第一个代码块中。在第二个代码块中,请求一个缓冲区页(16字节)的数据。一旦库有了可用的数据,它就会被检索到。除了需要在ESP8266系统上使用的延迟(0)外,库中没有延迟,即产生()。读取时间不确定,可用()用于同步EEPROM和Arduino。

写代码处理I2C限制和计时

无符号的intpa=页面编号*EFS_PAGESIZE;

Wire.beginTransmission(eepromaddr);
Wire.write((int)pa/256);
Wire.write((int)pa%256);
if (Wire.write(pagebuffer, EFS_PAGESIZE) != EFS_PAGESIZE) ferror|=1;
ferror+=2*Wire.endTransmission();
delay(EFSWRITEDELAY); // the write delay according to the AT24x datasheet
pagechanged=false;

地址传输两个字节,然后Write.write()发送缓冲区。EFS_PAGESIZE默认为16。任何不超过30的尺寸都是可能的,但不能更多。地址和有效负载必须适合于一个32字节的帧中。

传输后,一个快速延迟等待EEPROM消化数据。默认值为10 ms。在这里可以做一些调优。10 ms是一个标准值。有些EEPROM可以做4个ms。我们在这里阻止了库和调用程序,以避免读取与写操作的冲突。

除了此之外,eeprom不需要其他时间延迟。EEPROM的所有其他功能都以I2C总线速度运行。

如果您只需要一个稳定的EEPROM库,并且对文件系统API不感兴趣,那么您可以直接转到RAW API部分。使用这个API,您可以获得定时和缓冲,并可以作为字节数组访问EEPROM。

一个最佳的硬件适应线库将使用64或128字节的完整EEPROM块长度。这将使交易速度加快4到8倍。它还将增加EEPROM的生命周期,因为将避免使用多个缓冲区写入。

5

请使用POSIX样式的API

POSIX风格的API支持许多标准的C文件IO函数。实现的API函数是

bool format(uint8_t s);
uint8_t fopen(char* fn, char* m);
uint8_t fclose(uint8_t f);
uint8_t fclose(char* m);
bool eof(uint8_t f);
uint8_t fgetc(uint8_t f);
bool fputc(uint8_t ch, uint8_t f);
bool fflush(uint8_t f);
void rewind(uint8_t f);
uint8_t readdir();
char* filename(uint8_t f);
unsigned int filesize(uint8_t f);
uint8_t remove(char* fn);
uint8_t rename(char* ofn, char* nfn);
int available(uint8_t f);

文件句柄是uint8_t,即一个字节整数。

除了这些函数之外,还有一个包含事务错误状态的变量错误。0表示已成功。

将文件写入文件系统的典型程序应该是这样的

#include <EepromFS.h>
#include <Wire.h>
EepromFS EFS(0x50, 32768);
char* m="hello world";
void setup() {
 Wire.begin();
 Serial.begin(9600);
 if (EFS.begin()) {
  uint8_t f=EFS.fopen("test", "w");
  for(int i=0; i<11; i++) EFS.fputc(m[i], f);
  EFS.fclose(f);
  Serial.println("Wrote 11 bytes");
 } else 
  Serial.println("Mount failed");
}
void loop(){}

有许多例子,包括测试程序中的目录和错误处理

https://github.com/slviajero/EepromFS/blob/main/examples/efstest/efstest.ino

.

大多数函数的行为就像POSIX风格的文件访问函数将工作。

没有字符串打印或扫描功能。字符串转换必须在代码中使用该库来完成。这样,库就保持简单,不包括像String这样需要内存的库。

6

使用原始API

除了POSIX API之外,该库还提供了一个原始的访问API。这只是利用了读/写缓冲和时间,但不利用文件系统。使用原始API访问EEPROM可以并且将破坏文件系统结构。

我们实现了以下的库函数

uint8_t rawread(unsigned int a);
void rawwrite(unsigned int a, uint8_t d);
void rawflush();

EEPROM可以作为一个字节数组来访问。这些函数对应于EEPROM。写()和EEPROM。Arduino IDE的读()函数。所有的访问都是缓冲的,并且避免对I2C总线进行不必要的读写。这个库对于没有内置EEPROM的系统很有用。它为外部EEPROM提供了一个快速和精简的API。

关闭事务需要功能()。如果不需要传输更多的数据,就应该发生这种情况。它可以确保当前的缓冲区被写入到EEPROM中。

这个API的示例程序是

https://github.com/slviajero/EepromFS/blob/main/examples/efstest-eeprom/efstest-eeprom.ino

7

使用字节API

EepromFS的第三个API类似于原始API,但保护了文件系统结构。

uint8_t getdata(uint8_t s, unsigned int i);
void putdata(uint8_t s, unsigned int i, uint8_t d);
unsigned int EepromFS::size();

函数获取数据()和输入数据()访问文件系统的一个特定插槽。这个插槽是由一个整数s给出的。如果该插槽已被文件使用,则不进行测试。

这个API的代码片段将是这样的

 // allocate a file slot and call it dummy, close it right away
 uint8_t s=EFS.fopen("dummy", "w");
 EFS.fclose(s); 
 Serial.print("Slot number is "); Serial.println(s);
 // how big is the slot
 Serial.print("Data slot size is :"); Serial.println(EFS.size());
 // access it through the byte API
 for(int i=0; i<10; i++) EFS.putdata(s, i, i*i);
 EFS.rawflush();
 for(int i=0; i<10; i++) Serial.println(EFS.getdata(s, i));

fopen()语句仅用于创建一个文件,并记住它所在的插槽。调用输入数据()和获取数据()可以访问这个文件,就像标准的Arduino函数EEPROM一样。读取()和EEPROM。写()。必须保护数据。如果没有刷新,代码的读取部分仍然是成功的,因为数据存储在缓冲区中,但它不会被永久地写入EEPROM。

如果读取的数据或写入超出了槽的大小,则忽略事务。fgetc()在槽边界之外返回一个0,fputc()忽略数据。EFS.size()返回插槽中的字节数。

此测试程序可以从

https://github.com/slviajero/EepromFS/blob/main/examples/efstest-byte/efstest-byte.ino

8

使用基本的EepromFS

用EFS编写的eeprom可以集成并在物联网基本解释器中使用

https://github.com/slviajero/tinybasic

因为EepromFS是该系统中受支持的文件系统之一。你可以从

https://github.com/slviajero/tinybasic/tree/main/IoTBasic

.

使用Arduino UNO,可以编译一个整数基本值。打开IotBasic.ino并设置编译器指令

#undef BASICFULL
#undef BASICINTEGER
#define BASICSIMPLE
#undef BASICMINIMAL
#undef BASICTINYWITHFLOAT
BASICSIMPLE is defined, all others are #undef.
Open hardware-arduino.h and set the compiler directive
#undef USESPICOSERIAL 
#undef ARDUINOPS2
#undef ARDUINOUSBKBD
#undef ARDUINOPRT
#undef DISPLAYCANSCROLL
#undef ARDUINOLCDI2C
#undef ARDUINONOKIA51
#undef ARDUINOILI9488
#undef ARDUINOSSD1306
#undef LCDSHIELD
#undef ARDUINOTFT
#undef ARDUINOVGA
#undef ARDUINOEEPROM
#define ARDUINOEFS
#undef ARDUINOSD
#undef ESPSPIFFS
#undef RP2040LITTLEFS
#undef ARDUINORTC
#undef ARDUINOWIRE
#undef ARDUINOWIRESLAVE
#undef ARDUINORF24
#undef ARDUINOETH
#undef ARDUINOMQTT
#undef ARDUINOSENSORS
#undef ARDUINOSPIRAM 
#undef STANDALONE

所有编译器指令都是预期的。

另外,请将EEPROM参数设置为正确的值。

#define EEPROMI2CADDR 0x050
#define EFSEEPROMSIZE 32768

编译并上传草图到Arduino。

由测试程序创建的文件可以与目录一起列出

目录

file1 17

file2 52

测试11

假人

文件可以通过删除和重命名来删除和重命名。

基本的文件访问命令,如打开、关闭、输入和打印,可以用来读取数据。

eeprom可以在运行C++代码的系统和具有图形和物联网功能的更大的基本计算机之间进行交换,就像这些教程中的那样

https://www.instructables.com/A-Arduino-RP2040-Standalone-IoT-Computer-Running-B/

https://www.instructables.com/Build-a-80s-Style-Home-Computer-From-Scratch-From-/

https://www.instructables.com/The-10-Euro-IoT-Computer-With-ESP-8266/

数据可以通过这种方式被打乱,而不需要SD卡文件系统的开销。

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载