/*
Display 16x2
[][][][x][][][][][][][][][x][][][]
[][][][][][][][][][][][][][][][]
Filament dryer
Conexões do display e botões (Vista de cima)
[A][B][C][D][E][F][G][H][I] [J][K][L][M][N][O][P][Q]
A = 5v
B = D7
C = D6
D = GND
E = WLED
F = D5
G = D4
H = GND
I = BLED
J = EN
K = GLED
L = YLED
M = RS
N = RLED
O = BT+
P = BTM
Q = BT-
Placa do microcntrolador (Vista dos pinos)
[A][B][C][D][E][F][G][H][I][J][K] [L][M][N][O][P][Q]
PORTA OBJETIVO
A = D5 => D4 display
B = D4 => D5 display
C = D3 => D6 Display
D = D2 => D7 Display
E = D6 => EN Display
F = D7 => RS Display
G = D8 => YLED
H = D9 => WLED
I = D10 => GLED
J = D11 => RLED
K = D12 => BLED
L = 5V => 5V
M = A0 => Termistor
N = GND => GND
O = A1 => BTM
P = A2 => BT+
Q = A3 => BT-
*/
// ==== Inclusão de bibliotecas: ====
#include <LiquidCrystal.h>
#include "neotimer.h"
#include <PID_v1_bc.h>
// ==== Definição de portas: ====
const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
const int Power_Resistance = 9; //Envia sinais de controle da resistencia para aquecer a camara
#define Buzzer 8 //Ativa o buzzer em casos de emergencia
#define Rele_Power_Circuit 10 //Liga/Desliga o circuito de controle da resistencia/buzzer/Fan
#define Security_Reles 11 //Desliga apenas a Resistencia em casos de emergência
#define Rele_Power_Fan 12 //Desliga apenas o Fan caso seja necessário
#define Termistor A0 //Faz a leitura de temperatura
#define Door 13 //usado para um sensor na porta
int Portas[] = {Buzzer, Rele_Power_Circuit, Security_Reles, Rele_Power_Fan, Termistor};
int Bot[] = {A1, A2, A3}; //define botões Menu / + / -
int AntBot[] = {0, LOW, LOW};
// ==== Variaveis: ====
byte Paw[] = {
B00000,
B00100,
B10101,
B10001,
B01110,
B11111,
B11011,
B00000
};
String MenuI[] = {"Back", "Resfriar", "AutoTune", "PID", "Ventilacao", "Tempo", "Seguranca"};
String PID_I[] = {"Back", "Kp: ", "Ki: ", "Kd: "};
double pidAjust[3][3] = {
{30.0, 1.5, 10.0}, // Variavel
{255.0, 255.0, 255.0}, // Limite máximo
{0.0, 0.0, 0.0} // Limite minimo
};
String Ventilacao[] = {"Back", "Fan: ", "Porta: ", "SincR: ", "SincRInvert: "};
int FanAjust[3][4] = {
{1, 1, 0, 0}, // Variavel
{1, 1, 1, 1}, // Limite máximo
{0, 0, 0, 0} // Limite minimo
};
String Tempo[] = {"Back", "TR-Ativo: ", "TF-Ativo: ", "FinalAtivo: ", "FinalTempo: "};
int TempoAjust[3][4] = {
{0, 0, 0, 0}, // Variavel
{360, 360, 2, 30}, // Limite máximo minutos
{0, 0, 0, 0} // Limite minimo
};
String Seguranca[] = {"Back", "Alarme: ", "SobTemp: ", "TLT: "};
int alarmAjust[3][3] = {
{0, 20, 30}, // Variavel
{1, 50, 120}, // Limite máximo
{0, 0, 0} // Limite minimo
};
int Position = 0; // define a posição dos itens no menu
int C_Position = 0; // define a posição do cursor no menu
byte menu = 0; // define a configuração escolhida no menu
int MaxMenuLimits[] = {110, 5, 0, 2, 3, 3, 2};
int MinMenuLimits[] = {0, 0, 0, 0, 0, 0, 0};
int AltSetpoint = 0; // define se está em modo de setpoint
byte Icon = 0; // faz piscar um icOneA indicando que está no modo de setpoint
byte OneA = 0; // variavel que limita a quantidade de vezes em que um comando será exewcutado
byte OneB = 0; // usado para resetar o contador de tempo de um botão
byte OneC = 0; // usado para resetar o contador de tempo de um botão
byte OneD = 0; // usado para resetar o contador de tempo de um botão
byte Edit = 0; // permite ou não alterar o valor de alguma configuração
float Val = 0; // usado para alterar os valores das Configurações
// ==== Variáveis do PID ====
double setpoint; // Temperatura desejada (por exemplo, 50 ºC)
double input; // Temperatura atual lida do sensor
double output; // Saída do PID (usada para controle do aquecedor)
// ==== Parâmetros PID (ajuste necessário conforme o sistema real) ====
double Kp = 30.0;
double Ki = 1.5;
double Kd = 10.0;
const float R_SERIE = 100000.0; // Resistor de 10k em série com o NTC
const float BETA = 3950.0; // Constante beta do NTC
const float T0 = 298.15; // Temperatura nominal em Kelvin (25°C)
const float R0 = 100000.0; // Resistência do NTC a 25°C (10kΩ)
const float VCC = 5.0; // Tensão de alimentação do divisor
int RunnoutTemp = 20; //define um valor maximo de ultrapassagem do setpoint
int TimerTemp = 30000; //define o tempo maximo de espera até que a temperatura altere
int MaxTemp = 110; //define o valor máximo de temperatura
int MinTemp = 0; //define o valor minimo de temperatura
int DefineTemp = 00.0; //obtem um valor definido pelo usuário e guarda em setpoint
// ==== Definição de bibliotecas: ====
PID meuPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
Neotimer Delay0 = Neotimer(1000); //delay de leitura e monitoramento do PID
Neotimer Delay1 = Neotimer(100); //delay de atualização do display
Neotimer Delay2 = Neotimer(500); //delay de
Neotimer Delay3 = Neotimer(100); //delay de contagem do tempo em que ficou pressionado o botão "Menu"
Neotimer Delay4 = Neotimer(100); //delay de contagem do tempo em que ficou pressionado o botão " + "
Neotimer Delay5 = Neotimer(100); //delay de contagem do tempo em que ficou pressionado o botão " - "
Neotimer Delay6 = Neotimer(1); //delay de aumento de variavel
void setup() {
delay(100);
lcd.begin(16, 2);
Serial.begin(9600);
lcd.createChar(0, Paw);
pinMode(Power_Resistance, OUTPUT);
pinMode(Termistor, INPUT);
pinMode(Door, INPUT);
for (int Np = 0; Np <= 3; Np++) {
pinMode(Portas[Np], OUTPUT);
}
//resistencia 5v para manter desligado
//buzzer 0v para manter desligado
//rele fan 5v para manter desligado
//rele circuito 5v para manter desligado
//rele redsistencia 5v para manter desligado
setpoint = 00.0; // Define o setpoint (exemplo: 50 graus Celsius)
meuPID.SetOutputLimits(0, 255); // PWM 8 bits - Configura a saída máxima do PWM
meuPID.SetMode(AUTOMATIC); // Inicializa o PID em modo automático
analogWrite(Power_Resistance, 0); // Inicializa a saída do aquecedor desligada
lcd.clear(); //limpa o display
for (int Np = 1; Np <= 3; Np++) {
digitalWrite(Portas[Np], HIGH);
}
digitalWrite(Power_Resistance, HIGH);
lcd.setCursor(5, 0);
lcd.print("Coffee"); // Escreve um Nome Logo
digitalWrite(Portas[1], LOW); // Liga o circuito de aquecimento
digitalWrite(Portas[], LOW);
delay(1000);
digitalWrite(Portas[0], HIGH); // liga o buzzer
lcd.setCursor(3, 0); // Move o cursor no display
lcd.write(byte(0)); // desenha icOneAs ao lado do nome
lcd.setCursor(12, 0); // Move o cursor no displayc
lcd.write(byte(0)); // desenha icOneAs ao lado do nome
delay(500); // tempo em de espera com o buzzer ativo
digitalWrite(Portas[0], LOW); // desliga o buzzer
delay(1500);
digitalWrite(Portas[0], HIGH); // liga o buzzer
delay(100);
digitalWrite(Portas[0], LOW); // desliga o buzzer
delay(100);
digitalWrite(Portas[0], HIGH); // liga o buzzer
lcd.setCursor(0, 1);
lcd.print(" Filament Dryer");
delay(100);
digitalWrite(Portas[0], LOW); // desliga o buzzer
delay(100);
delay(1600);
lcd.clear(); //limpa o display
}
void loop() {
Display();
//int Map = map((int)output, 0, 225, 255, 0);
int Map = output;
// analogWrite(Power_Resistance, Map);
if (Map >= 0 && Map <= 127) {
digitalWrite(Power_Resistance, HIGH);
} else if (Map >= 128 && Map <= 256) {
digitalWrite(Power_Resistance, LOW);
}
if (DefineTemp <= 0) {
digitalWrite(Portas[2], LOW);
} else {
digitalWrite(Portas[2], HIGH);
}
// Tempo entre leituras (para sistemas térmicos, 1 segundo é bom)
if (Delay0.repeat()) {
// Lê a temperatura atual (simulação)
input = lerTemperaturaNTC();
// Calcula a nova saída do PID
meuPID.Compute();
// Aplica a saída no aquecedor
/* Exibe no monitor serial
Serial.print("Temperatura: ");
Serial.print(input);
Serial.print(" °C | Setpoint: ");
Serial.print(setpoint);
Serial.print(" °C | Saída PWM: ");
Serial.println(Map);*/
}
}
void Display() {
if (menu == 0) { //tela principal
if (Delay1.repeat()) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sv:");
lcd.setCursor(3, 0);
lcd.print(setpoint);
lcd.setCursor(0, 1);
lcd.print("Pv:");
lcd.setCursor(3, 1);
lcd.print(input);
lcd.setCursor(9, 0);
lcd.print("Pw:");
lcd.setCursor(12, 0);
lcd.print(output);
if (AltSetpoint == 1) { //definir um novo setpoint
if (Delay2.repeat()) {
Icon = !Icon;
}
if (Icon == 1) {
lcd.setCursor(13, 1);
lcd.write(byte(0));
lcd.setCursor(12, 1);
lcd.write(byte(0));
lcd.setCursor(11, 1);
lcd.write(byte(0));
}
} else {
if (Delay2.repeat()) {
Icon++;
if (Icon == 3) {
Icon = 0;
}
}
lcd.setCursor(11 + Icon, 1);
lcd.write(byte(0));
}
}
} else if (menu == 1) { // menu de seleção
lcd.setCursor(2, 0); //define a primeira linha
lcd.print(MenuI[Position]); //imprime uma configuração na primeira linha
lcd.setCursor(2, 1); //define a segunda linha
lcd.print(MenuI[Position + 1]); //imprime uma configuração na segunda linha
lcd.setCursor(0, C_Position);
lcd.write(byte(0));
} else if (menu == 2) { //iniciar AutoTune
menu = 1;
} else if (menu == 3) { // configuração de PID
lcd.setCursor(2, 0); //define a primeira linha
lcd.print(PID_I[Position]); //imprime uma configuração na primeira linha
lcd.setCursor(2, 1); //define a segunda linha
lcd.print(PID_I[Position + 1]); //imprime uma configuração na segunda linha
if (Position == 0) {
lcd.setCursor(5, 1);
lcd.print(pidAjust[0][Position]);
} else {
lcd.setCursor(5, 0);
lcd.print(pidAjust[0][Position - 1]);
lcd.setCursor(5, 1);
lcd.print(pidAjust[0][Position]);
}
lcd.setCursor(0, C_Position); //move o cursor
lcd.write(byte(0));
if (Edit == 1) {
if (OneA == 1) {
Serial.println("Modo de edição ativo");
int Np = Position + C_Position; //guarda a posição do cursor para obter um valor do PID
pidAjust[0][0] = meuPID.GetKp();
pidAjust[0][1] = meuPID.GetKi();
pidAjust[0][2] = meuPID.GetKd();
if (Position == 0 && C_Position == 1) {
Val = pidAjust[0][Position]; //Obtém um valor de configuração do PID
} else {
Val = pidAjust[0][Position - 1 + C_Position];
}
// Val = pidAjust[0][Np]; //Obtém um valor de configuração do PID
OneA = 0; //evita com que o valor seja resetado
}
if (Position == 0 && C_Position == 0) { //volta para o menu de configgurações
Edit = 0; //desativa o modo de edição
Position = 0; //reseta a posição dos icOneAs
C_Position = 0; //reseta a posição do cursor
menu = 1; //retorna para o menu principal
lcd.clear(); //apaga os icOneAs da configuração atual
} else if (Position == 0 && C_Position == 1) {
pidAjust[0][Position] = Val;
} else {
pidAjust[0][Position - 1 + C_Position] = Val;
}
meuPID.SetTunings(pidAjust[0][0], pidAjust[0][1], pidAjust[0][2]);
}
} else if (menu == 4) { // configuração de refrigeração
lcd.setCursor(2, 0); //define a primeira linha
lcd.print(Ventilacao[Position]); //imprime uma configuração na primeira linha
lcd.setCursor(2, 1); //define a segunda linha
lcd.print(Ventilacao[Position + 1]); //imprime uma configuração na segunda linha
lcd.setCursor(0, C_Position);
lcd.write(byte(0));
} else if (menu == 5) { // configuração de tempo
lcd.setCursor(2, 0); //define a primeira linha
lcd.print(Tempo[Position]); //imprime uma configuração na primeira linha
lcd.setCursor(2, 1); //define a segunda linha
lcd.print(Tempo[Position + 1]); //imprime uma configuração na segunda linha
lcd.setCursor(0, C_Position);
lcd.write(byte(0));
} else if (menu == 6) { // configuração de alarme
lcd.setCursor(2, 0); //define a primeira linha
lcd.print(Seguranca[Position]); //imprime uma configuração na primeira linha
lcd.setCursor(2, 1); //define a segunda linha
lcd.print(Seguranca[Position + 1]); //imprime uma configuração na segunda linha
lcd.setCursor(0, C_Position);
lcd.write(byte(0));
}
if (digitalRead(Bot[0]) == HIGH && AntBot[0] <= 1000) { //ajustar/confirmar/menu
if (Delay3.repeat(22)) { //repetição de 1 milisegundo para contagem do botão
AntBot[0] = AntBot[0] + 50;
// Serial.print("AumentoA: ");
// Serial.println(AntBot[0]);
}
} else if (AntBot[0] > 50 && AntBot[0] > 1000) { //opção caso o botão seja pressionado por mais de 1 segundo
if (menu == 0) {
AntBot[0] = 0; //reseta a contagem de tempo do botão pressionado
lcd.clear(); //limpa o display para exibir uma nova tela
menu = 1; //seleciona o menu de configurações
}
Serial.println("OK Menu");
OneB = 1;
} else if (AntBot[0] > 50 && AntBot[0] <= 1000) { //opção caso o botão seja apertado por menos de 1 segundo
if (menu == 0) {
if (AltSetpoint == 1) { //verifica o estado de AltSetpoint para garantir a escolha do setpoint correto
setpoint = DefineTemp;
}
AltSetpoint = !AltSetpoint; //altera o estado de Altsetpoint para poder alterar o valor de setpoint principal
} else if (menu == 1) {
if (Position + C_Position == 1) { //Função resfriar
DefineTemp = 0;
setpoint = DefineTemp;
lcd.clear();
menu = 0;
} else { //seleção de configuração
menu = Position + C_Position;
Position = 0;
C_Position = 0;
lcd.clear();
}
} else if (menu == 3) {
Edit = !Edit;
OneA = 1;
}
AntBot[0] = 0; //reseta a contagem de tempo do botão pressionado
OneB = 1;
} else {
if (OneB == 1) {
Delay3.repeatReset();
AntBot[0] = 0; //reseta a contagem de tempo do botão pressionado
OneB = 0;
}
}
if (digitalRead(Bot[1]) == HIGH && AntBot[1] <= 1000) { //ajustar/confirmar/menu
if (Delay4.repeat(22)) { //repetição de 1 milisegundo para contagem do botão
AntBot[1] = AntBot[1] + 50;
// Serial.print("AumentoB: ");
// Serial.println(AntBot[1]);
}
} else if (AntBot[1] > 50 && AntBot[1] > 1000 && digitalRead(Bot[1]) == HIGH) { //opção caso o botão seja pressionado por mais de 1 segundo
Val = Val + 0.10;
OneC = 1;
Serial.println("OK avanço");
} else if (AntBot[1] > 50 && AntBot[1] <= 1000) { //opção caso o botão seja apertado por menos de 1 segundo
if (menu == 0 && AltSetpoint == 1) { //verifica se está na tela inicial
DefineTemp = DefineTemp + 5; //Aumenta o Valor de setpoint
if (DefineTemp > MaxTemp) { //limita o aumento de setpoint maximo
DefineTemp = MaxTemp;
}
setpoint = DefineTemp;
} else if (menu >= 1) {
if (Edit == 1) {
Val = Val + 0.10;
Serial.print("Valor aumentado: "); Serial.println(Val);
} else {
C_Position--; //diminui a posição do cursor até o limite do display
if (C_Position < 0) { //verifica se o cursor ultrapassou o limite
C_Position = 0; //retorna o cursor para o seu limite minimo
Position--; //diminui valor de position para alterar a posição dos icOneAs (Sobe eles no display)
}
if (Position <= MinMenuLimits[menu]) { //verifica se o valor de posição dos itens de menu é menor que o limite
Position = MinMenuLimits[menu]; //define o valor minimo aceitavel pelo limite de icOneAs do menu
}
Serial.print("posição: ");
Serial.println(Position);
Serial.print("Cursor: ");
Serial.println(C_Position);
lcd.clear();
}
}
OneC = 1;
AntBot[1] = 0; //reseta a contagem de tempo do botão pressionado
} else {
if (OneC == 1) {
Delay4.repeatReset();
AntBot[1] = 0; //reseta a contagem de tempo do botão pressionado
OneC = 0;
}
}
if (digitalRead(Bot[2]) == HIGH && AntBot[2] <= 1000) { //ajustar/confirmar/menu
if (Delay5.repeat(22)) { //repetição de 1 milisegundo para contagem do botão
AntBot[2] = AntBot[2] + 50;
// Serial.print("AumentoC: ");
// Serial.println(AntBot[2]);
}
} else if (AntBot[2] > 50 && AntBot[2] > 1000 && digitalRead(Bot[2]) == HIGH) { //opção caso o botão seja pressionado por mais de 1 segundo
Val = Val - 0.10;
OneD = 1;
Serial.println("OK Retorno");
} else if (AntBot[2] > 50 && AntBot[2] <= 1000) { //opção caso o botão seja apertado por menos de 1 segundo
if (menu == 0 && AltSetpoint == 1) { //verifica se está na tela inicial
DefineTemp = DefineTemp - 5; //Diminui o valor de setpoint
if (DefineTemp < MinTemp) { //limita a diminuição do setpoint Minimo
DefineTemp = MinTemp;
}
setpoint = DefineTemp;
} else if (menu >= 1) {
if (Edit == 1) {
Val = Val - 0.10;
Serial.print("Valor diminuido: "); Serial.println(Val);
} else {
C_Position++;
if (C_Position > 1) { //verifica se o cursor ultrapassou o limite
C_Position = 1; //retorna o cursor para o seu limite minimo
Position++; //diminui valor de position para alterar a posição dos icOneAs (Sobe eles no display)
}
if (Position > MaxMenuLimits[menu]) { //verifica se o valor de posição dos itens de menu é maior que o limite aceitavel
Position = MaxMenuLimits[menu]; //define o valor maximo aceitavel pelo limite de icOneAs do menu
}
lcd.clear();
Serial.print("posição: ");
Serial.println(Position);
Serial.print("Cursor: ");
Serial.println(C_Position);
}
}
OneD = 1;
AntBot[2] = 0; //reseta a contagem de tempo do botão pressionado
} else {
if (OneD == 1) {
Delay5.repeatReset();
AntBot[2] = 0; //reseta a contagem de tempo do botão pressionado
OneD = 0;
}
}
/*
if (Delay4.repeat()) {
if (digitalRead(Bot[1]) == HIGH && AntBot[1] == LOW) { //subir/temperatura
if (menu == 0 && AltSetpoint == 1) { //verifica se está na tela inicial
DefineTemp = DefineTemp + 5; //Aumenta o Valor de setpoint
if (DefineTemp > MaxTemp) { //limita o aumento de setpoint maximo
DefineTemp = MaxTemp;
}
setpoint = DefineTemp;
} else if (menu >= 1) {
if (Edit == 1) {
Val++;
Serial.print("Valor aumentado: "); Serial.println(Val);
} else {
C_Position--; //diminui a posição do cursor até o limite do display
if (C_Position < 0) { //verifica se o cursor ultrapassou o limite
C_Position = 0; //retorna o cursor para o seu limite minimo
Position--; //diminui valor de position para alterar a posição dos icOneAs (Sobe eles no display)
}
if (Position <= MinMenuLimits[menu]) { //verifica se o valor de posição dos itens de menu é menor que o limite
Position = MinMenuLimits[menu]; //define o valor minimo aceitavel pelo limite de icOneAs do menu
}
Serial.print("posição: ");
Serial.println(Position);
Serial.print("Cursor: ");
Serial.println(C_Position);
lcd.clear();
}
}
}
AntBot[1] = digitalRead(Bot[1]);
if (digitalRead(Bot[2]) == HIGH && AntBot[2] == LOW) { //descer/temperatura
if (menu == 0 && AltSetpoint == 1) { //verifica se está na tela inicial
DefineTemp = DefineTemp - 5; //Diminui o valor de setpoint
if (DefineTemp < MinTemp) { //limita a diminuição do setpoint Minimo
DefineTemp = MinTemp;
}
setpoint = DefineTemp;
} else if (menu >= 1) {
if (Edit == 1) {
Val--;
Serial.print("Valor diminuido: "); Serial.println(Val);
} else {
C_Position++;
if (C_Position > 1) { //verifica se o cursor ultrapassou o limite
C_Position = 1; //retorna o cursor para o seu limite minimo
Position++; //diminui valor de position para alterar a posição dos icOneAs (Sobe eles no display)
}
if (Position > MaxMenuLimits[menu]) { //verifica se o valor de posição dos itens de menu é maior que o limite aceitavel
Position = MaxMenuLimits[menu]; //define o valor maximo aceitavel pelo limite de icOneAs do menu
}
lcd.clear();
Serial.print("posição: ");
Serial.println(Position);
Serial.print("Cursor: ");
Serial.println(C_Position);
}
}
}
AntBot[2] = digitalRead(Bot[2]);
} */
}
double lerTemperaturaNTC() {
int leitura = analogRead(Termistor);
// Converte leitura (0–1023) para tensão
float Vout = leitura * VCC / 1023.0;
// Calcula resistência do termistor (NTC)
float R_NTC = (VCC * R_SERIE / Vout) - R_SERIE;
// Aplica a equação simplificada de Steinhart-Hart
float temperaturaK = 1.0 / (1.0 / T0 + (1.0 / BETA) * log(R_NTC / R0));
float temperaturaC = temperaturaK - 273.15;
return temperaturaC;
}