// FinalProject.ino
// (C)2026 Dakota Thorpe — AVR-safe edition
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <RTClib.h>
#include <DHT11.h>
// Display lib
#include <Adafruit_SSD1306.h>
#include <LiquidCrystal_I2C.h>
// ================= CONFIG =================
// RTC types
#define RTC_TYPE_DS1307 1
#define RTC_TYPE_DS3231 2
// Display Types
#define DIS_TYPE_SSD1306 1 // Adafruit SSD1306
#define DIS_TYPE_LCD162I 2 // I2C LCD 16x2
#define DIS_TYPE_LCD204I 3 // I2C LCD 20x4
#define DIS_TYPE_NONE 4 // None
// Hardware selections
#define RTC_TYPE RTC_TYPE_DS1307
#define DIS_TYPE DIS_TYPE_SSD1306
// SD
const int sdCS = 10;
// Sensors
const int photo_pin = A0;
DHT11 dht11(2);
// =========== DISPLAY OBJECTS ============
#if DIS_TYPE == DIS_TYPE_SSD1306
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
#elif DIS_TYPE == DIS_TYPE_LCD162I
LiquidCrystal_I2C display(0x27, 16, 2);
#elif DIS_TYPE == DIS_TYPE_LCD204I
LiquidCrystal_I2C display(0x27, 20, 4);
#endif
// =========== DISPLAY HANDLE ============
void InitDisplay() {
#if DIS_TYPE == DIS_TYPE_SSD1306
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 init failed"));
return;
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Logger starting..."));
display.display();
#elif DIS_TYPE == DIS_TYPE_LCD162I || DIS_TYPE == DIS_TYPE_LCD204I
display.init();
display.backlight();
display.clear();
display.setCursor(0, 0);
display.print(F("Logger starting"));
#elif DIS_TYPE == DIS_TYPE_NONE
// No display
#endif
}
void UpdateDisplay(const DateTime& now, int temp, int hum, int light) {
#if DIS_TYPE == DIS_TYPE_SSD1306
display.clearDisplay();
display.setCursor(0, 0);
display.print(now.hour()); display.print(':');
display.print(now.minute()); display.print(':');
display.println(now.second());
display.print(F("T: "));
display.print(temp);
display.println(F(" C"));
display.print(F("H: "));
display.print(hum);
display.println(F(" %"));
display.print(F("L: "));
display.println(light);
display.display();
#elif DIS_TYPE == DIS_TYPE_LCD162I
display.clear();
display.setCursor(0, 0);
display.print(now.hour()); display.print(':');
display.print(now.minute()); display.print(':');
display.print(now.second());
display.setCursor(0, 1);
display.print(F("T:"));
display.print(temp);
display.print(F(" H:"));
display.print(hum);
#elif DIS_TYPE == DIS_TYPE_LCD204I
display.clear();
display.setCursor(0, 0);
display.print(F("Time "));
display.print(now.hour()); display.print(':');
display.print(now.minute()); display.print(':');
display.print(now.second());
display.setCursor(0, 1);
display.print(F("Temp "));
display.print(temp);
display.print(F(" C"));
display.setCursor(0, 2);
display.print(F("Hum "));
display.print(hum);
display.print(F(" %"));
display.setCursor(0, 3);
display.print(F("Light "));
display.print(light);
#elif DIS_TYPE == DIS_TYPE_NONE
// No display
#endif
}
// ================= RTC =================
#if RTC_TYPE == RTC_TYPE_DS1307
RTC_DS1307 rtc;
#elif RTC_TYPE == RTC_TYPE_DS3231
RTC_DS3231 rtc;
#else
#error "Unknown RTC type"
#endif
// ================= SD =================
File dataFile;
// ================= BUILD TIME PARSER =================
uint8_t monthFromString(const char* m) {
if (!strcmp(m, "Jan")) return 1;
if (!strcmp(m, "Feb")) return 2;
if (!strcmp(m, "Mar")) return 3;
if (!strcmp(m, "Apr")) return 4;
if (!strcmp(m, "May")) return 5;
if (!strcmp(m, "Jun")) return 6;
if (!strcmp(m, "Jul")) return 7;
if (!strcmp(m, "Aug")) return 8;
if (!strcmp(m, "Sep")) return 9;
if (!strcmp(m, "Oct")) return 10;
if (!strcmp(m, "Nov")) return 11;
if (!strcmp(m, "Dec")) return 12;
return 0;
}
// ================= RTC SETUP =================
void SetRTC2BuildDate() {
char monthStr[4];
int day, year;
int hr, min, sec;
// "__DATE__" → "Mmm dd yyyy"
sscanf(__DATE__, "%3s %d %d", monthStr, &day, &year);
// "__TIME__" → "hh:mm:ss"
sscanf(__TIME__, "%d:%d:%d", &hr, &min, &sec);
uint8_t month = monthFromString(monthStr);
rtc.adjust(DateTime(year, month, day, hr, min, sec));
}
// ================= LOGGING =================
void WriteAllEntries(File& file) {
DateTime now = rtc.now();
char date_str[32];
snprintf(date_str, sizeof(date_str),
"%04d-%02d-%02dT%02d:%02d:%02d",
now.year(), now.month(), now.day(),
now.hour(), now.minute(), now.second());
int temperature = 0;
int humidity = 0;
int light = analogRead(photo_pin);
dht11.readTemperatureHumidity(temperature, humidity);
file.print(date_str);
file.print(",");
file.print(humidity);
file.print(",");
file.print(temperature);
file.print(",");
file.println(light);
UpdateDisplay(now, temperature, humidity, light);
file.flush();
}
// ================= SETUP =================
void setup() {
Serial.begin(9600);
pinMode(photo_pin, INPUT);
Wire.begin();
// Setup Displays
InitDisplay();
if (!rtc.begin()) {
Serial.println(F("RTC not found!"));
while (1);
}
#if RTC_TYPE == RTC_TYPE_DS1307
if (!rtc.isrunning()) {
SetRTC2BuildDate();
}
#else
SetRTC2BuildDate(); // DS3231 always safe to adjust
#endif
if (!SD.begin(sdCS)) {
Serial.println(F("SD init failed"));
while (1);
}
SD.remove("/data.csv");
dataFile = SD.open("/data.csv", FILE_WRITE);
dataFile.println(F("date,hum,temp,light"));
}
// ================= LOOP =================
void loop() {
WriteAllEntries(dataFile);
delay(1000);
}