/*
* 气轨光电门系统 - SD卡存储版本
* 使用SD卡存储所有实验数据:时间、速度、加速度、时间差和时间戳
* 硬件连接:
* - 光电门1: 数字引脚 2
* - 光电门2: 数字引脚 3
* - 蜂鸣器: 数字引脚 8
* - LCD显示屏: I2C接口 (16×2)
* - SD卡模块: SPI接口(CS-4, MOSI-11, MISO-12, SCK-13)
*/
#include <SD.h> // 添加SD卡库
#include <SPI.h> // SPI通信库
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// 光电门和蜂鸣器引脚
const int GATE1_PIN = 2;
const int GATE2_PIN = 3;
const int BUZZER_PIN = 8;
const int STATUS_LED = 13;
const int SD_CS = 4; // SD卡片选引脚
// LCD配置 (16列, 2行)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// SD卡配置
#define FILENAME "DATA.CSV" // 数据文件名
#define MAX_RECORDS 1000 // SD卡可存储更多记录
// 实验参数
const float WIDTH = 0.1; // 挡光片宽度(米)
float gateDistance = 0.36; // 光电门间距(米)
// 实验数据结构(增加时间差字段)
struct ExperimentData {
unsigned long time1; // 光电门1时间(μs)
unsigned long time2; // 光电门2时间(μs)
unsigned long timeDiff; // 新增:两次挡光中间时刻的时间差(μs)
float velocity1; // 门1速度(m/s)
float velocity2; // 门2速度(m/s)
float acceleration; // 计算加速度(m/s²)
unsigned long timestamp; // 实验时间戳(ms)
};
// 计时变量
volatile unsigned long gate1Start = 0;
volatile unsigned long gate1End = 0;
volatile unsigned long gate2Start = 0;
volatile unsigned long gate2End = 0;
// 状态标志
volatile bool gate1Active = false;
volatile bool gate2Active = false;
volatile bool experimentComplete = false;
// 显示控制
int displayState = 0; // 0:状态 1:测量中 2:结果 3:历史
ExperimentData currentData; // 当前实验数据
// 系统启动时间(用于相对时间戳)
const unsigned long SYSTEM_START_TIME = millis();
// 全局变量
int recordCount = 0; // 记录计数器
ExperimentData lastRecord; // 最后一条记录缓存
void setup() {
Serial.begin(115200);
// 初始化LCD
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SD Card System");
lcd.setCursor(0, 1);
lcd.print("Version 3.0"); // 更新版本号
delay(2000);
// 初始化SD卡
if (!SD.begin(SD_CS)) {
Serial.println("SD卡初始化失败!");
lcd.clear();
lcd.print("SD Card Error");
while (1) {
// 闪烁LED提示错误
digitalWrite(STATUS_LED, HIGH);
delay(200);
digitalWrite(STATUS_LED, LOW);
delay(200);
}
}
Serial.println("SD卡初始化成功");
// 创建数据文件头(如果文件不存在)
if (!SD.exists(FILENAME)) {
File dataFile = SD.open(FILENAME, FILE_WRITE);
if (dataFile) {
dataFile.println("Timestamp(ms),Time1(us),Time2(us),TimeDiff(us),Velocity1(m/s),Velocity2(m/s),Acceleration(m/s2)");
dataFile.close();
Serial.println("创建CSV文件头");
}
}
// 初始化引脚
pinMode(GATE1_PIN, INPUT);
pinMode(GATE2_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(STATUS_LED, OUTPUT);
pinMode(SD_CS, OUTPUT);
// 配置中断
attachInterrupt(digitalPinToInterrupt(GATE1_PIN), gate1ISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(GATE2_PIN), gate2ISR, CHANGE);
// 加载记录计数
recordCount = getRecordCount();
Serial.println("系统就绪");
Serial.println("命令: R-读取数据 | C-清除数据 | E-导出数据 | T-设置时间");
Serial.println("--------------------------------------");
}
void loop() {
// 处理实验完成
if (experimentComplete) {
calculateAndStoreResults();
resetExperiment();
}
// 处理串口命令
if (Serial.available() > 0) {
processSerialCommand();
}
// 更新LCD显示
updateLCD();
// 自动状态切换
static unsigned long lastStateChange = 0;
if (millis() - lastStateChange > 10000 && displayState == 0) {
displayState = 3; // 切换到历史记录
lastStateChange = millis();
} else if (millis() - lastStateChange > 5000 && displayState == 3) {
displayState = 0; // 返回状态显示
lastStateChange = millis();
}
}
// 光电门中断服务程序
void gate1ISR() {
if (digitalRead(GATE1_PIN) == HIGH) {
gate1Start = micros();
gate1Active = true;
digitalWrite(STATUS_LED, HIGH);
displayState = 1;
// 记录实验开始时间戳
currentData.timestamp = millis() - SYSTEM_START_TIME;
} else {
gate1End = micros();
gate1Active = false;
digitalWrite(STATUS_LED, LOW);
// 记录时间1
currentData.time1 = gate1End - gate1Start;
// 计算速度1
currentData.velocity1 = WIDTH / (currentData.time1 / 1000000.0);
}
}
void gate2ISR() {
if (digitalRead(GATE2_PIN) == HIGH) {
gate2Start = micros();
gate2Active = true;
displayState = 1;
} else {
gate2End = micros();
gate2Active = false;
experimentComplete = true;
displayState = 2;
// 记录时间2
currentData.time2 = gate2End - gate2Start;
// 计算速度2
currentData.velocity2 = WIDTH / (currentData.time2 / 1000000.0);
}
}
// 计算并存储结果(增加时间差计算)
void calculateAndStoreResults() {
// 计算两个光电门中间时刻的时间差
unsigned long midTime1 = gate1Start + currentData.time1/2;
unsigned long midTime2 = gate2Start + currentData.time2/2;
currentData.timeDiff = midTime2 - midTime1; // 存储时间差(微秒)
// 计算加速度
float timeInterval = currentData.timeDiff / 1000000.0;
currentData.acceleration = (currentData.velocity2 - currentData.velocity1) / timeInterval;
// 串口打印完整数据(增加时间差输出)
Serial.println("\n===== 实验数据 =====");
Serial.print("时间戳: "); Serial.print(currentData.timestamp); Serial.println(" ms");
Serial.print("时间1: "); Serial.print(currentData.time1); Serial.println(" μs");
Serial.print("时间2: "); Serial.print(currentData.time2); Serial.println(" μs");
Serial.print("时间差: "); Serial.print(currentData.timeDiff); Serial.println(" μs");
Serial.print("速度1: "); Serial.print(currentData.velocity1, 4); Serial.println(" m/s");
Serial.print("速度2: "); Serial.print(currentData.velocity2, 4); Serial.println(" m/s");
Serial.print("加速度: "); Serial.print(currentData.acceleration, 4); Serial.println(" m/s²");
// 蜂鸣器提示
beep(3, 100);
// 存储到SD卡
saveToSD();
// 循环显示3遍,每遍10秒
for (int cycle = 0; cycle < 3; cycle++) {
// 显示结果10秒
unsigned long startTime = millis();
while (millis() - startTime < 10000) { // 10秒显示
showResultsScreen();
// 在第二行显示剩余时间和循环次数
int remaining = 10 - (millis() - startTime) / 1000;
if (remaining < 0) remaining = 0;
lcd.setCursor(12, 1);
if (remaining < 10) lcd.print(" "); // 对齐
lcd.print(remaining);
lcd.print("s ");
lcd.print(cycle+1);
lcd.print("/3");
delay(100);
}
// 短暂返回初始状态(0.5秒)
displayState = 0;
showStatusScreen();
delay(500);
}
// 最终返回初始状态
displayState = 0;
}
// 更新LCD显示
void updateLCD() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 300) return;
lastUpdate = millis();
switch(displayState) {
case 0: showStatusScreen(); break;
case 1: showMeasurementScreen(); break;
case 2: showResultsScreen(); break;
case 3: showHistorySummary(); break;
}
}
// 显示状态屏幕
void showStatusScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ready");
lcd.setCursor(0, 1);
lcd.print("Records:");
lcd.print(recordCount);
}
// 显示测量屏幕
void showMeasurementScreen() {
lcd.clear();
if (gate1Active) {
lcd.setCursor(0, 0);
lcd.print("Door1:");
unsigned long elapsed = micros() - gate1Start;
lcd.print(elapsed);
lcd.print("μs");
lcd.setCursor(0, 1);
lcd.print("V1:");
lcd.print(WIDTH / (elapsed / 1000000.0), 2);
lcd.print("m/s");
}
else if (gate2Active) {
lcd.setCursor(0, 0);
lcd.print("Door2:");
unsigned long elapsed = micros() - gate2Start;
lcd.print(elapsed);
lcd.print("μs");
lcd.setCursor(0, 1);
lcd.print("V2:");
lcd.print(WIDTH / (elapsed / 1000000.0), 2);
lcd.print("m/s");
}
}
// 显示结果屏幕
void showResultsScreen() {
lcd.clear();
// 第一行: 时间戳和加速度
lcd.setCursor(0, 0);
lcd.print("Time:");
lcd.print(currentData.timestamp / 1000); // 转换为秒
lcd.print("s a:");
lcd.print(currentData.acceleration, 2);
// 第二行: 存储状态和时间差
lcd.setCursor(0, 1);
lcd.print("Time diff:");
lcd.print(currentData.timeDiff / 1000.0, 1); // 转换为毫秒显示
lcd.print("ms");
// 在有限空间内显示存储信息
lcd.setCursor(10, 1);
lcd.print("#");
lcd.print(recordCount + 1);
}
// 保存完整数据到SD卡
void saveToSD() {
File dataFile = SD.open(FILENAME, FILE_WRITE);
if (dataFile) {
// 以CSV格式写入数据
dataFile.print(currentData.timestamp); dataFile.print(",");
dataFile.print(currentData.time1); dataFile.print(",");
dataFile.print(currentData.time2); dataFile.print(",");
dataFile.print(currentData.timeDiff); dataFile.print(",");
dataFile.print(currentData.velocity1, 4); dataFile.print(",");
dataFile.print(currentData.velocity2, 4); dataFile.print(",");
dataFile.println(currentData.acceleration, 4);
dataFile.close();
Serial.println("数据已存SD卡");
// 更新记录计数和最后记录
recordCount++;
lastRecord = currentData;
} else {
Serial.println("写入SD卡失败!");
}
}
// 显示历史摘要
void showHistorySummary() {
lcd.clear();
if (recordCount == 0) {
lcd.setCursor(0, 0);
lcd.print("No data");
return;
}
// 第一行: 记录数
lcd.setCursor(0, 0);
lcd.print("Records:");
lcd.print(recordCount);
// 第二行: 最后记录的加速度和时间差
lcd.setCursor(0, 1);
lcd.print("A:");
lcd.print(lastRecord.acceleration, 2);
lcd.print(" Δt:");
lcd.print(lastRecord.timeDiff / 1000.0, 1); // 转换为毫秒显示
lcd.print("ms");
}
// 获取记录数量
int getRecordCount() {
File dataFile = SD.open(FILENAME, FILE_READ);
int count = 0;
if (dataFile) {
// 跳过标题行
dataFile.readStringUntil('\n');
// 计算行数
while (dataFile.available()) {
if (dataFile.read() == '\n') count++;
}
dataFile.close();
}
// 加载最后一条记录
if (count > 0) {
loadLastRecord();
}
return count;
}
// 加载最后一条记录
void loadLastRecord() {
File dataFile = SD.open(FILENAME, FILE_READ);
if (!dataFile) return;
// 定位到文件末尾
dataFile.seek(dataFile.size());
// 向前查找最后一行
int linesToRead = 2; // 最后一行和可能的空行
while (dataFile.position() > 0 && linesToRead > 0) {
dataFile.seek(dataFile.position() - 1);
char c = dataFile.read();
if (c == '\n') {
linesToRead--;
if (linesToRead == 0) break;
}
if (dataFile.position() <= 1) break;
dataFile.seek(dataFile.position() - 2);
}
// 读取最后一行
String lastLine = dataFile.readStringUntil('\n');
parseCSVLine(lastLine, lastRecord);
dataFile.close();
}
// 解析CSV行到数据结构
void parseCSVLine(String line, ExperimentData &data) {
int index = 0;
int lastIndex = 0;
// 时间戳
index = line.indexOf(',', lastIndex);
data.timestamp = line.substring(lastIndex, index).toInt();
lastIndex = index + 1;
// time1
index = line.indexOf(',', lastIndex);
data.time1 = line.substring(lastIndex, index).toInt();
lastIndex = index + 1;
// time2
index = line.indexOf(',', lastIndex);
data.time2 = line.substring(lastIndex, index).toInt();
lastIndex = index + 1;
// timeDiff
index = line.indexOf(',', lastIndex);
data.timeDiff = line.substring(lastIndex, index).toInt();
lastIndex = index + 1;
// velocity1
index = line.indexOf(',', lastIndex);
data.velocity1 = line.substring(lastIndex, index).toFloat();
lastIndex = index + 1;
// velocity2
index = line.indexOf(',', lastIndex);
data.velocity2 = line.substring(lastIndex, index).toFloat();
lastIndex = index + 1;
// acceleration
data.acceleration = line.substring(lastIndex).toFloat();
}
// 导出所有实验数据(增加时间差列)
void exportAllData() {
File dataFile = SD.open(FILENAME, FILE_READ);
if (!dataFile) {
Serial.println("无数据可导出");
return;
}
Serial.println("\n===== 所有实验数据 =====");
// 输出所有数据
while (dataFile.available()) {
Serial.write(dataFile.read());
}
dataFile.close();
Serial.println("===== 导出完成 =====");
}
// 从SD卡读取数据(增加时间差显示)
void readFromSD() {
File dataFile = SD.open(FILENAME, FILE_READ);
if (!dataFile) {
Serial.println("SD卡中无数据");
lcd.clear();
lcd.print("No Data");
delay(2000);
return;
}
Serial.println("\n===== 历史数据 =====");
// 跳过标题行
dataFile.readStringUntil('\n');
int recordNum = 0;
while (dataFile.available()) {
String line = dataFile.readStringUntil('\n');
if (line.length() == 0) continue;
recordNum++;
ExperimentData data;
parseCSVLine(line, data);
// 串口输出(增加时间差)
Serial.print("记录 #");
Serial.print(recordNum);
Serial.print(" 时间戳: ");
Serial.print(data.timestamp);
Serial.println(" ms");
Serial.print("时间1: "); Serial.print(data.time1); Serial.println(" μs");
Serial.print("时间2: "); Serial.print(data.time2); Serial.println(" μs");
Serial.print("时间差: "); Serial.print(data.timeDiff); Serial.println(" μs");
Serial.print("速度1: "); Serial.print(data.velocity1, 4); Serial.println(" m/s");
Serial.print("速度2: "); Serial.print(data.velocity2, 4); Serial.println(" m/s");
Serial.print("加速度: "); Serial.print(data.acceleration, 4); Serial.println(" m/s²");
Serial.println("----------------------");
// LCD显示当前记录摘要(增加时间差)
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Record ");
lcd.print(recordNum);
lcd.setCursor(0, 1);
lcd.print("Δt:");
lcd.print(data.timeDiff / 1000.0, 1); // 转换为毫秒显示
lcd.print("ms A:");
lcd.print(data.acceleration, 2);
delay(10000); // 每条记录显示10秒
}
Serial.println("--------------------------------------");
dataFile.close();
displayState = 0;
beep(1, 100);
}
// 清除SD卡数据
void clearSD() {
if (SD.remove(FILENAME)) {
// 重新创建文件头
File dataFile = SD.open(FILENAME, FILE_WRITE);
if (dataFile) {
dataFile.println("Timestamp(ms),Time1(us),Time2(us),TimeDiff(us),Velocity1(m/s),Velocity2(m/s),Acceleration(m/s2)");
dataFile.close();
}
recordCount = 0;
Serial.println("所有数据已清除");
lcd.clear();
lcd.print("Data cleared");
delay(1000);
} else {
Serial.println("清除数据失败");
}
beep(2, 50);
displayState = 0;
}
// 处理串口命令
void processSerialCommand() {
char command = Serial.read();
switch(command) {
case 'R':
case 'r':
readFromSD(); // 改为从SD卡读取
break;
case 'C':
case 'c':
clearSD(); // 改为清除SD卡数据
break;
case 'E':
case 'e':
exportAllData(); // 导出所有数据
break;
case 'T': // 设置时间偏移
if (Serial.available() >= 4) {
unsigned long timeOffset;
Serial.readBytes((char*)&timeOffset, sizeof(timeOffset));
Serial.println("时间戳调整功能在SD卡版本中不可用");
}
break;
default:
Serial.println("未知命令");
Serial.println("可用命令: R-读取, C-清除, E-导出, T-设置时间");
}
}
// 蜂鸣器控制(音量提高)
void beep(int times, int duration) {
for (int i = 0; i < times; i++) {
// 使用高频音调(3000Hz)提高音量
tone(BUZZER_PIN, 3000); // 持续发声
delay(duration); // 持续时间
noTone(BUZZER_PIN); // 停止发声
if (i < times - 1) delay(duration); // 间隔
}
}
// 重置实验状态
void resetExperiment() {
gate1Active = false;
gate2Active = false;
experimentComplete = false;
gate1Start = 0;
gate1End = 0;
gate2Start = 0;
gate2End = 0;
displayState = 0;
// 重置当前数据结构
memset(¤tData, 0, sizeof(currentData));
}
此代码报错为Sketch uses 25076 bytes (77%) of program storage space. Maximum is 32256 bytes.
Global variables use 2105 bytes (102%) of dynamic memory, leaving -57 bytes for local variables. Maximum is 2048 bytes.
Not enough memory; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing your footprint.
data section exceeds available space in board
Compilation error: data section exceeds available space in board请你修改
最新发布