器→工具, 电子电路

Arduino 存储之EEPROM 和 SD 卡模块

钱魏Way · · 4 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

EEPROM (电可擦除可编程只读存储器)

EEPROM 是 Arduino 微控制器内部的一种非易失性存储器。这意味着即使 Arduino 断电,存储在 EEPROM 中的数据也不会丢失。它适用于存储少量、需要长期保存的配置信息、校准数据或计数器等。

EEPROM 的特点

  • 非易失性:数据断电不丢失。
  • 容量小:通常只有几百字节到几千字节(例如,ATmega328P 微控制器内置 1KB EEPROM)。
  • 擦写寿命有限:每个字节的擦写次数是有限的(通常为 10 万次),所以不适合频繁写入。
  • 读写速度相对较慢:相比 RAM。

如何使用 EEPROM

Arduino IDE 提供了一个方便的 EEPROM 库,让你轻松读写 EEPROM。

核心函数

  • read(address): 从指定地址读取一个字节的数据。
  • write(address, value): 将一个字节的数据写入指定地址。
  • update(address, value): 这是一个优化过的写入函数。它会先读取地址上的数据,如果与要写入的数据相同,则不进行写入操作,这样可以延长 EEPROM 的寿命。强烈建议使用 update() 而不是 write() 进行写入。
  • get(address, variable): 读取多个字节的数据,将其反序列化为任何数据类型(如 int, float, struct 等)。
  • put(address, value): 写入多个字节的数据,将任何数据类型序列化后写入 EEPROM。

学习示例

写入和读取单个字节

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  while (!Serial); // 等待串口连接

  int address = 0; // 从地址0开始存储

  // 写入一个字节
  byte valueToWrite = 123;
  EEPROM.update(address, valueToWrite); // 使用 update() 延长寿命
  Serial.print("写入值: ");
  Serial.println(valueToWrite);

  // 读取一个字节
  byte valueRead = EEPROM.read(address);
  Serial.print("读取值: ");
  Serial.println(valueRead);
}

void loop() {
  // EEPROM 操作通常在 setup() 中执行,或者在特定事件触发时执行。
  // 避免在 loop() 中频繁写入。
}

存储和读取复杂数据类型(例如:一个整数)

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  int address = 0; // 从地址0开始

  // 写入一个整数
  int intValueToWrite = 12345;
  EEPROM.put(address, intValueToWrite);
  Serial.print("写入整数: ");
  Serial.println(intValueToWrite);

  // 读取一个整数
  int intValueRead;
  EEPROM.get(address, intValueRead);
  Serial.print("读取整数: ");
  Serial.println(intValueRead);
}

void loop() {
}

存储和读取自定义结构体

#include <EEPROM.h>

// 定义一个结构体来存储多个相关数据
struct MyData {
  int id;
  float temperature;
  bool isActive;
};

void setup() {
  Serial.begin(9600);
  while (!Serial);

  int address = 0;

  // 准备要写入的数据
  MyData dataToWrite = {101, 25.5, true};
  Serial.println("写入数据:");
  Serial.print("  ID: "); Serial.println(dataToWrite.id);
  Serial.print("  Temperature: "); Serial.println(dataToWrite.temperature);
  Serial.print("  Active: "); Serial.println(dataToWrite.isActive ? "Yes" : "No");

  // 将结构体写入 EEPROM
  EEPROM.put(address, dataToWrite);

  // 从 EEPROM 读取数据
  MyData dataRead;
  EEPROM.get(address, dataRead);
  Serial.println("\n读取数据:");
  Serial.print("  ID: "); Serial.println(dataRead.id);
  Serial.print("  Temperature: "); Serial.println(dataRead.temperature);
  Serial.print("  Active: "); Serial.println(dataRead.isActive ? "Yes" : "No");
}

void loop() {
}

学习建议

  • 理解地址:EEPROM 像一个数组,每个字节都有一个唯一的地址。
  • 注意数据类型:当使用 put() 和 get() 存储和读取复杂数据类型时,确保读取时的数据类型与写入时的数据类型完全匹配,否则可能会得到乱码。
  • 寿命限制:记住 EEPROM 的擦写寿命限制。避免在 loop() 中频繁写入,只在必要时才进行写入操作。
  • EEPROM 大小:不同型号的 Arduino 板载 EEPROM 大小不同,例如 Uno (ATmega328P) 是 1KB,Mega (ATmega2560) 是 4KB。可以通过length() 获取 EEPROM 的总大小。

SD 卡模块 (MicroSD Card Module)

当你需要存储大量数据时,例如传感器数据日志、图片、音频文件或复杂的配置文件,EEPROM 的容量就远远不够了。这时,SD 卡模块就成了理想的选择。它允许你使用标准的 MicroSD 卡作为存储介质。

SD 卡模块的特点

  • 容量大:SD 卡的容量可以从几百兆字节到几百吉字节,远超 EEPROM。
  • 非易失性:数据断电不丢失。
  • 擦写寿命长:通常比 EEPROM 长得多。
  • 读写速度快:比 EEPROM 快得多,尤其适合日志记录。
  • 使用文件系统:SD 卡通常使用 FAT16 或 FAT32 文件系统,方便文件管理。

所需硬件

  • SD 卡模块:通常是 SPI 接口。
  • MicroSD 卡:根据需求选择合适的容量和速度等级。
  • Arduino 板
  • 跳线或面包板用于连接。

连接 SD 卡模块到 Arduino

SD 卡模块通常通过 SPI (Serial Peripheral Interface) 协议与 Arduino 通信。以下是典型的连接方式:

SD 卡模块引脚 Arduino Uno 引脚(SPI)
VCC 5V
GND GND
MISO D12
MOSI D11
SCK D13
CS (Chip Select) D10 (或任何其他数字引脚)

注意:对于 Arduino Mega,SPI 引脚位置不同:MISO (D50), MOSI (D51), SCK (D52)。CS 引脚仍然可以自由选择。

如何使用 SD 卡模块

Arduino IDE 提供了一个强大的 SD 库,用于与 SD 卡进行交互。

前期准备

  • 格式化 SD 卡:第一次使用 SD 卡时,建议将其格式化为 FAT16 或 FAT32 文件系统。可以通过电脑来完成。
  • 安装 SD 库:Arduino IDE 通常内置了 SD 库,如果提示找不到,可以通过 “工具” -> “管理库” 搜索并安装。

核心函数

  • begin(csPin): 初始化 SD 卡模块。csPin 是连接到 SD 模块 CS 引脚的 Arduino 引脚号。
  • exists(filename): 检查文件或目录是否存在。
  • mkdir(directoryName): 创建目录。
  • rmdir(directoryName): 删除空目录。
  • remove(filename): 删除文件。
  • open(filename, mode): 打开文件。mode 可以是 FILE_READ (只读) 或 FILE_WRITE (写入,如果文件不存在则创建)。
  • print(), file.println(), file.write(): 写入数据到文件,用法与 Serial 类似。
  • read(): 从文件中读取一个字节。
  • available(): 返回文件中可供读取的字节数。
  • close(): 关闭文件。非常重要! 写入数据后必须关闭文件才能确保数据被保存。

学习示例

检查 SD 卡并列出文件

#include <SPI.h>
#include <SD.h>

const int chipSelect = 10; // SD 模块的 CS 引脚连接到 Arduino 的 D10

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.print("正在初始化 SD 卡...");

  if (!SD.begin(chipSelect)) {
    Serial.println("SD 卡初始化失败或未插入!");
    while (true); // 停在这里
  }
  Serial.println("SD 卡初始化成功。");

  Serial.println("\nSD 卡内容:");
  printDirectory(SD.open("/"), 0); // 列出根目录内容
}

void loop() {
}

void printDirectory(File dir, int numTabs) {
  while (true) {
    File entry = dir.openNextFile();
    if (!entry) {
      // 没有更多文件了
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1); // 递归列出子目录
    } else {
      // files have a size
      Serial.print("\t\t");
      Serial.println(entry.size());
    }
    entry.close();
  }
}

创建文件并写入数据

#include <SPI.h>
#include <SD.h>

const int chipSelect = 10;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.print("正在初始化 SD 卡...");
  if (!SD.begin(chipSelect)) {
    Serial.println("SD 卡初始化失败!");
    while (true);
  }
  Serial.println("SD 卡初始化成功。");

  // 创建一个新文件并写入数据
  File dataFile = SD.open("datalog.txt", FILE_WRITE);

  // 如果文件打开成功,写入数据
  if (dataFile) {
    Serial.print("正在写入 datalog.txt...");
    dataFile.println("Hello from Arduino!");
    dataFile.println("This is a test log entry.");
    dataFile.println(millis()); // 写入当前运行时间
    dataFile.close(); // 关闭文件
    Serial.println("写入完成。");
  } else {
    Serial.println("打开 datalog.txt 失败。");
  }
}

void loop() {
}

读取文件内容

#include <SPI.h>
#include <SD.h>

const int chipSelect = 10;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.print("正在初始化 SD 卡...");
  if (!SD.begin(chipSelect)) {
    Serial.println("SD 卡初始化失败!");
    while (true);
  }
  Serial.println("SD 卡初始化成功。");

  // 打开文件进行读取
  File dataFile = SD.open("datalog.txt"); // 默认以只读模式打开

  if (dataFile) {
    Serial.println("正在读取 datalog.txt:");
    // 逐字符读取文件内容并输出到串口
    while (dataFile.available()) {
      Serial.write(dataFile.read());
    }
    dataFile.close(); // 关闭文件
  } else {
    Serial.println("打开 datalog.txt 失败。");
  }
}

void loop() {
}

学习建议

  • SPI 连接:确保 SPI 引脚连接正确,特别是 MISO, MOSI, SCK。CS 引脚可以灵活选择,但要确保在begin() 中指定正确的引脚。
  • 文件关闭:写入数据后务必调用close() 关闭文件,否则数据可能不会被正确写入 SD 卡。
  • 文件路径:SD 卡支持目录结构,可以使用 / 来表示目录分隔符,例如open(“mydata/sensor.txt”)。
  • 错误处理:始终检查begin() 和 SD.open() 的返回值,以处理初始化失败或文件打开失败的情况。
  • 电流消耗:SD 卡模块在工作时会有一定的电流消耗,如果通过电池供电,需要考虑这一点。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注