#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include <SD.h>
#include <Encoder.h>
#include <RTClib.h>
// Screen dimensions
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
// Pin definitions for the TFT display and others
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC 8
#define SD_CS 53
#define BUTTON_PIN 4
#define INJECTOR_CONTROL_PIN 7
#define FLOW_SENSOR_PIN 5
#define PRESSURE_SENSOR_PIN 6
#define CURRENT_SENSOR_PIN A0
#define VOLTAGE_SENSOR_PIN A1
struct TestResults {
float averagePressure;
float averageVoltage;
float deadTime;
float flowRate;
};
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
Encoder knob(2, 3); // Pins for the rotary encoder
RTC_DS3231 rtc;
// Adjust the function signature to take a TestResults object
void logResultsToSDCard(const TestResults& results) {
File dataFile = SD.open("TestResults.txt", FILE_WRITE);
// Add headers if the file is new
if (dataFile.size() == 0) {
dataFile.println("Timestamp, Pressure (psi), Voltage (V)");
}
if (dataFile) {
unsigned long timestamp = millis(); // Get the current time in milliseconds
// Log the formatted results
dataFile.println(String(timestamp) + ", " + String(results.averagePressure, 2) + ", " + String(results.averageVoltage, 2));
dataFile.close();
} else {
Serial.println("Error opening the test results file.");
}
}
// Function prototypes
void displaySplashScreen();
void showMainMenu();
int selectMenuOption();
void displayMenuHighlight(int option);
void executeMenuOption(int option);
void performInjectorTest();
void showTestResultsOnDisplay(const TestResults& results);
String createFilename();
void logDataToSDCard(float pressure);
float calculateDeadTime();
float calculateFlowRate();
void showTestResultsOnDisplay(const TestResults& results);
// Calculation functions
float calculateDeadTime() {
// Placeholder for dead time calculation logic
return 1.5; // Example dead time in milliseconds
}
float calculateFlowRate() {
// Placeholder for flow rate calculation logic
return 250.0; // Example flow rate in ml/min
}
void setup() {
Serial.begin(9600);
pinMode(INJECTOR_CONTROL_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
tft.begin();
tft.setRotation(0);
if (!SD.begin(SD_CS)) {
tft.setTextColor(ILI9341_RED);
tft.setCursor(0, 0);
tft.setTextSize(2);
tft.println("SD Card Error!");
while (1); // Halt if SD fails
}
if (!rtc.begin()) {
tft.setTextColor(ILI9341_RED);
tft.setCursor(0, 20);
tft.setTextSize(2);
tft.println("RTC Error!");
while (1); // Halt if RTC fails
}
displaySplashScreen();
delay(2000);
showMainMenu();
}
void loop() {
int selectedOption = selectMenuOption();
if (selectedOption > 0) {
executeMenuOption(selectedOption);
}
delay(100); // Loop delay to prevent bouncing and excessive CPU use
}
void displaySplashScreen() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 60);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println("iMOB Racing");
tft.println("Injector Tester");
}
void viewFileContents(const char* filename) {
File file = SD.open(filename);
if (!file) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.println("Failed to open file");
delay(2000);
return;
}
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 10);
tft.setTextSize(1);
while (file.available()) {
String line = file.readStringUntil('\n');
tft.println(line);
if (tft.getCursorY() > SCREEN_HEIGHT - 10) { // Scroll or wait for user input if end of screen is reached
tft.println("Press button to continue...");
while (digitalRead(BUTTON_PIN) == HIGH); // Wait for button press
delay(200);
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 10);
}
}
file.close();
tft.println("End of file. Press button to return.");
while (digitalRead(BUTTON_PIN) == HIGH); // Wait for button press
delay(200); // Debouncing delay
showMainMenu(); // Return to main menu
}
void viewResults() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.println("Select a file:");
File root = SD.open("/");
File file = root.openNextFile();
int fileCount = 0;
int selectedFileIndex = 0;
bool refreshDisplay = true;
char filenames[10][13]; // Array to store filenames (max 10 files for simplicity)
while (file && fileCount < 10) {
if (!file.isDirectory() && file.name()[0] != '.') {
strncpy(filenames[fileCount], file.name(), 12);
filenames[fileCount][12] = '\0'; // Ensure null termination
fileCount++;
}
file = root.openNextFile();
}
int lastPosition = -1; // Variable to store the last position of the rotary encoder
while (true) {
int newPosition = knob.read() / 4; // Read the rotary encoder
if (newPosition != lastPosition) {
selectedFileIndex = newPosition % fileCount;
refreshDisplay = true;
}
if (refreshDisplay) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
for (int i = 0; i < fileCount; i++) {
if (i == selectedFileIndex) {
tft.setTextColor(ILI9341_YELLOW); // Highlight selected file
} else {
tft.setTextColor(ILI9341_WHITE);
}
tft.println(filenames[i]);
}
refreshDisplay = false;
}
if (digitalRead(BUTTON_PIN) == LOW) { // Check if the button is pressed
delay(200); // Debounce delay
while (digitalRead(BUTTON_PIN) == LOW); // Wait for button release
break; // Exit the loop to view the selected file
}
lastPosition = newPosition;
}
viewFileContents(filenames[selectedFileIndex]);
}
void showMainMenu() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.println("Main Menu");
tft.setTextSize(1);
tft.setCursor(10, 50);
tft.println("1. Start Test");
tft.println("2. Settings");
tft.println("3. Option 3");
}
int selectMenuOption() {
static long oldPosition = -999;
long newPosition = knob.read()/4;
if (newPosition != oldPosition) {
oldPosition = newPosition;
displayMenuHighlight(newPosition % 3);
}
if (digitalRead(BUTTON_PIN) == LOW) { // Active LOW for button press
delay(200); // Debounce delay
while (digitalRead(BUTTON_PIN) == LOW); // Wait for button release
return oldPosition % 3 + 1;
}
return 0; // No option selected
}
void displayMenuHighlight(int option) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.println("Main Menu");
tft.setTextSize(1);
for (int i = 0; i < 3; i++) {
tft.setCursor(10, 50 + 20 * i);
if (i == option) {
tft.setTextColor(ILI9341_YELLOW); // Highlight selected option
} else {
tft.setTextColor(ILI9341_WHITE);
}
tft.println(i == 0 ? "1. Start Test" : (i == 1 ? "2. Option 2" : "3. Option 3"));
}
}
void executeMenuOption(int option) {
switch(option) {
case 1:
performInjectorTest();
break;
case 2:
viewResults();
break;
case 3:
// Placeholder for future functionality
break;
}
}
void performInjectorTest() {
unsigned long testStartTime = millis();
unsigned long lastInjectionTime = 0;
const int pulseWidth = 10; // in milliseconds
const int cycleDuration = 1000; // in milliseconds
startDataCollection();
int readingCount = 0;
float totalPressure = 0.0, totalVoltage = 0.0;
File logFile = SD.open(createFilename(), FILE_WRITE);
if (!logFile) {
Serial.println("Failed to open log file for writing");
return;
}
// Perform the test
while (shouldContinueTest()) {
activateInjector();
recordData(); // This function should store data in arrays or similar
updateDisplay();
}
while (millis() - testStartTime < 30000) { // 30 seconds test
if (millis() - lastInjectionTime >= cycleDuration) {
digitalWrite(INJECTOR_CONTROL_PIN, HIGH);
delay(pulseWidth);
digitalWrite(INJECTOR_CONTROL_PIN, LOW);
float currentPressure = analogRead(PRESSURE_SENSOR_PIN) * (5.0 / 1023.0) * 100; // Example conversion
float currentVoltage = analogRead(VOLTAGE_SENSOR_PIN) * (5.0 / 1023.0); // Example conversion
totalPressure += currentPressure;
totalVoltage += currentVoltage;
readingCount++;
logFile.println(String(millis() - testStartTime) + "," + String(currentPressure) + "," + String(currentVoltage));
lastInjectionTime = millis();
}
}
TestResults results;
results.deadTime = calculateDeadTime();
results.flowRate = calculateFlowRate();
results.averagePressure = calculateAveragePressure();
results.averageVoltage = calculateAverageVoltage();
logFile.println("Average Pressure: " + String(results.averagePressure) + " psi");
logFile.println("Average Voltage: " + String(results.averageVoltage) + " V");
logFile.println("Dead Time: " + String(results.deadTime) + " ms");
logFile.println("Flow Rate: " + String(results.flowRate) + " ml/min");
logResultsToSDCard(results);
logFile.close();
showTestResultsOnDisplay(results);
// Calculate results after the test is done
TestResults results;
results.averagePressure = calculateAveragePressure();
results.averageVoltage = calculateAverageVoltage();
logResultsToSDCard(results);
displayResults(results);
}
void showTestResultsOnDisplay(const TestResults& results) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.println("Test Complete");
tft.setTextSize(1);
tft.setCursor(10, 40);
tft.println("Avg Pressure: " + String(results.averagePressure, 2) + " psi");
tft.println("Avg Voltage: " + String(results.averageVoltage, 2) + " V");
tft.println("Dead Time: " + String(results.deadTime, 2) + " ms");
tft.println("Flow Rate: " + String(results.flowRate, 2) + " cc/min"); // Display in cc/min
}
String createFilename() {
DateTime now = rtc.now();
// Create a filename in the format "MMDDHHMM.TXT"
char filename[13]; // Array to hold the filename plus null terminator
sprintf(filename, "%02d%02d%02d%02d.TXT", now.month(), now.day(), now.hour(), now.minute());
return String(filename);
}
void logDataToSDCard(float pressure) {
String filename = createFilename();
File dataFile = SD.open(filename.c_str(), FILE_WRITE);
if (dataFile) {
dataFile.print("Pressure: ");
dataFile.println(pressure);
dataFile.close();
} else {
Serial.println("Error opening " + filename);
}
}
void activateInjector() {
digitalWrite(INJECTOR_CONTROL_PIN, HIGH); // Turn on injector
delay(10); // Assume injector needs to be open for 10 ms - adjust based on your needs
digitalWrite(INJECTOR_CONTROL_PIN, LOW); // Turn off injector
}
struct SensorData {
float pressure;
float voltage;
};
// Global variable to store sensor data
SensorData sensorData;
void recordData() {
sensorData.pressure = analogRead(PRESSURE_SENSOR_PIN) * (5.0 / 1023.0) * 100; // Example conversion to pressure
sensorData.voltage = analogRead(VOLTAGE_SENSOR_PIN) * (5.0 / 1023.0); // Convert ADC reading to voltage
// Log this data to SD card or prepare it for display
}
void updateDisplay() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print("Pressure: ");
tft.print(sensorData.pressure, 2);
tft.println(" psi");
tft.setCursor(10, 30);
tft.print("Voltage: ");
tft.print(sensorData.voltage, 2);
tft.println(" V");
};
TestResults results;
void calculateResults() {
// For simplicity, assume we collect a fixed number of samples
static const int numSamples = 30; // Number of data samples
float totalPressure = 0;
float totalVoltage = 0;
// Example loop to simulate data accumulation
for (int i = 0; i < numSamples; i++) {
totalPressure += sensorData.pressure;
totalVoltage += sensorData.voltage;
}
results.averagePressure = totalPressure / numSamples;
results.averageVoltage = totalVoltage / numSamples;
}
void displayResults() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.println("Results:");
tft.setTextSize(1);
tft.setCursor(10, 40);
tft.print("Avg Pressure: ");
tft.print(results.averagePressure, 2);
tft.println(" psi");
tft.setCursor(10, 60);
tft.print("Avg Voltage: ");
tft.print(results.averageVoltage, 2);
tft.println(" V");
// Wait for a button press to return to the main menu
while (digitalRead(BUTTON_PIN) == HIGH);
delay(200); // Debouncing delay
showMainMenu();
}
float calculateAveragePressure() {
// Assuming you have a way to store multiple pressure readings in an array or similar
float totalPressure = 0.0;
int count = 0; // number of readings
// Example loop to simulate adding up all pressure readings
for (int i = 0; i < count; i++) {
totalPressure += pressureReadings[i]; // Assume pressureReadings is an array of float
}
if (count > 0) {
return totalPressure / count; // Return the average
} else {
return 0; // No readings to average
}
}
float calculateAverageVoltage() {
float totalVoltage = 0.0;
int count = 0; // number of readings
// Similar loop for voltage readings
for (int i = 0; i < count; i++) {
totalVoltage += voltageReadings[i]; // Assume voltageReadings is an array of float
}
if (count > 0) {
return totalVoltage / count; // Return the average
} else {
return 0; // No readings to average
}
}