#include <OneWire.h>
#include <Wire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
// LCD, RTC e SD
constexpr uint8_t LCD_ADDR = 0x27;
constexpr uint8_t LCD_COLS = 20;
constexpr uint8_t LCD_ROWS = 4;
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
RTC_DS3231 rtc;
constexpr uint8_t SD_CS_PIN = 10;
// Sensores em pinos separados
constexpr uint8_t SENSOR_PINS[4] = {2, 3, 4, 5};
OneWire oneWires[4] = {OneWire(SENSOR_PINS[0]), OneWire(SENSOR_PINS[1]), OneWire(SENSOR_PINS[2]), OneWire(SENSOR_PINS[3])};
DallasTemperature sensors[4] = {DallasTemperature(&oneWires[0]), DallasTemperature(&oneWires[1]), DallasTemperature(&oneWires[2]), DallasTemperature(&oneWires[3])};
File dataFile;
// Botões (pull-up interno)
constexpr uint8_t BTN_PROG = 7;
constexpr uint8_t BTN_UP = 6;
constexpr uint8_t BTN_DOWN = 8;
// Menus
constexpr uint8_t MAIN_MENU_SIZE = 3;
const char* mainMenu[MAIN_MENU_SIZE] = {"Automatico", "Selecionar", "Historico"};
constexpr uint8_t SENSOR_MENU_SIZE = 5;
const char* sensorMenu[SENSOR_MENU_SIZE] = {"Ambiente", "Interno", "Externo", "Pastilha", "Sair"};
int mainMenuIndex = 0;
int sensorMenuIndex = 0;
void setup() {
Serial.begin(9600);
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.backlight();
pinMode(BTN_PROG, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
for (auto& sensor : sensors) sensor.begin();
if (!rtc.begin()) {
lcd.clear(); lcd.print("Erro no RTC"); while (1);
}
if (!SD.begin(SD_CS_PIN)) {
lcd.clear(); lcd.print("Erro no SD"); while (1);
}
// Splash
lcd.clear();
lcd.setCursor(5, 1); lcd.print("PeltierLab");
lcd.setCursor(2, 2); lcd.print("Press any button");
while (digitalRead(BTN_PROG) == HIGH &&
digitalRead(BTN_UP) == HIGH &&
digitalRead(BTN_DOWN) == HIGH) {
delay(10);
}
delay(300);
showMainMenu();
}
void loop() {
handleMainMenu();
}
// ——— Funções utilitárias ————————————————————————————————
bool buttonPressed() {
return digitalRead(BTN_PROG) == LOW ||
digitalRead(BTN_UP) == LOW ||
digitalRead(BTN_DOWN) == LOW;
}
void waitForButtonRelease() {
while (buttonPressed()) delay(10);
}
void debounceDelay() {
delay(200);
}
// ——— MENU PRINCIPAL ——————————————————————————————————————
void showMainMenu() {
DateTime now = rtc.now();
lcd.clear();
for (uint8_t i = 0; i < MAIN_MENU_SIZE; i++) {
lcd.setCursor(0, i);
lcd.print((i == mainMenuIndex) ? ">" : " ");
lcd.print(mainMenu[i]);
}
// Hora na última linha
lcd.setCursor(14, 3);
printTime(now.hour(), now.minute());
}
void handleMainMenu() {
if (digitalRead(BTN_UP) == LOW) {
mainMenuIndex = (mainMenuIndex > 0) ? mainMenuIndex - 1 : MAIN_MENU_SIZE - 1;
debounceDelay();
showMainMenu();
}
if (digitalRead(BTN_DOWN) == LOW) {
mainMenuIndex = (mainMenuIndex < MAIN_MENU_SIZE - 1) ? mainMenuIndex + 1 : 0;
debounceDelay();
showMainMenu();
}
if (digitalRead(BTN_PROG) == LOW) {
debounceDelay();
switch (mainMenuIndex) {
case 0: showAutomaticScreen(); break;
case 1: showSensorSelection(); break;
case 2: showHistory(); break;
}
showMainMenu();
}
}
// ——— AUTOMATICO ——————————————————————————————————————
void showAutomaticScreen() {
uint8_t screen = 0; // 0: Temperaturas, 1: Média+Hora+Data
unsigned long lastSwitch = millis();
const unsigned long interval = 1500;
float temps[4], media = 0;
DateTime now;
char hora[6], data[11];
while (true) {
if (screen == 0) {
media = 0;
for (uint8_t i = 0; i < 4; i++) {
sensors[i].requestTemperatures();
temps[i] = sensors[i].getTempCByIndex(0);
media += temps[i];
}
media /= 4.0;
now = rtc.now();
snprintf(hora, sizeof(hora), "%02d:%02d", now.hour(), now.minute());
snprintf(data, sizeof(data), "%02d/%02d/%04d", now.day(), now.month(), now.year());
lcd.clear();
for (uint8_t i = 0; i < 4; i++) {
lcd.setCursor(0, i);
lcd.print(sensorMenu[i]);
lcd.print(" : ");
lcd.print(temps[i], 1);
lcd.write(223); lcd.print("C");
}
} else {
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Media : "); lcd.print(media, 1); lcd.write(223); lcd.print("C");
lcd.setCursor(0, 1); lcd.print("Hora : "); lcd.print(hora);
lcd.setCursor(0, 3); lcd.print("Data : "); lcd.print(data);
// Grava log apenas na segunda tela
char log[64];
snprintf(log, sizeof(log), "%s %s; S1=%.2f; S2=%.2f; S3=%.2f; S4=%.2f; M=%.2f",
data, hora, temps[0], temps[1], temps[2], temps[3], media);
dataFile = SD.open("log.txt", FILE_WRITE);
if (dataFile) { dataFile.println(log); dataFile.close(); }
}
lastSwitch = millis();
while (millis() - lastSwitch < interval) {
if (buttonPressed()) {
waitForButtonRelease();
showMainMenu();
return;
}
delay(10);
}
screen = 1 - screen; // alterna entre 0 e 1
}
}
// ——— SELEÇÃO DE SENSOR ————————————————————————————————
void showSensorSelection() {
sensorMenuIndex = 0;
int displayStart = 0;
auto printSensorMenu = [&]() {
lcd.clear();
for (uint8_t i = 0; i < 4; i++) {
int optionIdx = displayStart + i;
if (optionIdx >= SENSOR_MENU_SIZE) break;
lcd.setCursor(0, i);
lcd.print((optionIdx == sensorMenuIndex) ? ">" : " ");
lcd.print(sensorMenu[optionIdx]);
}
};
printSensorMenu();
while (true) {
if (digitalRead(BTN_UP) == LOW) {
sensorMenuIndex = (sensorMenuIndex > 0) ? sensorMenuIndex - 1 : SENSOR_MENU_SIZE - 1;
if (sensorMenuIndex < displayStart) displayStart = sensorMenuIndex;
if (sensorMenuIndex == SENSOR_MENU_SIZE - 1) displayStart = SENSOR_MENU_SIZE - 4 < 0 ? 0 : SENSOR_MENU_SIZE - 4;
debounceDelay();
printSensorMenu();
}
if (digitalRead(BTN_DOWN) == LOW) {
sensorMenuIndex = (sensorMenuIndex < SENSOR_MENU_SIZE - 1) ? sensorMenuIndex + 1 : 0;
if (sensorMenuIndex > displayStart + 3) displayStart = sensorMenuIndex - 3;
if (sensorMenuIndex == 0) displayStart = 0;
debounceDelay();
printSensorMenu();
}
if (digitalRead(BTN_PROG) == LOW) {
debounceDelay();
if (strcmp(sensorMenu[sensorMenuIndex], "Sair") == 0) return;
float temp = 0;
if (sensorMenuIndex < 4) {
sensors[sensorMenuIndex].requestTemperatures();
temp = sensors[sensorMenuIndex].getTempCByIndex(0);
}
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Sensor: "); lcd.print(sensorMenu[sensorMenuIndex]);
lcd.setCursor(0, 1); lcd.print("Temp : "); lcd.print(temp, 1); lcd.write(223); lcd.print("C");
delay(3000);
printSensorMenu();
}
}
}
// ——— HISTÓRICO ————————————————————————————————————————
void showHistory() {
String logs[4];
int count = 0;
File f = SD.open("log.txt");
if (f) {
int totalLines = 0;
while (f.available()) { f.readStringUntil('\n'); totalLines++; }
f.seek(0);
int skip = totalLines > 4 ? totalLines - 4 : 0;
for (int i = 0; i < skip; i++) f.readStringUntil('\n');
while (f.available() && count < 4) logs[count++] = f.readStringUntil('\n');
f.close();
}
int selected = 0, lastSelected = -1;
while (true) {
if (selected != lastSelected) {
lcd.clear();
lcd.setCursor(0, 0);
for (int i = 0; i < count; i++) {
lcd.setCursor(0, i + 1);
lcd.print((i == selected) ? ">" : " ");
lcd.print(logs[i].substring(0, 18));
}
lastSelected = selected;
}
if (digitalRead(BTN_UP) == LOW) {
selected = (selected > 0) ? selected - 1 : count - 1;
debounceDelay();
}
if (digitalRead(BTN_DOWN) == LOW) {
selected = (selected < count - 1) ? selected + 1 : 0;
debounceDelay();
}
if (digitalRead(BTN_PROG) == LOW) {
debounceDelay();
return;
}
}
}
// ——— Funções auxiliares ————————————————————————————————
void printTime(uint8_t hour, uint8_t minute) {
if (hour < 10) lcd.print('0');
lcd.print(hour);
lcd.print(':');
if (minute < 10) lcd.print('0');
lcd.print(minute);
}
Up
Down
PROG