Hi there,
I'm making a Dew Controller. I'm kinda new to this, and ChatGPT has helped me do things i never could have otherwise, so I'm in a weird position of trying to implement code that compiles, but I don't fully understand.
Anyway, the idea is that the Dew Controller compares Ambient Temperature and Humidity to the temperature of my telescope mirror. It heats it up via PWM to keep it just above the calculated dewpoint.
The problem is the PID controls just don't seem to have any real effect. The integral saturates at -75 every time within a few iterations, and the mirror is kept a constant 1-1.5 degrees above the target temperature (like an offset). As a system, it's slow in that sampling is once per minute, and doubly slow in that there is a lag when heat is applied, to when the sensor detects it. As i said, I'm pretty new, so maybe me and Chat GPT just arent aware of some big issue.
The relevant variables are below, followed by the code.
Thanks for any help, I'm really pulling my hair out here!
Markus
Kp = 6;
Ki = 0.0001;
Kd = 0.8;
maxRampRate = 1;
decayFactor = 0.85;
Deadband = -+0.5 deg.
Code Below. And I'll include some test data below that
//Libraries
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <algorithm> // Include the algorithm library for max_element and min_element
#include <cfloat> // For FLT_MAX and FLT_MIN
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64,0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Define to which pin of the Arduino the 1-Wire bus is connected:
#define ONE_WIRE_BUS 8
#define DHTPIN 2 // what pin we're connected to
#define DHTTYPE DHT22 // DHT 22 (AM2302)
// Create a new instance of the oneWire class to communicate with any OneWire device:
OneWire oneWire(ONE_WIRE_BUS);
// Pass the oneWire reference to DallasTemperature library:
DallasTemperature sensors(&oneWire);
//Constants
DHT dht(DHTPIN, DHTTYPE); //// Initialize DHT sensor for normal 16mhz Arduino
// const int mtemp_pin = 2;
// const int atemp_pin = 8;
const int dewPWM = 5; /*does the program actually output PWM to the pin, or just calculate it while being useless?*/
const int mtemp_pin = 8;
const int atemp_pin = 2;
//onewirebus is pin 8 as defined elsewhere
//Variables
float hum = 0; //Stores humidity value
float atemp = 0; //Stores temperature value
float dewpoint = 0; //stores dew point value
float mtemp = 0; //current mirror temp
int power = 0; //power on scale of 0-255
int level = 2; //How many degrees above dew point the target is going to be
int brightness = 50; //Display backlight Brightness - no effect?
float powerCent = 0; //Power in percent
int delayAmount = 5000;
const int chipSelect = 10; //for OLED
int fileIndex = 0; //counter for generating new SD files
float target = 0;
const int marginLeft = 16; // 8-pixel margin on the left Space for Graph = 128-16 = 112
const int marginBottom = 3; // 8-pixel margin at the bottom Space left for graph = 64-0 = 64
const int graphWidth = SCREEN_WIDTH - marginLeft; // Width of the graph area
const int graphHeight = SCREEN_HEIGHT - marginBottom; // Height of the graph area
const int xAxisY = 61; // X-axis line moved to pixel row 61
const int numPoints = graphWidth; // Number of points in the graph
const int numDataPoints = graphWidth; // Number of data points to plot
float values[numDataPoints]; // Array to hold data points for plotting
int totalLines = 0;
float minValues[5]; // For ATemp, MTemp, Humidity, Dewpoint, Power
float maxValues[5]; // For ATemp, MTemp, Humidity, Dewpoint, Power
float minValue = 0.0; // Global variable for the current minimum value
float maxValue = 0.0; // Global variable for the current maximum value
// PID control constants and variables
float Kp = 6; // (was 5) responds to the error difference. Higher = more reactive
float Ki = 0.0001; // (was 0.0002)Integral constant, eliminate persistent offset. Higher = more reactive. Smaller values minimise overshooting
float Kd = 0.8; // (was 0.8) Predicts rate of change. Lower values mean slower changes
float integral = 0.0;
float previousError = 0.0;
unsigned long lastUpdateTime = 0; // For time delta calculations
float maxRampRate = 1; // (was 1.5) Limits the rate at which power (or the control variable) can change.
float error = 0;
float decayFactor = 0.85; // (was 0.85) Decay the integral by each iteration. Prevent windup and smooth control within a deadband.
const char Filename[]= "DEWLOG00.txt"; //The base name of the file before enumeration
String currentFileName = "DEWLOG00.txt";
float minATemp, maxATemp, minMTemp, maxMTemp, minHum, maxHum, minDewpoint, maxDewpoint, minPowerCent, maxPowerCent;
File myFile;//
// Function prototypes
void displayData();
void plotGraph();
void displayVariable(float value, int decimalPlaces);
void displayMinMax();
void calculateAllMinMaxFromSD();
float calculateDewPoint();
void loadGraphDataFromSD(int commaIndex);
// sD TROUBLESHOTING Function prototypes
void testSPI();
void testSDCard();
void listFiles();
void printDirectory(File dir, int numTabs);
void setup() //****************************************************************************************
{
Serial.begin(9600);
// Rest of your setup code...
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.dim(true);//test dimming display
display.display();
delay(1000); // Pause for 2 seconds
// Clear the buffer
/* ****************************Splash Screen****************/
display.clearDisplay();
display.setTextSize(3); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println("Markus");
display.println("Stone");
display.setTextSize(1);
display.println(" ");
display.println("Dew Controller 2.5");
display.display();
delay(500);
lastUpdateTime = millis();
//Initialise Temp Sensors
dht.begin();
sensors.begin();
// Send the command for all devices on the bus to perform a temperature conversion:
sensors.requestTemperatures(); // Request temperature readings
Serial.print("Number of devices found on OneWire bus: ");
Serial.println(sensors.getDeviceCount());
DeviceAddress tempSensorAddress;
if (sensors.getAddress(tempSensorAddress, 0)) {
Serial.print("Sensor 0 address: ");
for (int i = 0; i < 8; i++) {
if (tempSensorAddress[i] < 16) Serial.print("0"); // Leading zero padding
Serial.print(tempSensorAddress[i], HEX);
}
Serial.println();
} else {
Serial.println("No sensor found at index 0.");
}
pinMode(mtemp_pin, INPUT);
pinMode(atemp_pin, INPUT);
pinMode(dewPWM, OUTPUT);
//Initialise SD Card
// Initialize SD Card
Serial.print("Initializing SD card...");
if (!SD.begin(chipSelect)) {
while (!Serial) ; // Wait for Serial Monitor to connect (for Leonardo/Micro)
// Serial.println("Starting SD card debug...");
// // Test if the SD card module responds to SPI signals
// testSPI();
// // Test SD card initialization
// testSDCard();
Serial.println("Card failed, or not present");
// Display error message on OLED
display.clearDisplay();
display.setTextSize(2); // Large font size
display.setTextColor(SSD1306_WHITE); // White text
display.setCursor(0, 10); // Position on screen
display.println("SD Error!");
display.setTextSize(1); // Smaller text for details
display.setCursor(0, 35);
display.println("Insert SD card");
display.println("and reboot");
display.display();
// Halt further execution
while (true) {
delay(1000); // Infinite loop with delay
}
}
Serial.println("Card initialized.");
myFile = SD.open("DEWLOG00.txt", FILE_WRITE);
myFile.close();
loadPIDStateFromFile(integral, previousError);
}
void loop() //**************************************************************************************** Void Loop
{
// serial commands monitor
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
command.trim(); // Remove any leading/trailing whitespace
if (command.equalsIgnoreCase("C")) {
clearFile("DEWLOG00.txt");
} else if (command.equalsIgnoreCase("R")) {
readFile("DEWLOG00.txt");
}
else if (command.equalsIgnoreCase("I")) {
clearFile("DEWLOG00.txt");
integral = 0.0; // Reset the integral term
Serial.println("File Cleared. Integral reset to 0.");
return; // Skip the rest of the loop for this iteration
}
else {
Serial.println("Unknown command.");
}
}
atemp = dht.readTemperature(); //ambient Temperature
hum = dht.readHumidity();
dewpoint = (calculateDewPoint());
target=level+dewpoint;
// Update Mirror Temperature
sensors.requestTemperatures(); // Request new temperature readings
float rawMTemp = sensors.getTempCByIndex(0);
if (rawMTemp == DEVICE_DISCONNECTED_C || rawMTemp < -50 || rawMTemp > 150) {
Serial.println("Error: Invalid Mirror Temperature Reading!");
return; // Skip this iteration if MTemp is invalid
}
mtemp = rawMTemp - 10; // Apply calibration offset
// Calculate error and target
error = target - mtemp;
unsigned long currentTime = millis();
float deltaTime = (currentTime - lastUpdateTime) / 1000.0; // Time in seconds
lastUpdateTime = currentTime;
// Check if error is within the deadband
if (abs(error) <= 0.5) {
integral *= decayFactor; // Decay integral within deadband
power = constrain(power - 1, 0, 255); // Gradual ramp-down
} else {
// PID Calculations
if (abs(error) > 0.5) { // Deadband for small errors
if (power < 255 && power > 0) { // Prevent windup when output is saturated
integral += error * deltaTime;
} else {
integral *= decayFactor; // Decay integral when output is saturated
}
integral = constrain(integral, -75, 75); // Constrain integral within limits
}
// Derivative calculation
float derivative = (deltaTime > 0) ? (error - previousError) / deltaTime : 0.0;
previousError = error;
// PID output calculation
float pidOutput = Kp * error + Ki * integral + Kd * derivative;
// Apply sigmoid for smoother control
float sigmoidPower = 255.0 / (1.0 + exp(-pidOutput * 0.1));
power += constrain(sigmoidPower - power, -maxRampRate * deltaTime, maxRampRate * deltaTime);
power = constrain(power, 0, 255); // Constrain power within bounds
}
// Apply calculated power to the heating element
analogWrite(dewPWM, power);
powerCent = map(power, 0, 255, 0, 100); // Map power for display
values[fileIndex % numDataPoints] = atemp; // Replace 'atemp' with the desired variable
fileIndex++;
// **************************determine appropriate power output
//*****************************Max Min Test
if (mtemp > maxMTemp) {
maxMTemp = mtemp;
} else if (mtemp < minMTemp) {
minMTemp = mtemp;
}
if (atemp > maxATemp) {
maxATemp = atemp;
} else if (atemp < minATemp) {
minATemp = atemp;
}
if (hum > maxHum) {
maxHum = hum;
} else if (hum < minHum) {
minHum = hum;
}
if (powerCent > maxPowerCent) {
maxPowerCent = powerCent;
} else if (powerCent < minPowerCent) {
minPowerCent = powerCent;
}
if (dewpoint > maxDewpoint) {
maxDewpoint = dewpoint;
} else if (dewpoint < minDewpoint) {
minDewpoint = dewpoint;
}
//make a string for assembling the data to log:
String dataString = String(atemp);
dataString += ", ";
dataString += mtemp;
dataString += ", ";
dataString += hum;
dataString += ", ";
dataString += dewpoint;
dataString += ", ";
dataString += powerCent;
dataString += ", ";
dataString += power;
dataString += ", ";
dataString += error;
dataString += ", ";
dataString += integral;
dataString += ", ";
dataString += previousError;
Serial.println(dataString);
File myFile = SD.open(currentFileName, FILE_WRITE);
if (myFile) {
myFile.println(dataString);
myFile.flush();
myFile.close();
// 3. Calculate min/max values once
calculateAllMinMaxFromSD();
// Serial.println("File Closed");
// Serial.println(dataString);
// Serial.println("Data written to file and serial monitor.");
} else {
Serial.println("Error opening file for writing.");
}
//*******************begin OLED display
displayMinMax();
//Serial.println("MinMax Display complete, now for plotGraphs");
//-----------------------Display ATemp Graph
display.clearDisplay();
plotGraph(0);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(95,0); // Start at top-left corner
display.println("ATemp");
display.display();
delay(delayAmount);
//----------------------Display Mirror Graph
display.clearDisplay();
plotGraph(1);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(95,0); // Start at top-left corner
display.println("MTemp");
display.display();
delay(delayAmount);
//-----------------------Display Hum Graph
display.clearDisplay();
plotGraph(2);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(100,0); // Start at top-left corner
display.println("Hum");
display.display();
delay(delayAmount);
//Graph Heading
//----------------------Display Dew Point Graph
display.clearDisplay();
plotGraph(3);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(100,0); // Start at top-left corner
display.println("Dew");
display.display();
delay(delayAmount);
//----------------------Display PowerCent Graph
display.clearDisplay();
plotGraph(4);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(100,0); // Start at top-left corner
display.println("Pwr%");
display.display();
delay(delayAmount);
//----------------------Display Error Graph
display.clearDisplay();
plotGraph(6);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(100,0); // Start at top-left corner
display.println("Err");
display.display();
delay(delayAmount);
Serial.print("Total lines in file: ");
Serial.println(totalLines);
}
/* **************************************************Loop End ********************************************/
/* **************************************************Functions Defined********************************************/
//************************************graph function
// Update displayMinMax to use values from SD card
void displayMinMax() {
// Display min/max for ATemp
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0,0); // Start at top-left corner
display.println("Ambient");
display.println("");
display.setTextSize(3);
char buffer[7]; // Create a buffer to hold the formatted string
sprintf(buffer, "%.1f", atemp); // Format error with 1 decimal place and append "°C"
display.print(buffer); // Send the formatted string to the display
display.cp437(true);
display.write(248);
display.println("C");
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[0]); // Cached min for ATemp
display.print(" Hi = ");
display.println(maxValues[0]); // Cached max for ATemp
display.display();
delay(delayAmount);
// Display min/max for MTemp
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0,0); // Start at top-left corner
if (mtemp==-127){
display.println(" Mirror");
display.setTextSize(1);
display.println("");
display.setTextSize(2);
display.println("** not **");
display.println("attached!!");
}
else
{
display.println("Mirror");
display.println("");
display.setTextSize(1);
display.setTextSize(3);
sprintf(buffer, "%.1f", mtemp); // Format error with 1 decimal place and append "°C"
display.print(buffer); // Send the formatted string to the display
display.cp437(true);
display.write(248);
display.println("C");
display.setTextSize(1); //minmax display
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[1]); // Cached min for MTemp
display.print(" Hi = ");
display.println(maxValues[1]); // Cached max for MTemp
}
display.display();
delay(delayAmount);
// Display min/max for Humidity
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Humidity");
display.println("");
display.setTextSize(3);
displayVariable(hum, 1);
display.println("%");
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[2]); // Cached min for Hum
display.print(" Hi = ");
display.println(maxValues[2]); // Cached max for Hum
display.display();
delay(delayAmount);
// Display min/max for Dew Point
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Dew Point");
display.println("");
display.setTextSize(3);
sprintf(buffer, "%.1f", dewpoint); // Format error with 1 decimal place and append "°C"
display.print(buffer); // Send the formatted string to the display
display.cp437(true);
display.write(248);
display.println("C");
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[3]); // Cached min for Dew
display.print(" Hi = ");
display.println(maxValues[3]); // Cached max for Dew
display.display();
delay(delayAmount);
// Display min/max for PowerCent
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Power");
display.println("");
display.setTextSize(3);
displayVariable(powerCent, 1);
display.println("%");
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[4]); // Cached min for Pwr
display.print(" Hi = ");
display.println(maxValues[4]); // Cached max for Pwr
display.display();
delay(delayAmount);
// Display min/max for Error
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Error");
display.println("");
display.setTextSize(3);
sprintf(buffer, "%.1f", error); // Format error with 1 decimal place and append "°C"
display.print(buffer); // Send the formatted string to the display
display.cp437(true);
display.write(248);
display.println("C");
display.setTextSize(1);
display.print("Lo = ");
display.print(minValues[4]); // Cached min for Pwr
display.print(" Hi = ");
display.println(maxValues[4]); // Cached max for Pwr
display.display();
delay(delayAmount);
}
void calculateAllMinMaxFromSD() {
File dataFile = SD.open("DEWLOG00.txt", FILE_READ);
if (!dataFile) {
Serial.println("Error opening data file for Min/Max calculation.");
return;
}
for (int i = 0; i < 5; i++) {
minValues[i] = FLT_MAX;
maxValues[i] = FLT_MIN;
}
while (dataFile.available()) {
String line = dataFile.readStringUntil('\n');
int commaIndex = 0;
for (int i = 0; i < 5; i++) {
int startIndex = commaIndex;
commaIndex = line.indexOf(',', commaIndex);
if (commaIndex == -1) commaIndex = line.length();
String valueString = line.substring(startIndex, commaIndex);
float value = valueString.toFloat();
if (!isnan(value)) {
if (value < minValues[i]) minValues[i] = value;
if (value > maxValues[i]) maxValues[i] = value;
}
commaIndex++;
}
}
dataFile.close();
}
void plotGraph(int commaIndex) {
minValue = minValues[commaIndex];
maxValue = maxValues[commaIndex];
loadGraphDataFromSD(commaIndex); // Load data points from the SD card
// Ensure the Y-axis range is at least 1 degree
if (maxValue - minValue < 1.0) {
float midPoint = (maxValue + minValue) / 2.0;
minValue = midPoint - 0.5;
maxValue = midPoint + 0.5;
}
display.clearDisplay();
int labelWidth = 12; // Y-axis labels width
int adjustedGraphWidth = SCREEN_WIDTH - labelWidth;
// Draw Y-axis
display.drawLine(labelWidth, 0, labelWidth, graphHeight, SSD1306_WHITE);
// Draw Y-axis labels (integer, max of 5)
int numLabels = 5;
int labelStep = ceil((maxValue - minValue) / (numLabels - 1)); // Step between labels
// Ensure labels are integers
int roundedMinValue = floor(minValue);
for (int i = 0; i < numLabels; i++) {
int labelValue = roundedMinValue + i * labelStep; // Calculate label value
int y = map(labelValue, minValue, maxValue, graphHeight - 1, 0); // Map label to Y coordinate
// Draw label text
display.setTextSize(1);
display.setCursor(0, y - 4); // Adjust vertical position for centering text
display.print(labelValue); // Print integer value
}
int prevY = -1; // Previous Y-coordinate for connecting lines
for (int i = 0; i < fileIndex; i++) {
// Map the X coordinate directly to pixels
int x = labelWidth + i; // Each point gets one pixel horizontally
if (x >= SCREEN_WIDTH) break; // Prevent drawing beyond the screen width
// Map the Y coordinate based on the value range
int y = map(values[i] * 100, minValue * 100, maxValue * 100, graphHeight - 1, 0);
// Draw a line to the previous point if it exists
if (prevY != -1) {
display.drawLine(x - 1, prevY, x, y, SSD1306_WHITE);
}
prevY = y; // Update the previous Y-coordinate
}
// Draw X-axis
display.drawLine(labelWidth, graphHeight - 1, SCREEN_WIDTH - 1, graphHeight - 1, SSD1306_WHITE);
drawTicks(fileIndex, delayAmount, SCREEN_WIDTH);
// Refresh display
display.display();
}
void drawTicks(int totalLines, int samplingDelay, int screenWidth) {
int labelWidth = 12; // Y-axis labels width
int graphWidth = screenWidth - labelWidth; // Adjust for graph width
if (totalLines < 1 || graphWidth <= 0) return; // Avoid divide-by-zero or invalid states
// Total time span in seconds
float totalTimeSeconds = (samplingDelay / 1000.0) * totalLines;
// Total time span in hours
float totalTimeHours = totalTimeSeconds / 3600.0;
// Calculate time per pixel in hours
float timePerPixel = totalTimeHours / graphWidth;
// Determine pixels per tick for 1-hour intervals
int pixelsPerHourTick = max(1, (int)(1.0 / timePerPixel)); // Ensure at least 1 pixel per tick
// Draw ticks across the X-axis
for (int x = labelWidth; x < screenWidth; x += pixelsPerHourTick) {
int tickYStart = graphHeight - 1; // Use the global graphHeight
int tickLength = 5; // Length of tick marks
// Draw the tick
display.drawPixel(x, tickYStart, SSD1306_WHITE); // Bottom pixel of the tick
display.drawPixel(x, tickYStart - tickLength, SSD1306_WHITE); // Extend the tick upwards
}
}
void displayVariable(float value, int decimalPlaces) {
int integerPart = (int)value;
int fractionalPart = (int)((value - integerPart) * pow(10, decimalPlaces));
display.print(integerPart);
display.print(".");
// Pad fractional part with leading zeros if necessary
if (fractionalPart < 10 && decimalPlaces > 1) {
display.print("0");
}
display.print(fractionalPart);
}
float calculateDewPoint() {
const float a = 17.27;
const float b = 237.7;
float alpha = (a * atemp) / (b + atemp) + log(hum / 100.0);
return (b * alpha) / (a - alpha);
}
void loadGraphDataFromSD(int commaIndex) {
File dataFile = SD.open("DEWLOG00.txt", FILE_READ);
if (!dataFile) {
Serial.println("Error opening data file for graph.");
return;
}
int totalValidLines = 0;
int index = 0;
// First pass: Count total lines in the file
while (dataFile.available()) {
String line = dataFile.readStringUntil('\n');
if (line.length() > 0) {
totalValidLines++;
}
}
// Calculate the skip factor for subsampling
int skipFactor = 1;
if (totalValidLines > numDataPoints) {
skipFactor = totalValidLines / numDataPoints;
}
// Reset file for second pass to read and subsample
dataFile.seek(0);
int currentLine = 0;
totalLines = totalValidLines; // Update global totalLines variable
float subMin = FLT_MAX; // Minimum value in subsampled data
float subMax = FLT_MIN; // Maximum value in subsampled data
while (dataFile.available() && index < numDataPoints) {
String line = dataFile.readStringUntil('\n');
currentLine++;
// Skip lines that are not part of the subsampling
if (currentLine % skipFactor != 0 && totalValidLines > numDataPoints) {
continue;
}
// Parse and validate the data
int startIndex = 0;
for (int i = 0; i < commaIndex; i++) {
startIndex = line.indexOf(',', startIndex) + 1;
if (startIndex == 0) continue;
}
int endIndex = line.indexOf(',', startIndex);
if (endIndex == -1) endIndex = line.length();
String valueString = line.substring(startIndex, endIndex);
float value = valueString.toFloat();
if (isnan(value)) continue;
// Update subsampled min/max
subMin = min(subMin, value);
subMax = max(subMax, value);
// Store the valid value
values[index] = value;
index++;
}
// Update fileIndex and subsampled min/max
fileIndex = index;
minValue = subMin;
maxValue = subMax;
dataFile.close();
}
void testSPI() {
Serial.println("\nTesting SPI connections...");
// Set SPI pin modes (adjust for your Arduino model if needed)
pinMode(MISO, INPUT);
pinMode(MOSI, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(chipSelect, OUTPUT);
// Perform a simple SPI transfer test
digitalWrite(chipSelect, LOW);
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
byte response = SPI.transfer(0xFF); // Send dummy byte
SPI.endTransaction();
digitalWrite(chipSelect, HIGH);
if (response != 0xFF) {
Serial.println("SPI test failed! Check MISO, MOSI, SCK, or chipSelect.");
} else {
Serial.println("SPI test passed.");
}
}
void testSDCard() {
Serial.println("\nInitializing SD card...");
// Set chipSelect pin as OUTPUT
pinMode(chipSelect, OUTPUT);
digitalWrite(chipSelect, HIGH);
// Attempt to initialize SD card
if (!SD.begin(chipSelect)) {
Serial.println("SD card initialization failed!");
Serial.println("Possible causes:");
Serial.println("- Incorrect chipSelect pin.");
Serial.println("- Loose connections.");
Serial.println("- Damaged SD card/module.");
return;
}
Serial.println("SD card initialized successfully!");
// Retrieve and print card information
listFiles();
}
void listFiles() {
File root = SD.open("/");
Serial.println("\nListing files on SD card:");
printDirectory(root, 0);
}
void printDirectory(File dir, int numTabs) {
while (true) {
File entry = dir.openNextFile();
if (!entry) {
// No more files
break;
}
for (int i = 0; i < numTabs; i++) {
Serial.print("\t");
}
Serial.print(entry.name());
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs + 1);
} else {
// Files have sizes, print it
Serial.print("\t\t");
Serial.println(entry.size());
}
entry.close();
}
}
// Function to clear the contents of a file
void clearFile(const char *filename) {
if (SD.exists(filename)) {
File myFile = SD.open(filename, O_WRITE | O_TRUNC); // Open with truncate mode
if (myFile) {
myFile.close(); // Closing the file ensures it's truncated to 0 bytes
Serial.print(filename);
Serial.println(" contents erased.");
} else {
Serial.print("Failed to open ");
Serial.println(filename);
}
} else {
Serial.print(filename);
Serial.println(" does not exist.");
}
}
// Function to read the contents of a file
void readFile(const char *filename) {
if (SD.exists(filename)) {
File myFile = SD.open(filename, FILE_READ);
if (myFile) {
Serial.print("Contents of ");
Serial.println(filename);
while (myFile.available()) {
Serial.write(myFile.read()); // Print file content byte by byte
}
myFile.close();
Serial.println("\nEnd of file.");
} else {
Serial.print("Failed to open ");
Serial.println(filename);
}
} else {
Serial.print(filename);
Serial.println(" does not exist.");
}
}
void loadPIDStateFromFile(float &integral, float &previousError) {
myFile = SD.open("DEWLOG00.txt", FILE_READ); // Open the file
if (!myFile) {
// File doesn't exist
Serial.println("DEWLOG00.txt not found. Using default PID state.");
integral = 0.0; // Default value for integral
previousError = 0.0; // Default value for previousError
return;
}
if (!myFile.available()) {
// File exists but is empty
Serial.println("DEWLOG00.txt is empty. Using default PID state.");
integral = 0.0; // Default value for integral
previousError = 0.0; // Default value for previousError
myFile.close();
return;
}
// File exists and has data, read the last line
String lastLine;
while (myFile.available()) {
lastLine = myFile.readStringUntil('\n'); // Read until the last line
}
myFile.close();
// Parse the last line to extract integral and previousError
int lastCommaIndex = lastLine.lastIndexOf(',');
int secondLastCommaIndex = lastLine.lastIndexOf(',', lastCommaIndex - 1);
if (lastCommaIndex != -1 && secondLastCommaIndex != -1) {
integral = lastLine.substring(secondLastCommaIndex + 1, lastCommaIndex).toFloat();
previousError = lastLine.substring(lastCommaIndex + 1).toFloat();
Serial.print("Loaded PID State: Integral = ");
Serial.print(integral);
Serial.print(", PreviousError = ");
Serial.println(previousError);
} else {
// If parsing fails, fallback to defaults
Serial.println("Error parsing DEWLOG00.txt for PID state. Using default values.");
integral = 0.0;
previousError = 0.0;
}
}
Test Data
26.20, 21.00, 58.30, 17.35, 25.00, 65, -1.65, -54.19, -1.65
26.20, 20.75, 58.30, 17.35, 28.00, 73, -1.40, -75.00, -1.40
26.20, 20.94, 58.30, 17.35, 26.00, 67, -1.58, -75.00, -1.58
26.20, 20.94, 58.70, 17.46, 27.00, 70, -1.48, -75.00, -1.48
26.20, 20.88, 58.40, 17.38, 27.00, 69, -1.49, -75.00, -1.49
26.10, 20.81, 58.40, 17.29, 0.00, 0, -1.53, -63.75, -1.53
26.20, 20.75, 58.50, 17.41, 29.00, 75, -1.34, 0.00, -1.34
26.20, 20.81, 58.60, 17.43, 28.00, 73, -1.38, -75.00, -1.38
26.20, 20.69, 58.50, 17.41, 30.00, 77, -1.28, -75.00, -1.28
26.20, 20.75, 58.70, 17.46, 30.00, 77, -1.29, -75.00, -1.29
26.20, 20.81, 58.70, 17.46, 29.00, 74, -1.35, -75.00, -1.35
26.20, 20.75, 58.70, 17.46, 30.00, 77, -1.29, -75.00, -1.29
26.30, 20.75, 58.60, 17.53, 30.00, 79, -1.22, -75.00, -1.22
26.30, 20.69, 58.60, 17.53, 31.00, 81, -1.16, -75.00, -1.16
26.30, 20.75, 58.70, 17.55, 31.00, 80, -1.20, -75.00, -1.20
26.30, 20.75, 58.60, 17.53, 30.00, 79, -1.22, -75.00, -1.22
26.30, 20.88, 58.90, 17.61, 0.00, 0, -1.27, -63.75, -1.27
26.30, 20.88, 58.60, 17.53, 29.00, 74, -1.35, -54.19, -1.35
26.30, 20.88, 58.60, 17.53, 29.00, 74, -1.35, -75.00, -1.35
26.30, 20.75, 58.60, 17.53, 30.00, 79, -1.22, -75.00, -1.22
26.30, 20.81, 58.60, 17.53, 30.00, 77, -1.28, -75.00, -1.28
26.40, 20.81, 58.70, 17.65, 31.00, 81, -1.16, -75.00, -1.16
26.30, 20.81, 58.70, 17.55, 0.00, 0, -1.26, -63.75, -1.26
26.30, 20.69, 58.60, 17.53, 31.00, 81, -1.16, -54.19, -1.16
26.40, 20.75, 58.70, 17.65, 0.00, 0, -1.10, -46.06, -1.10
26.40, 20.62, 59.00, 17.73, 35.00, 91, -0.90, -39.15, -0.90
26.40, 20.62, 58.90, 17.70, 0.00, 0, -0.92, -33.28, -0.92
26.40, 20.56, 58.80, 17.68, 35.00, 91, -0.89, -28.29, -0.89
26.40, 20.44, 58.70, 17.65, 37.00, 95, -0.79, -75.00, -0.79
26.40, 20.38, 58.70, 17.65, 38.00, 97, -0.73, -75.00, -0.73
26.40, 20.56, 58.90, 17.70, 0.00, 0, -0.86, -63.75, -0.86