存储设备 #
一、存储设备概述 #
1.1 存储设备分类 #
text
┌─────────────────────────────────────────────────────────┐
│ 存储设备分类 │
├─────────────────────────────────────────────────────────┤
│ │
│ 按存储介质分类: │
│ ├── SD卡:大容量,可移动 │
│ ├── SPI Flash:中等容量,速度快 │
│ ├── EEPROM:小容量,可靠性高 │
│ └── FRAM:非易失,高速写入 │
│ │
│ 按接口分类: │
│ ├── SDIO:SD卡原生接口 │
│ ├── SPI:通用串行接口 │
│ └── I2C:EEPROM常用接口 │
│ │
│ 按容量分类: │
│ ├── EEPROM:几KB - 几百KB │
│ ├── SPI Flash:几MB - 几十MB │
│ └── SD卡:几GB - 几百GB │
│ │
└─────────────────────────────────────────────────────────┘
1.2 存储设备对比 #
| 类型 | 容量 | 速度 | 接口 | 典型应用 |
|---|---|---|---|---|
| EEPROM | KB级 | 慢 | I2C | 配置存储 |
| SPI Flash | MB级 | 快 | SPI | 固件存储 |
| SD卡 | GB级 | 中等 | SDIO/SPI | 数据记录 |
| USB Flash | GB级 | 快 | USB | 数据传输 |
二、SD卡存储 #
2.1 SD卡基础操作 #
Raspberry Pi上的SD卡通常作为系统盘使用,我们可以通过文件系统直接访问。
java
package com.example.storage;
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.ArrayList;
public class SDCardStorage {
private final Path basePath;
public SDCardStorage(String basePath) {
this.basePath = Paths.get(basePath);
if (!Files.exists(this.basePath)) {
try {
Files.createDirectories(this.basePath);
} catch (IOException e) {
throw new RuntimeException("无法创建存储目录", e);
}
}
}
public void writeFile(String filename, byte[] data) throws IOException {
Path filePath = basePath.resolve(filename);
Files.write(filePath, data);
}
public void writeFile(String filename, String content) throws IOException {
Path filePath = basePath.resolve(filename);
Files.write(filePath, content.getBytes());
}
public byte[] readFile(String filename) throws IOException {
Path filePath = basePath.resolve(filename);
return Files.readAllBytes(filePath);
}
public String readTextFile(String filename) throws IOException {
Path filePath = basePath.resolve(filename);
return new String(Files.readAllBytes(filePath));
}
public void appendFile(String filename, String content) throws IOException {
Path filePath = basePath.resolve(filename);
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
public void deleteFile(String filename) throws IOException {
Path filePath = basePath.resolve(filename);
Files.deleteIfExists(filePath);
}
public boolean exists(String filename) {
return Files.exists(basePath.resolve(filename));
}
public List<String> listFiles() throws IOException {
List<String> files = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(basePath)) {
for (Path path : stream) {
files.add(path.getFileName().toString());
}
}
return files;
}
public long getFileSize(String filename) throws IOException {
Path filePath = basePath.resolve(filename);
return Files.size(filePath);
}
public StorageInfo getStorageInfo() throws IOException {
FileStore store = Files.getFileStore(basePath);
long total = store.getTotalSpace();
long free = store.getUsableSpace();
long used = total - free;
return new StorageInfo(total, used, free);
}
public static class StorageInfo {
public final long totalBytes;
public final long usedBytes;
public final long freeBytes;
public StorageInfo(long total, long used, long free) {
this.totalBytes = total;
this.usedBytes = used;
this.freeBytes = free;
}
public double getUsedPercentage() {
return (usedBytes * 100.0) / totalBytes;
}
@Override
public String toString() {
return String.format("Total: %.2fGB, Used: %.2fGB (%.1f%%), Free: %.2fGB",
totalBytes / 1024.0 / 1024 / 1024,
usedBytes / 1024.0 / 1024 / 1024,
getUsedPercentage(),
freeBytes / 1024.0 / 1024 / 1024);
}
}
public static void main(String[] args) throws IOException {
SDCardStorage storage = new SDCardStorage("/home/pi/data");
System.out.println("SD卡存储测试");
System.out.println("存储信息: " + storage.getStorageInfo());
storage.writeFile("test.txt", "Hello SD Card!");
System.out.println("写入文件: test.txt");
String content = storage.readTextFile("test.txt");
System.out.println("读取内容: " + content);
System.out.println("文件列表: " + storage.listFiles());
System.out.println("文件大小: " + storage.getFileSize("test.txt") + " bytes");
}
}
2.2 数据记录器 #
java
package com.example.storage;
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;
import java.util.function.Supplier;
public class DataLogger {
private final Path logPath;
private final DateTimeFormatter formatter;
private final ScheduledExecutorService scheduler;
private final Supplier<String> dataSupplier;
private ScheduledFuture<?> loggingTask;
public DataLogger(String logDirectory, String filenamePrefix,
Supplier<String> dataSupplier) {
this.logPath = Paths.get(logDirectory);
this.formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
this.dataSupplier = dataSupplier;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
try {
Files.createDirectories(logPath);
} catch (IOException e) {
throw new RuntimeException("无法创建日志目录", e);
}
}
public void startLogging(int intervalMs) {
if (loggingTask != null) {
loggingTask.cancel(false);
}
loggingTask = scheduler.scheduleAtFixedRate(() -> {
try {
String timestamp = LocalDateTime.now().format(formatter);
String data = dataSupplier.get();
String logLine = timestamp + "," + data + "\n";
String filename = "log_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) +
".csv";
Path filePath = logPath.resolve(filename);
Files.write(filePath, logLine.getBytes(),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (Exception e) {
e.printStackTrace();
}
}, 0, intervalMs, TimeUnit.MILLISECONDS);
}
public void stopLogging() {
if (loggingTask != null) {
loggingTask.cancel(false);
loggingTask = null;
}
}
public void logEvent(String event) throws IOException {
String timestamp = LocalDateTime.now().format(formatter);
String logLine = timestamp + ",EVENT," + event + "\n";
String filename = "events_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) +
".log";
Path filePath = logPath.resolve(filename);
Files.write(filePath, logLine.getBytes(),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
public void shutdown() {
stopLogging();
scheduler.shutdown();
}
public static void main(String[] args) throws InterruptedException {
DataLogger logger = new DataLogger("/home/pi/logs", "sensor",
() -> String.format("%.2f,%.2f", Math.random() * 100, Math.random() * 50));
System.out.println("数据记录器测试");
logger.startLogging(1000);
Thread.sleep(10000);
logger.stopLogging();
logger.shutdown();
System.out.println("记录完成");
}
}
2.3 循环缓冲文件 #
java
package com.example.storage;
import java.io.*;
import java.nio.file.*;
import java.util.concurrent.atomic.AtomicLong;
public class CircularFileBuffer {
private final Path basePath;
private final long maxFileSize;
private final int maxFiles;
private final AtomicLong currentSize = new AtomicLong(0);
private Path currentFile;
private int currentIndex = 0;
public CircularFileBuffer(String basePath, long maxFileSize, int maxFiles) {
this.basePath = Paths.get(basePath);
this.maxFileSize = maxFileSize;
this.maxFiles = maxFiles;
try {
Files.createDirectories(this.basePath);
findCurrentFile();
} catch (IOException e) {
throw new RuntimeException("无法创建缓冲目录", e);
}
}
private void findCurrentFile() throws IOException {
for (int i = 0; i < maxFiles; i++) {
Path file = basePath.resolve("buffer_" + i + ".dat");
if (Files.exists(file)) {
long size = Files.size(file);
if (size < maxFileSize) {
currentFile = file;
currentIndex = i;
currentSize.set(size);
return;
}
}
}
rotateFile();
}
private void rotateFile() throws IOException {
currentIndex = (currentIndex + 1) % maxFiles;
currentFile = basePath.resolve("buffer_" + currentIndex + ".dat");
Files.deleteIfExists(currentFile);
Files.createFile(currentFile);
currentSize.set(0);
}
public synchronized void write(byte[] data) throws IOException {
if (currentSize.get() + data.length > maxFileSize) {
rotateFile();
}
Files.write(currentFile, data, StandardOpenOption.APPEND);
currentSize.addAndGet(data.length);
}
public synchronized void writeLine(String line) throws IOException {
write((line + "\n").getBytes());
}
public byte[] readOldest() throws IOException {
int oldestIndex = (currentIndex + 1) % maxFiles;
Path oldestFile = basePath.resolve("buffer_" + oldestIndex + ".dat");
if (Files.exists(oldestFile)) {
return Files.readAllBytes(oldestFile);
}
return null;
}
public byte[] readCurrent() throws IOException {
if (currentFile != null && Files.exists(currentFile)) {
return Files.readAllBytes(currentFile);
}
return null;
}
public void clear() throws IOException {
for (int i = 0; i < maxFiles; i++) {
Path file = basePath.resolve("buffer_" + i + ".dat");
Files.deleteIfExists(file);
}
rotateFile();
}
public static void main(String[] args) throws IOException, InterruptedException {
CircularFileBuffer buffer = new CircularFileBuffer("/home/pi/circular",
1024 * 1024, 5);
System.out.println("循环缓冲文件测试");
for (int i = 0; i < 100; i++) {
buffer.writeLine("Line " + i + ": " + System.currentTimeMillis());
Thread.sleep(100);
}
System.out.println("当前文件内容:");
byte[] current = buffer.readCurrent();
if (current != null) {
System.out.println(new String(current));
}
}
}
三、EEPROM存储 #
3.1 I2C EEPROM驱动 #
java
package com.example.storage;
import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;
public class I2CEEPROM {
private static final int PAGE_SIZE = 64;
private static final int WRITE_DELAY_MS = 5;
private final I2C device;
private final int capacity;
public I2CEEPROM(var pi4j, int bus, int address, int capacity) {
this.capacity = capacity;
this.device = pi4j.create(I2C.newConfigBuilder(pi4j)
.id("eeprom")
.name("I2C EEPROM")
.bus(bus)
.device(address)
.build());
}
public void writeByte(int address, byte data) throws InterruptedException {
byte[] buffer = new byte[2];
buffer[0] = (byte) ((address >> 8) & 0xFF);
buffer[1] = (byte) (address & 0xFF);
device.write(buffer);
device.write(data);
Thread.sleep(WRITE_DELAY_MS);
}
public byte readByte(int address) {
byte[] addrBytes = {
(byte) ((address >> 8) & 0xFF),
(byte) (address & 0xFF)
};
device.write(addrBytes);
return (byte) device.read();
}
public void write(int address, byte[] data) throws InterruptedException {
int offset = 0;
while (offset < data.length) {
int pageStart = (address + offset) & ~(PAGE_SIZE - 1);
int pageOffset = (address + offset) % PAGE_SIZE;
int writeSize = Math.min(PAGE_SIZE - pageOffset, data.length - offset);
byte[] buffer = new byte[writeSize + 2];
buffer[0] = (byte) (((address + offset) >> 8) & 0xFF);
buffer[1] = (byte) ((address + offset) & 0xFF);
System.arraycopy(data, offset, buffer, 2, writeSize);
device.write(buffer);
Thread.sleep(WRITE_DELAY_MS);
offset += writeSize;
}
}
public byte[] read(int address, int length) {
byte[] addrBytes = {
(byte) ((address >> 8) & 0xFF),
(byte) (address & 0xFF)
};
device.write(addrBytes);
byte[] data = new byte[length];
device.read(data, 0, length);
return data;
}
public void writeString(int address, String str) throws InterruptedException {
byte[] data = str.getBytes();
byte[] withLength = new byte[data.length + 2];
withLength[0] = (byte) ((data.length >> 8) & 0xFF);
withLength[1] = (byte) (data.length & 0xFF);
System.arraycopy(data, 0, withLength, 2, data.length);
write(address, withLength);
}
public String readString(int address) {
byte[] lengthBytes = read(address, 2);
int length = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
if (length <= 0 || length > capacity - address - 2) {
return null;
}
byte[] data = read(address + 2, length);
return new String(data);
}
public void clear() throws InterruptedException {
byte[] zeros = new byte[PAGE_SIZE];
for (int address = 0; address < capacity; address += PAGE_SIZE) {
write(address, zeros);
}
}
public int getCapacity() {
return capacity;
}
public static void main(String[] args) throws InterruptedException {
var pi4j = Pi4J.newAutoContext();
I2CEEPROM eeprom = new I2CEEPROM(pi4j, 1, 0x50, 32768);
System.out.println("I2C EEPROM测试");
System.out.println("容量: " + eeprom.getCapacity() + " bytes");
eeprom.writeString(0, "Hello EEPROM!");
System.out.println("写入字符串");
String str = eeprom.readString(0);
System.out.println("读取字符串: " + str);
eeprom.writeByte(100, (byte) 0x55);
byte b = eeprom.readByte(100);
System.out.printf("写入/读取字节: 0x%02X%n", b);
pi4j.shutdown();
}
}
3.2 配置存储管理 #
java
package com.example.storage;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ConfigStorage {
private final I2CEEPROM eeprom;
private final Map<String, String> config = new ConcurrentHashMap<>();
private static final int CONFIG_START = 0;
private static final int CONFIG_MAX_SIZE = 4096;
public ConfigStorage(I2CEEPROM eeprom) {
this.eeprom = eeprom;
load();
}
public void put(String key, String value) {
config.put(key, value);
save();
}
public String get(String key) {
return config.get(key);
}
public String get(String key, String defaultValue) {
return config.getOrDefault(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String value = config.get(key);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
public double getDouble(String key, double defaultValue) {
String value = config.get(key);
if (value != null) {
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
public boolean getBoolean(String key, boolean defaultValue) {
String value = config.get(key);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
public void remove(String key) {
config.remove(key);
save();
}
public Set<String> keys() {
return config.keySet();
}
public void clear() {
config.clear();
save();
}
private void save() {
try {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : config.entrySet()) {
sb.append(entry.getKey())
.append("=")
.append(entry.getValue().replace("\n", "\\n"))
.append("\n");
}
String content = sb.toString();
if (content.length() > CONFIG_MAX_SIZE) {
throw new RuntimeException("配置数据超出最大限制");
}
eeprom.writeString(CONFIG_START, content);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void load() {
try {
String content = eeprom.readString(CONFIG_START);
if (content != null && !content.isEmpty()) {
String[] lines = content.split("\n");
for (String line : lines) {
int eqIndex = line.indexOf('=');
if (eqIndex > 0) {
String key = line.substring(0, eqIndex);
String value = line.substring(eqIndex + 1).replace("\\n", "\n");
config.put(key, value);
}
}
}
} catch (Exception e) {
// 首次使用,配置为空
}
}
public static void main(String[] args) throws InterruptedException {
var pi4j = Pi4J.newAutoContext();
I2CEEPROM eeprom = new I2CEEPROM(pi4j, 1, 0x50, 32768);
ConfigStorage config = new ConfigStorage(eeprom);
System.out.println("配置存储测试");
config.put("device.name", "EmbeddedDevice01");
config.put("device.interval", "1000");
config.put("device.threshold", "25.5");
config.put("device.enabled", "true");
System.out.println("设备名称: " + config.get("device.name"));
System.out.println("间隔: " + config.getInt("device.interval", 0));
System.out.println("阈值: " + config.getDouble("device.threshold", 0));
System.out.println("启用: " + config.getBoolean("device.enabled", false));
pi4j.shutdown();
}
}
四、SPI Flash存储 #
4.1 W25Q Flash驱动 #
java
package com.example.storage;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiMode;
public class W25QFlash {
private static final byte CMD_WRITE_ENABLE = 0x06;
private static final byte CMD_WRITE_DISABLE = 0x04;
private static final byte CMD_READ_STATUS1 = 0x05;
private static final byte CMD_READ_STATUS2 = 0x35;
private static final byte CMD_WRITE_STATUS = 0x01;
private static final byte CMD_PAGE_PROGRAM = 0x02;
private static final byte CMD_QUAD_PAGE_PROGRAM = 0x32;
private static final byte CMD_BLOCK_ERASE_4K = 0x20;
private static final byte CMD_BLOCK_ERASE_32K = 0x52;
private static final byte CMD_BLOCK_ERASE_64K = (byte) 0xD8;
private static final byte CMD_CHIP_ERASE = (byte) 0xC7;
private static final byte CMD_READ_DATA = 0x03;
private static final byte CMD_FAST_READ = 0x0B;
private static final byte CMD_READ_JEDEC_ID = (byte) 0x9F;
private static final byte CMD_POWER_DOWN = (byte) 0xB9;
private static final byte CMD_RELEASE_POWER_DOWN = (byte) 0xAB;
private static final int PAGE_SIZE = 256;
private static final int SECTOR_SIZE = 4096;
private static final int BLOCK_SIZE = 65536;
private final Spi spi;
public W25QFlash(var pi4j, int bus, int chipSelect) {
this.spi = pi4j.create(Spi.newConfigBuilder(pi4j)
.id("w25q")
.name("W25Q SPI Flash")
.bus(bus)
.chipSelect(chipSelect)
.mode(SpiMode.MODE_0)
.baud(16000000)
.build());
}
public int[] readJEDECId() {
byte[] txData = {CMD_READ_JEDEC_ID, 0, 0, 0};
byte[] rxData = spi.transfer(txData);
return new int[]{rxData[1] & 0xFF, rxData[2] & 0xFF, rxData[3] & 0xFF};
}
public byte readStatus() {
byte[] txData = {CMD_READ_STATUS1, 0};
byte[] rxData = spi.transfer(txData);
return rxData[1];
}
public boolean isBusy() {
return (readStatus() & 0x01) != 0;
}
public void waitReady() {
while (isBusy()) {
try { Thread.sleep(1); } catch (InterruptedException e) {}
}
}
public void writeEnable() {
spi.transfer(CMD_WRITE_ENABLE);
}
public void writeDisable() {
spi.transfer(CMD_WRITE_DISABLE);
}
public byte[] read(int address, int length) {
byte[] txData = new byte[length + 4];
txData[0] = CMD_READ_DATA;
txData[1] = (byte) ((address >> 16) & 0xFF);
txData[2] = (byte) ((address >> 8) & 0xFF);
txData[3] = (byte) (address & 0xFF);
byte[] rxData = spi.transfer(txData);
byte[] result = new byte[length];
System.arraycopy(rxData, 4, result, 0, length);
return result;
}
public void pageProgram(int address, byte[] data) {
if (data.length > PAGE_SIZE) {
throw new IllegalArgumentException("数据长度超过页大小");
}
writeEnable();
waitReady();
byte[] txData = new byte[data.length + 4];
txData[0] = CMD_PAGE_PROGRAM;
txData[1] = (byte) ((address >> 16) & 0xFF);
txData[2] = (byte) ((address >> 8) & 0xFF);
txData[3] = (byte) (address & 0xFF);
System.arraycopy(data, 0, txData, 4, data.length);
spi.transfer(txData);
waitReady();
}
public void write(int address, byte[] data) {
int offset = 0;
while (offset < data.length) {
int pageRemaining = PAGE_SIZE - ((address + offset) % PAGE_SIZE);
int writeSize = Math.min(pageRemaining, data.length - offset);
byte[] pageData = new byte[writeSize];
System.arraycopy(data, offset, pageData, 0, writeSize);
pageProgram(address + offset, pageData);
offset += writeSize;
}
}
public void eraseSector(int address) {
writeEnable();
waitReady();
byte[] txData = {
CMD_BLOCK_ERASE_4K,
(byte) ((address >> 16) & 0xFF),
(byte) ((address >> 8) & 0xFF),
(byte) (address & 0xFF)
};
spi.transfer(txData);
waitReady();
}
public void eraseBlock(int address) {
writeEnable();
waitReady();
byte[] txData = {
CMD_BLOCK_ERASE_64K,
(byte) ((address >> 16) & 0xFF),
(byte) ((address >> 8) & 0xFF),
(byte) (address & 0xFF)
};
spi.transfer(txData);
waitReady();
}
public void chipErase() {
writeEnable();
waitReady();
spi.transfer(CMD_CHIP_ERASE);
waitReady();
}
public void powerDown() {
spi.transfer(CMD_POWER_DOWN);
}
public void powerUp() {
spi.transfer(CMD_RELEASE_POWER_DOWN);
try { Thread.sleep(3); } catch (InterruptedException e) {}
}
public static void main(String[] args) throws InterruptedException {
var pi4j = Pi4J.newAutoContext();
W25QFlash flash = new W25QFlash(pi4j, 0, 0);
System.out.println("W25Q SPI Flash测试");
int[] jedec = flash.readJEDECId();
System.out.printf("JEDEC ID: %02X %02X %02X%n", jedec[0], jedec[1], jedec[2]);
byte[] writeData = "Hello SPI Flash!".getBytes();
flash.eraseSector(0);
flash.write(0, writeData);
System.out.println("写入数据: " + new String(writeData));
byte[] readData = flash.read(0, writeData.length);
System.out.println("读取数据: " + new String(readData));
pi4j.shutdown();
}
}
五、存储管理器 #
5.1 统一存储接口 #
java
package com.example.storage;
import java.io.IOException;
public interface Storage {
void write(int address, byte[] data) throws IOException, InterruptedException;
byte[] read(int address, int length) throws IOException;
void erase(int address, int length) throws IOException, InterruptedException;
int getCapacity();
String getName();
}
5.2 存储管理器 #
java
package com.example.storage;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class StorageManager {
private final Map<String, Storage> storages = new ConcurrentHashMap<>();
public void register(String name, Storage storage) {
storages.put(name, storage);
}
public Storage get(String name) {
return storages.get(name);
}
public void write(String storageName, int address, byte[] data)
throws IOException, InterruptedException {
Storage storage = storages.get(storageName);
if (storage != null) {
storage.write(address, data);
}
}
public byte[] read(String storageName, int address, int length) throws IOException {
Storage storage = storages.get(storageName);
if (storage != null) {
return storage.read(address, length);
}
return null;
}
public Map<String, Integer> getCapacities() {
Map<String, Integer> capacities = new ConcurrentHashMap<>();
storages.forEach((name, storage) -> capacities.put(name, storage.getCapacity()));
return capacities;
}
}
六、总结 #
存储设备驱动要点:
- 接口选择:根据容量和速度需求选择合适的存储介质
- 写入保护:注意Flash写入前需要擦除
- 页对齐:Flash写入需要按页对齐
- 数据校验:添加CRC校验确保数据完整性
- 磨损均衡:对于频繁写入的场景实现磨损均衡
下一章我们将学习网络通信,实现设备间的数据传输。
最后更新:2026-03-27