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