文章内容如有错误或排版问题,请提交反馈,非常感谢!
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 卡模块在工作时会有一定的电流消耗,如果通过电池供电,需要考虑这一点。



